思维自惊奇于疑问开始

栈溢出

栈图

return to text


64位二进制文件,保护措施均未打开,将文件拖进ida查看

发现漏洞,read函数读入200bit的数据,而在栈上开辟了128bit

检查函数发现后门函数callsystem在里面发现/bin/sh

通过gbd动态调试,我们在read函数中写入AAAAAAAA,发现其距离rbp128个字节

rbp上方是父函数的返回地址,先用8个字节的垃圾数据填入rbp中(32位的文件则填入4个字节)我们在其中填入8个字节的返回地址,也就是/bin/sh的地址。
那我们怎么获得/bin/sh的地址,首先应清除一点,也就是对面服务器的栈随机化是一定会打开的,所以我们用symbols这个函数查找/bin/sh
开始编写pyload,先让我们的机器接收数据recvuntil,直到接收到orld\n,然后构建payload,b’A’*(0x80+0x8)+binsh

return to shellcode

使用shellcraft.sh(),未找到bin/sh,但栈可执行

观察文件类型,32位程序

拖进ida,查看漏洞,虽然定义是char[136],但read往其读入了0x100的数据,但与上一题不同的是,并没有在程序中找到bin/sh,但这题的栈可执行是开启的,即Stack: Executable,这就可以使得我们可以把bin/sh/发送到栈上,然后执行,但是由于aslr的存在,我们并不知道我们写入的bin\sh的地址,但是观察函数,char[136],后面紧跟着write,给出了我们的写入的地址,所以我们就可以通过接收这个地址来确定我们填入的bin\sh的存在。用到nop滑梯,也就是\x90,程序执行到时,不进行任何操作,执行下一步

经过计算,rbp和rsp的距离是136

那么我们生成shellcode,这里我是用shellcode=asm(shellcraft.sh()),使用shellcraft,生成带有/bin/sh的汇编指令,然后asm将汇编指令转为二进制形式

return to systemcall

checksec一下,32位文件类型,但是nx打开,栈不可执行

拖进ida,发现程序中存在system,所以这题可以使用return to systemcall
因为程序中存在system,所以在程序的got表中就存在system的地址,我们可以用elf.symbols[‘system’],来获得system的实际地址

再次观察ida,使用shift+f12搜索字符串,发现/bin/sh

这里解释一下为什么我们可以用/bin/sh字符串完成我们的系统调用,原因是——/bin/sh是在text段上的,这是一段可读可执行的代码区。
在一个安全的程序中,写和执行权限一般不可能存在相同的一段代码区域,我们现在返回systemcall和之后返回libc都是这种原因。

这里笔者的虚拟机出现了问题,在执行system时程序直接崩掉了,把断点打在read上,输入数据查看栈也并没有得出栈溢出的距离,……无奈之下看了写好的shellcode,难搞。撰写payload,并进入交互模式
这里next函数是用于获取下一个数据,因为search函数查询到的数据是用列表的形势给出的,所以使用next可以得到实际的地址

return to libc

checksec一下,nx enabled栈不可执行

将文件拖进ida,并没有发现system以及/bin/sh,其实这里观察文件,发现其函数链接库很少,不难判断这是一个动态链接的程序,这道题也是给出了libc.29.io

那就不难判断力,return to libc就好
这里也是说明一下原理,在栈上连续填入system,exit,/bin/sh,0,函数调用的参数在其上两位,即system所需的是/bin/sh,exit所需的是0,这样我们就执行了一次系统调用

动态调试一下,发现栈溢出的距离是136,因为这是32位程序,所以再加4

由于笔者这次攻打的是本地,与远程服务器的libc版本不同,所以先用ldd level3这一指令调出本地的libc链接库

这里的思路是先调用write函数,将write函数再got表中的地址利用泄露出来,先浅析一下原理
程序调用函数,会先在plt表中查询got表,再从got表中获取地址,若是第一次调用函数,got表中填入的地址就是plt中的地址,程序就会返回plt表,查询被调用li函数的实际地址,再返回got,将实际地址存入got表中,之后调用,就直接从plt表再到got表,从got获得实际地址,直接调用函数就好,这样做有个好处就是可以减少程序的内存消耗。所以我们调用完函数之后,可以直接再got表中获得实际地址
再清楚一点,我们利用elf.got获得的是got表中表象的地址,而got填入的是实际地址,为了获取实际地址,应使用next()之一函数。

这里我们先构建能获得got表中write实际地址的payload

笔者又出现了问题……检查了好几遍,也没有发现recv之后那个乱码是怎么来的,不过实际地址却跟在后面……那就先这样吧,也能做题
好吧,这并不是乱码,确实是得到的实际地址,只不过不可见字符以这种形式出现……我用u32转义时也报错,发现是字符不够四字节……又在报错

得到write实际地址之后,用它的地址减去在libc库中的地址,就得到了函数的偏移地址,加上system的地址,就得到了system的实际地址,同理,也得到了bin/sh的实际地址

64位

32位与64位的参数传递并不相同,64位架构下前6个参数依次放在rdi、rsi、rds、rcs、r8、r9寄存器中第7个以后的参数存放在栈中
所以构造ROP链时,应
ROP=pop_rdi_ret+system_adds+binsh_adds,根据ROP链的长度选择合适的寄存器
而不是在32位下
ROP=system_adds+b’dead’+binsh_adds

这种技巧最终也是return to libc,不做赘述,

结束

写完这篇ROP技巧花费了约3天的时间,既是有上课的原因,也有笔者对知识的遗忘,所以虽说是技巧,其实也是对ROP查漏补缺,用于以后的翻阅浏览。