查看原文
其他

ROPEmporium全解

nYale 看雪学院 2021-03-07

本文为看雪论坛精华文章

看雪论坛作者ID:nYale



0x00


本系列文章算是ROPEmporium的wp,做的过程中参考了很多大佬的博客,写的过程中融入了必要的基础知识和其他的思路,算是入门ROP技术的基石吧。


0x01


ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御。(比如内存不可执行和代码签名等)

ROP是一种攻击技术,其中攻击者使用堆栈的控制来在现有程序代码中的子程序中的返回指令之前,立即间接地执行精心挑选的指令或机器指令组。

因为所有执行的指令来自原始程序内的可执行存储器区域,所以这避免了直接代码注入的麻烦,并绕过了用来阻止来自用户控制的存储器的指令的执行的大多数安全措施。

因此,ROP技术是可以用来绕过现有的程序内部内存的保护机制的。

ROP要完成的任务包括要完成的任务包括:在内存中确定某段指令的地址,并用其覆盖返回地址。有时目标函数在内存内无法找到,有时目标操作并没有特定的函数可以完美适配,此时就需要在内存中寻找多个指令片段,拼凑出一系列操作来达成目的。假如要执行某段指令(我们将其称为“gadget”,意为小工具),溢出数据应该以下面的方式构造:

payload : padding + address of gadget



上图是包括单个gadget的溢出。

如果想连续执行若干段指令,就需要每个 gadget 执行完毕可以将控制权交给下一个 gadget。所以 gadget 的最后一步应该是 RET 指令,这样程序的控制权(eip)才能得到切换,所以这种技术被称为返回导向编程( Return Oriented Programming )。要执行多个 gadget,溢出数据应该以下面的方式构造:

payload : padding + address of gadget 1 + address of gadget 2 + ...... + address of gadget n


在这样的构造下,被调用函数返回时会跳转执行 gadget 1,执行完毕时 gadget 1 的 RET 指令会将此时的栈顶数据(也就是 gadget 2 的地址)弹出至 eip,程序继续跳转执行 gadget 2,以此类推。



上图是包含多个gadget的溢出数据。

现在任务可以分解为:针对程序栈溢出所要实现的效果,找到若干段以 ret 作为结束的指令片段,按照上述的构造将它们的地址填充到溢出数据中。所以我们要解决以下几个问题。

首先,栈溢出之后要实现什么效果?

ROP 常见的拼凑效果是实现一次系统调用,Linux系统下对应的汇编指令是 int 0x80。执行这条指令时,被调用函数的编号应存入 eax,调用参数应按顺序存入 ebx,ecx,edx,esi,edi 中。例如,编号125对应函数:

mprotect (void *addr, size_t len, int prot)


可用该函数将栈的属性改为可执行,这样就可以使用 shellcode 了。假如我们想利用系统调用执行这个函数,eax、ebx、ecx、edx 应该分别为“125”、内存栈的分段地址(可以通过调试工具确定)、“0x10000”(需要修改的空间长度,也许需要更长)、“7”(RWX 权限)。

其次,如何寻找对应的指令片段?

有若干开源工具可以实现搜索以 ret 结尾的指令片段,著名的包括 ROPgadget、rp++、ropeme 等,甚至也可以用 grep 等文本匹配工具在汇编指令中搜索 ret 再进一步筛选。

最后,如何传入系统调用的参数?

对于上面提到的 mprotect 函数,我们需要将参数传输至寄存器,所以可以用 pop 指令将栈顶数据弹入寄存器。

如果在内存中能找到直接可用的数据,也可以用 mov 指令来进行传输,不过写入数据再 pop 要比先搜索再 mov 来的简单,对吧?

如果要用 pop 指令来传输调用参数,就需要在溢出数据内包含这些参数,所以上面的溢出数据格式需要一点修改。对于单个 gadget,pop 所传输的数据应该在 gadget 地址之后,如下图所示。



上图是以gadget “pop eax; ret;”为例。

在调用 mprotect() 为栈开启可执行权限之后,我们希望执行一段 shellcode,所以要将 shellcode 也加入溢出数据,并将 shellcode 的开始地址加到 int 0x80 的 gadget之后。我们可以使用 push esp 这个 gadget。



我们假设现在内存中可以找到如下几条指令:

pop eax; ret; # pop stack top into eax
pop ebx; ret; # pop stack top into ebx
pop ecx; ret; # pop stack top into ecx
pop edx; ret; # pop stack top into edx
int 0x80; ret; # system call
push esp; ret; # push address of shellcode


对于所有包含 pop 指令的 gadget,在其地址之后都要添加 pop 的传输数据,同时在所有 gadget 最后包含一段 shellcode,最终溢出数据结构应该变为如下格式。

payload : padding + address of gadget 1 + param for gadget 1 + address of gadget 2 + param for gadget 2 + ...... + address of gadget n + shellcode




