-
-
[原创]心得分享之PWN的ROP构造
-
发表于: 2023-7-27 10:20 4891
-
前言:
ROP(Return Oriented Programming),栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程,这里的小片段就是函数在结束时以ret结尾的指令序列。ret指令对应的指令为call,call调用函数,结束以后为了继续往下执行,ret就会返回调用处,如果程序存在溢出,那么就会覆盖掉返回地址,也就是程序跳转到其他地方。
导入:
我们可以这样理解ROP:简单来说就是程序自己攻击自己,利用程序中的一些方法和命令,执行我们想要的功能。当然根据前面说的可知要想利用ROP,必须满足以下条件:
·程序存在溢出,并且可以控制返回地址;
·可以找到相应gadgets以及相应gadgets的地址;
下面首来了解保护机制,用到的软件也是常用的软件checksec:
RELRO:RELRO会有Partial RELRO和FULL RELRO,如果开启FULL RELRO,意味着我们无法修改got表;
Stack:如果栈中开启Canary found,那么就不能用直接用溢出的方法覆盖栈中返回地址,而且要通过改写指针与局部变量、leak canary、overwrite canary的方法来绕过;
NX:NX enabled如果这个保护开启就是意味着栈中数据没有执行权限,以前的经常用的call esp或者jmp esp的方法就不能使用,但是可以利用rop这种方法绕过;
PIE:PIE enabled如果程序开启这个地址随机化选项就意味着程序每次运行的时候地址都会变化,而如果没有开PIE的话那么No PIE (0x400000),括号内的数据就是程序的基地址
FORTIFY:FORTIFY_SOURCE机制对格式化字符串有两个限制(1)包含%n的格式化字符串不能位于程序内存中的可写地址。(2)当使用位置参数时,必须使用范围内的所有参数。所以如果要使用%7$x,你必须同时使用1,2,3,4,5和6;
RPATH/RUNPATH:可以在编译时指定程序运行时动态链接库的搜寻路径,防止将一些动态库恶意替换,以达到攻击目的。查找动态库的过程中,大致的顺序是 RPATH→LD_LIBRARY_PATH→RUNPATH,所以,如果使用的是RPATH用户将无法进行调整,所以建议使用RUNPATH。
这里就是开启了NX,也就是栈中的数据没有执行权限,我们无法通过堆栈注入代码来实现执行程序功能:
先介绍几种常规的ROP。
ret2text:
这种是最经典且最简单的,因为程序中有我们想要的函数和功能,可理解为后门函数,查看汇编代码:
可以看到在.text段中的0804863A中存在一个/bin/sh的后门函数,我们只需要将溢出的返回地跳到此处让它执行命令即可。
溢出的payload可以写为 ‘A’* 溢出大小+4字节的偏移量+bin/sh的地址。
ret2syscall:
字面意思就是系统调用,即只要我们把对应获取 shell 的系统调用的参数放到对应的寄存器中,再执行 int 0x80,就可以执行相应的命令。这里用到一个工具ROPgadget,常用命令如下:
保存系统调用号的寄存器eax:
ROPgadget --binary 【文件名】--only 'pop|ret' | grep 'eax'
寻找保存函数参数的通用寄存器:
ROPgadget --binary 【文件名】--only 'pop|ret' | grep 'ebx'
寻找保存int80的地址:
ROPgadget --binary 【文件名】--only int
寻找保存‘/bin/sh’的地址:
ROPgadget --binary 【文件名】--string '/bin/sh'
之后就是将对应的地址写入payload组合,这个方法不做过多介绍。
ret2libc:
这种方法涉及到一个知识点,说到此不得不提到下面这幅图表:
其实可以看作plt表存放got表的地址,既可执行文件调用相关函数只需要查询plt表的地址,然后plt就会jmp到got中,got表中存放了libc对应函数的相关地址。我们同样来分析一下汇编代码:
图中可以看到08048440是plt的基地址,像puts函数,它的push 10h对应的地址就是它的plt地址,jmp sub_8048440就会跳转到开始,最终跳转到dword_804A008也就是got的起始地址。
由于puts函数的偏移量为push 10h ,也就是10个偏移量。那么 dword_804A008 +10h 就是 dword_804A018 程序,也就找到了在got表中的地址。在程序运行时,通过got表中的地址就可以找到puts函数。
其实就是将一个程序在运行时寻找函数的一个过程,而libc就是我们存放各种函数的一个动态链接库,这个链接库根据系统的不同,版本也不同。可通过溢出调用系统libc中想要的函数从而达到我们想要的结果。
具体的思路就是通过got泄露。一般通过像puts函数来输出相关的got地址,通过这个地址来获取真实地址,通过查询libc版本获取libc基址再根据相对地址的偏移量获取相关函数的地址,具体操作如下:
这里为了方便就直接使用pwntools库中的elf函数来获取puts的plt和got地址,在这里plt可理解为调用,got就是libc的真实地址,main是主程序运行时的地址。可理解为:puts(libc_address)。
在payload中因为有时候程序会报错,所以在溢出执行puts后返回到main函数本来的地址,让程序继续执行。
题外话:#这里我也有个问题,至今都不明白,puts_plt,第一个函数,main_addr,第二个函数,puts_got第一个函数的参数,第三个函数,第二个函数的参数.......以此类推。
然后我们就得到puts函数libc(真实)的地址,可以在线查询https://libc.blukat.me
可以看到对应版本的libc的偏移量0x067360
那么
libc起始地址=puts的真实地址-0x067360
加入想要执行system函数
System真实地址=libc起始地址+0x03cd10
通过构造和发送即可执行我们想要的相关命令。
小结:
这次先分享这么多,以上内容只是构造ROP的一部分,更多的内容后续也会跟大家分享。一入PWN坑深似海,总结一下PWN真的很有意思。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)