ROP
ROP即返回导向的编程
主要是用在栈溢出
在栈堆不可执行开启后,栈溢出不能简单的向栈中写入shellcode利用jmp rsp运行,因此开发出的一种栈溢出利用手段,主要的意思就是修改ret的位置,运行到一些程序内部的代码片段(gadget)的位置,然后通过这些去控制程序的栈空间,来控制:寄存器,参数传递,函数调用,
由于gadget的使用要是最后ret结尾,由此成为面向ret的编程。
这一篇是结合经典文档:《蒸米的一步步学ROP》自己的笔记
排布首先是栈溢出的简述,然后就是按照攻击的思路去划分出的知识框架。
文章目录
栈溢出
首先还是先介绍有名的栈溢出漏洞,
首先是程序接受的输入数据,一般是保存到栈中,而程序运行时调控变量参量和函数调用的主要工具就是栈,(eax一直用于返回值传递,在64位中寄存器会参数参与参数传递),
栈中的栈帧结构保证函数的调用,
栈帧:
栈内的一块区域,是函数调用时形成的,作用就是保存和该函数有关的参数变量返回地址等,且一个函数能够控制的栈内空间也只有自己的栈帧, 这样也就实现了函数之间的隔离
也辅助程序的流程控制,
程序运行流程
首先程序运行时是永远在运行rip/eip寄存器指向的指令,而我们的程序运行的底层汇编语言永远不会直接改变这个寄存器,而是通过jmp,call,ret,各种条件的jmp,来间接的影响rip/eip寄存器,而影响程序的运行,
jmp和条件的jmp类:
都可以其后直接跟一个地址,直接跳转到该位置运行(其实相当于直接给rip/eip赋值了)
call:
函数调用指令,调用其实相当与jmp,但还需要顾及调用函数需要返回,这里就用到了栈保存这个返回地址,
相当与push rip+xx; jmp xxx
,这里就是将原本该call下一个语句的rip保存到栈,然后跳转到目标位置,
ret:
函数执行完毕后的返回指令,从栈中取出call指令保存的返回地址,然后跳转过去执行,
相当与pop jmp
,或者就pop rip/eip
漏洞
而将用户输入的信息传入栈中是一个比较危险的事情,
因为我们的返回地址同样保存在栈中,当程序读入的值超过了该函数的栈帧空间的大小,就有可能导致输入的数值覆盖掉保存的返回地址,也就是可以构造一个输入,随意的修改返回地址,修改ret指令执行后rip/eip的值,从而控制程序流程,
利用
栈溢出的利用主要就是修改ret控制程序流程,从而让我们的程序执行我们想让其执行的代码。
这就牵扯到一个问题 q1: 将ret改成啥?
将ret修改调用函数时又会出现另一个问题 q2:修改了以后我们调用函数时需要传入的参数怎么改?
最后假如我们的程序和导入的库中不能够得到我们想要的函数呢,q3:如何写一个shellcode并使其执行。
修改ret
后门函数
一般很简单的pwn会出现这种类型,就是一个比较典型的后门函数,直接计算距离然后就可以修改ret运行到对应的函数直接getshell或者catflag的:
payload = 'a' * xxx + p64(ret_addr)/p32(ret_addr)
ELF中查找函数
我们首先说到pwntools的ELF函数,导入一个elf文件,并允许从其中查找相应的地址。
常用来导入我们的程序和该程序用到的libc.so文件
这个就需要一定的理解了,想要理解需要一些关于动态链接和重定向的知识。
elf = ELF('levelxx')
libc = ELF('libc.so'