此处为了简单,先假定输入溢出数据不受“\x00"字符的影响,所以 payload 可以直接包含 “\x7d\x00\x00\x00”(传给 eax 的参数125)。如果希望实现更为真实的操作,可以用多个 gadget 通过运算得到上述参数。比如可以通过下面三条 gadget 来给 eax 传递参数。

pop eax; ret; # pop stack top 0x1111118e into eax
pop ebx; ret; # pop stack top 0x11111111 into ebx
sub eax, ebx; ret; # eax -= ebx

解决完上述问题,我们就可以拼接出溢出数据,输入至程序来为程序调用栈开启可执行权限并执行 shellcode。

出于简单化考虑,我们假设了所有需要的 gadget 的存在。在实际搜索及拼接 gadget 时,并不会像上面一样顺利,有两个方面需要注意。

第一,很多时候并不能一次凑齐全部的理想指令片段,这时就要通过数据地址的偏移、寄存器之间的数据传输等方法来“曲线救国”。举个例子,假设找不到下面这条 gadget:

pop ebx; ret;


但假如可以找到下面的 gadget:

mov ebx, eax; ret;


我们就可以将它和 pop eax; ret; 组合起来实现将数据传输给 ebx 的功能。上面提到的用多个 gadget 避免输入“\x00”也是一个实例应用。

第二,要小心 gadget 是否会破坏前面各个 gadget 已经实现的部分,比如可能修改某个已经写入数值的寄存器。另外,要特别小心 gadget 对 ebp 和 esp 的操作,因为它们的变化会改变返回地址的位置,进而使后续的 gadget 无法执行。


0x02


第一关是ret2win,简单运行看看:



使用r2进行分析:



进入了r2的shell界面,输入aaaa进行分析:



然后使用afl列出函数:



在上图中我们注意到几个关键的函数,包括main,pwnme,ret2win,我们使用pdf分别反汇编。

main的:



pwnme的:



ret2win的:



我们注意到在ret2win函数中会打印flag.txt,也就是我们需要实现的目的。

从上图中可以看到我们需要跳转的内存地址,以便执行上面打印flag的代码,地址为0x00400811。

接下来我们需要知道覆盖指令指针所需的偏移量,在64位中需要关注RIP,使用gdb得到偏移。

首先载入:



随机创建长度为200的字符串:



输入r开始运行:



然后将前面生成的字符串复制过来:



此时程序已经终止,报SIGSEGV了:



接下来pattern_search来查找偏移量。

在64位程序中,我们先看RIP,发现它不包含我们前面随机生成的序列。在64位中,如果实际上无法跳转到该地址并执行,则不会向RIP中pop值。

因此,如果在将其pop到RIP失败,该值会位于堆栈的顶部,因此需要从RSP获取值。可以看到,段错误时RSP的值为“AA0AAFAAb”。



接下来找到具体多少padding:



现在我们已经知道了覆盖RIP所需的padding(40),以及要跳转的地址(0x00400811)。

所需exp的关键就是"\x90"*40 + "\x11\x08\x40\x00\x00\x00\x00\x00\x00"。

使用python简单地验证下:



可以看到确实打印出了flag。

解释下上图的命令,python –c用于运行python命令,在这个例子中我们只是打印出值“\x90”*40是我们所需的40字节的Nop sled,"\x11\x08\x40\x00\x00\x00\x00\x00"是一个小端格式的ret2win的地址,后面有一些填充,因为它是一个64位的地址所以它是我们的4字节地址,有4个字节的填充。

然后通过管道传给ret2win,也可以用pwntools写一个小脚本,代码在1.py。



运行如图:




0x03 参考


https://medium.com/@int0x33/
https://paper.seebug.org/272/
https://www.rootnetsec.com/
https://bestwing.me/ropemporium-all-writeup.html
https://firmianay.github.io/2017/11/02/rop_emporium.html
https://www.oipapio.com/cn/article-5389490
http://ascii.911cha.com/
https://www.bejson.com/convert/ox2str/
https://larry.ngrep.me/2018/06/14/rop-emporium-write-up/
https://www.voidsecurity.in/2013/07/some-gadget-sequence-for-x8664-rop.html
https://www.blackhat.com/docs/asia-18/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR.pdf
https://www.blackhat.com/docs/asia-18/asia-18-Marco-return-to-csu-a-new-method-to-bypass-the-64-bit-Linux-ASLR-wp.pdf
https://www.jianshu.com/p/a9ad38ad33e5
https://zhuanlan.zhihu.com/p/27339191
https://www.voidsecurity.in/2013/07/some-gadget-sequence-for-x8664-rop.html

注:此为第(一)部分,第(二)到(八)部分可以点击阅读原文直接下载附件的 word 查看。


- End -






看雪ID:nYale

https://bbs.pediy.com/user-794495.htm 


*本文由看雪论坛  nYale  原创,转载请注明来自看雪社区





推荐文章++++

应急服务辅助工具与系统溯源思路

利用auxv控制canary

未知黑客团队钓鱼样本分析

使用Binary Ninja去除ollvm流程平坦混淆

某盗链App逆向


好书推荐







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存