初识ROP和ret2shellcode
如果遇到NX保护,即栈堆不可执行的情况,我们shellcode将不能作为机器码被执行,那么我们就需要新的方法————ROP技术。
何为ROP?ROP的全称为返回导向编程。听名字十分牛的样子。ROP的核心在返回,即ret上。ret等价于pop EIP/RIP,call所调用的函数结束后,ret会把返回地址弹出栈,程序会跳到ret指向的地址。

出自《深入理解计算机系统(CSAPP)》第三章,要注意的一个问题是,国内外的计算机教材对于栈的画法是相反的,国外的教材更倾向于把栈倒过来画,即栈顶sp在下,栈底bp在上,栈向下增长(向下画)。不过并不影响实际的理解。
接下来我们需要知道什么是gadget。gadget在英语里的翻译是小玩意,小装置的意思,在二进制领域,gadget就是一小段程序里已经有的,通常以ret结尾的汇编代码,注意这里的关键词,“一小段”,“已有的”,“以ret结尾的”,重点是后两个词,这就意味着我们可以通过一段又一段的gadget不断ret到程序内的不同地方。就算开了NX不让我们执行shellcode,但是程序里已经有的指令总不能不让我们执行吧,这就是gadget的用处。
所以ROP就是一项通过构造一系列的gadget实现攻击的技术。
再讲一下got表和plt表是什么,这一块牵扯到静态链接和动态链接,以及延迟绑定相关的内容。延迟绑定我还没有做到涉及的题目,所以暂时按下不表。
静态链接就是把程序要用的函数,代码等直接写进了可执行文件里,在编译时加载,缺点就是文件的大小会比较大。
动态链接会在程序启动时从动态链接库里调用函数,比如libc库。
PLT表(Procedure Linkage Table,过程链接表),用于支持动态链接函数调用(例如 printf, puts, system等),相当于调用外部函数时的跳板。
GOT表 (Global Offset Table全局偏移表)是在运行时保存动态库函数或全局变量实际地址的表。plt表经过一段复杂的过程后,会跳转并链接到got表,这里是存储函数的真实地址,动态链接器负责把libc的地址写进got表,got表会跳转到存放函数的真实地址。
接下来我以CTFshow的pwn入门43题讲解一下ROP的使用

这种情况下我们需要想办法自己构造出一个bin/sh出来,我们首先需要知道程序的哪个地方存在可读可写的段。我们gdb程序,在main函数下断点并运行,使用vmmap指令

果然.bss段里有一个buf2的变量,我们可以把bin/sh写进去
1 |
|
来解释一下这段exp:第一步栈溢出,覆盖到EIP处,把EIP指向的返回地址改到gets处,再将gets的返回地址改为system,在调用完gets函数后,会弹出ret指令,ret到system的位置,第一个buf2作为gets的参数,我们的目的是通过gets将bin/sh字符写入buf2,(io.sendline(b”/bin/sh”)就是把bin/sh字符串通过gets函数读入buf里),而第二个buf2作为system的一参被调用,最后成功执行system bin/sh获得shell
我们用gdb调试一下,验证这段exp是否正确。
我们在exp里,在gets函数和system函数处分别下一个断点,运行exp进入gdb调试界面,按c走到第一个断点之前

这里的backtrace窗口简称bt窗口,可以显示当前程序的调用栈,即call stack,每一行都是一个栈帧,可以帮我们看函数调用的顺序
我们可以看的非常清楚,我们的ebp已经被aaaa覆盖,eip被改到了gets函数处,esp栈顶被改到了system处,在栈窗口,system下方是我们覆盖的两个buf2

我们继续按c,执行完gets函数,到system断点前,x/s查看一下buf的内存,可以看到bin/sh已经被成功写到buf2里了

总结
本题属于典型的有system无bin/sh或sh代替,需要我们自己在一段可读可写的内存里写入bin/sh字符串,是非常经典的一道基本ret2libc题目,让我们初步认识了ROP的原理以及是如何构造的,如何找到可读可写的内存,plt表和got表是什么,动态链接和静态链接的过程分别是怎么样的,32位系统是如何通过栈传参的,栈是如何增长的,call和ret指令的本质是怎样的等






