ROP技术实战
ROP实战
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)
ROP是一种攻击技术,其中攻击者使用堆栈的控制来在现有程序代码中的子程序中的返回指令之前,立即间接地执行精心挑选的指令或机器指令组。
ret2win
背景
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)
ROP是一种攻击技术,其中攻击者使用堆栈的控制来在现有程序代码中的子程序中的返回指令之前,立即间接地执行精心挑选的指令或机器指令组。
因为所有执行的指令来自原始程序内的可执行存储器区域,所以这避免了直接代码注入的麻烦,并绕过了用来阻止来自用户控制的存储器的指令的执行的大多数安全措施。
因此,ROP技术是可以用来绕过现有的程序内部内存的保护机制的。
ROP要完成的任务包括要完成的任务包括:在内存中确定某段指令的地址,并用其覆盖返回地址。有时目标函数在内存内无法找到,有时目标操作并没有特定的函数可以完美适配,此时就需要在内存中寻找多个指令片段,拼凑出一系列操作来达成目的。假如要执行某段指令(我们将其称为“gadget”,意为小工具),溢出数据应该以下面的方式构造:
payload : padding + address of gadget
上图是包括单个gadget的溢出
如果想连续执行若干段指令,就需要每个 gadget 执行完毕可以将控制权交给下一个 gadget。所以 gadget 的最后一步应该是 RET 指令,这样程序的控制权(eip)才能得到切换,所以这种技术被称为返回导向编程( Return OrientedProgramming )。要执行多个 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。
我们假设现在内存中可以找到如下几条指令:
1 | pop eax; ret; # pop stack top into eax |
对于所有包含 pop 指令的 gadget,在其地址之后都要添加 pop 的传输数据,同时在所有 gadget 最后包含一段 shellcode,最终溢出数据结构应该变为如下格式。
payload : padding + address of gadget 1 +param for gadget 1 + address of gadget 2 + param for gadget 2 + …… + addressof gadget n + shellcode
此处为了简单,先假定输入溢出数据不受“\x00”字符的影响,所以 payload 可以直接包含 “\x7d\x00\x00\x00”(传给 eax 的参数125)。如果希望实现更为真实的操作,可以用多个 gadget 通过运算得到上述参数。比如可以通过下面三条 gadget 来给 eax 传递参数。
1 | pop eax; ret; # pop stack top 0x1111118e into eax |
解决完上述问题,我们就可以拼接出溢出数据,输入至程序来为程序调用栈开启可执行权限并执行 shellcode。
出于简单化考虑,我们假设了所有需要的 gadget 的存在。在实际搜索及拼接 gadget 时,并不会像上面一样顺利,有两个方面需要注意。
第一,很多时候并不能一次凑齐全部的理想指令片段,这时就要通过数据地址的偏移、寄存器之间的数据传输等方法来“曲线救国”。举个例子,假设找不到下面这条 gadget
1 | pop ebx; ret; |
但假如可以找到下面的gadget
1 | mov ebx, eax; ret; |
我们就可以将它和
1 | pop eax; ret; |
组合起来实现将数据传输给ebx 的功能。上面提到的用多个gadget 避免输入“\x00”也是一个实例应用。
第二,要小心 gadget 是否会破坏前面各个 gadget 已经实现的部分,比如可能修改某个已经写入数值的寄存器。另外,要特别小心gadget 对 ebp 和 esp 的操作,因为它们的变化会改变返回地址的位置,进而使后续的 gadget 无法执行。
ret2win
运行程序测试一下
radare2是一个开源的逆向工程和二进制分析框架,包括反汇编、分析数据、打补丁、比较数据、搜索、替换、虚拟化等等,同时具备超强的脚本加载能力,它可以运行在几乎所有主流的平台(GNU/Linux, .Windows *BSD, iOS, OSX, Solaris…)并且支持很多的cpu架构以及文件格式。 radare2工程是由一系列的组件构成,这些组件可以在 radare2 界面或者单独被使用–比如我们将要在接下来实验中使用到的rahash2, rabin2, ragg2三个组件,所有这些组件赋予了 radare2 强大的静态以及动态分析、十六进制编辑以及溢出漏洞挖掘的能力。
使用r2进行分析,输入aaaa进行分析,然后afl列出所有函数
在上图中我们注意到几个关键的函数,包括main,pwnme,ret2win,我们使用pdf分别反汇编
我们注意到在ret2win函数中会打印flag.txt,也就是我们需要实现的目的
从上图中可以看到我们需要跳转的内存地址,以便执行上面打印flag的代码,地址为0x00400811
接下来我们需要知道覆盖指令指针所需的偏移量,在64位中需要关注RIP,可以使用gdb调试得到偏移。先使用q退出
随机创建长度为200的字符串,pattern_create 200
输入r并填入字符,发现程序终止,报SIGSEGV
接下来用pattern_search寻找偏移量
在64位程序中,我们先看RIP,发现它不包含我们前面随机生成的序列。在64位环境下,指针无法到达高地址,即不能超过0x00007fffffffffff,所以不能直接利用查看$eip的方法。但因为ret
指令,相当于pop rsp
,所以只要看一下rsp
的值,就知道跳转的地址,从而知道溢出点。为了访问这些地址值,可以从 RSP 寄存器中获取它们。 可以看到,段错误时RSP的值为“AA0AAFAAb”
使用pattern offset查找偏移
现在我们已经知道了覆盖RIP所需的padding(40),以及要跳转的地址(0x00400811)
所需exp的关键就是”\x90”*40 +”\x11\x08\x40\x00\x00\x00\x00\x00\x00”
使用python简单地验证下
1 | python -c 'print "\x90"*40 +"\x11\x08\x40\x00\x00\x00\x00\x00\x00"' | ./ret2win |
或者写一个python脚本
1 | from pwn import * |
split
先使用file、checksec等命令看一下文件的基本信息。或rabin2 -I
可以看到nx enabled,即开启了NX,也就是栈不可执行
先载入r2分析
r2 -AAA ./split
使用afl列出所有函数
看到了三个可能是比较重要的函数
main:开始分析的地方
pwnme,usefulfunction:提示的这么明显了
先反汇编main
pdf @ main
在上图中注意到其调用了pwnme
所以我们顺着分析pwnme
同样反汇编
pdf @ sym.pwnme
从上图可以看到有一个32字节的缓冲区,可以通过fgets接收96字节的输入从而溢出,也是和上一题ret2win一样溢出rip吗?
我们先使用gdb 分析,往input中写入随机100字节序列
将input作为输入运行
然后pattern_search
可以看到溢出rsp需要40个字节,我们可以尝试通过调用其他函数吗,比如usefulfunction?
我们回到r2反汇编usefulfunction
r2 -AAA ./split
我们可以看到它调用将执行/bin/ls的system()函数。
usefulFunction函数的地址是0x00400807,所以我们需要40个字节的随机数据和这个地址。
简单的使用python生成exp写入input
1 | python2 -c 'from pwn import *;print("A" * 40 +p64(0x00400807))' > input |
在gdb中运行测试
可以看到成功执行了/bin/ls
不过我们的目标是打印flag,而不是ls,所以继续研究下去
回到r2中使用izz列出字符串
可以打印flag的字符串
这个字符串的地址是0x0001060
现在我们尝试溢出栈,直接执行到system()
不过我们要找到一个办法,直接将这个地址传入RDI寄存器(x86-64传参时依次通过rdi,rsi,,,传参,rdi是第一个)
这时候我们就需要ROPgadget了,简单地说,它们就是写以ret指令结尾的指令序列。指定–only来筛选
找到了很多gadget,那么哪个符合要求呢
我们前面提到必须将值传入RDI,所以要找到pop rdi
所以符合要求的是地址是0x400883
我们传递这个gadget地址(0x400883)后,它会把栈中下一个值传到RDI寄存器中,所以下一个地址应该是能够打印flag的字符串的地址(0x601060),最后是system()函数的地址(0x400810)
使用python简单地将exp输出到input
1 | python2 -c 'from pwn import *;print("A" * 40+p64(0x0400883) + p64(0x00601060)+p64(0x00400810))' > input |
在gdb中查看,发现确实输出了flag。
同样也可以通过pwntools快速写一个exp
1 | from pwn import * |
callme
首先看一下二进制文件的信息 rabin -I callme
可以看到nx为true,同样设置了栈不可执行
接下来在r2中加载分析
r2 -AAA callme
afl列出函数
在上图中看到了此前出现过的main,pwnem,usefulFunction,不过这里比较有意思的是还出现了callme_one,callme_two,callme_three
我们看看题目的描述
我们知道需要通过对应的顺序传入对应的参数才能得到flag
即:
callme_one(1,2,3),callme_two(1,2,3),callme_three(1,2,3)
每个函数都有三个参数
在进一步分析他们之前,我们先来看看main
在上图中可以看到还是调用了pwnme。
我们跟着分析pwnme,看看buffer的大小是否还是一样
pdf @ sym.pwnme
可以看到缓冲区大小还是32字节,fgets函数容易造成缓冲区溢出
再看看usefulFunction中有什么
pdf @ sym.usefulFunction
从上图中可以看到是按照给出的参数、顺序来调用callme_1,2,3三个函数的
所以我们在写的exp时的依据就是这个
需要注意的是,传参时顺序是相反的
我们可以在这个网站(https://godbolt.org/)自己写一段简单的函数并且在main中调用,对照汇编分析
为了将值放入用于传递参数的寄存器中,我们还要用到rop gadget,用于将值从栈pop到这些寄存器中
ROPgadget –binary callme
在0x401ab0,这个gadget可以将值从栈上pop到对应的三个寄存器上
这部分的exp比较长,我们直接用pwntools写,关键是四个地址,一个是rop gadget,已经知道了,另外三个是callme_1,2,3的地址,分别如下
照用以前的框架。使用pwntools进行编写。
1 | from pwn import * |
运行python文件,取得flag。
Write4
先通过rabin2 看一下基础的文件信息
rabin2 -I write4
被设置了nx。也可以用 r2 -AAA ./write4后用i~nx来筛选查看。
在前面的实验中当我们需要打印flag时,用的是文件自身的字符串,我们看看这题里有没有,直接使用strings配合grep过滤
strings write4 | grep ‘cat flag.txt’
可以看到,这种字符串是不存在的
接下来先看看涉及的函数 r2 -AAA ./write4
afl
看看usefulFunction里会不会有我们需要的信息,反汇编它
pdf @ sym.usefulFunction
在上图中我们看到,我们调用了system(),不过传给system的是/bin/ls,也就是说会执行ls命令
不过我们想执行的是cat flag.txt的命令,因为二进制文件中不存在这种字符串,所以我们需要手动进行。
首先需要考虑的是,把cat flag.txt写到哪个地址
readelf -a write4
我们关注输出的sectionheaders部分。
可以看到打印出一系列的section,我们需要在其中找到一个合适的,在其中我们可以写入值。
一般我们都会选择写到data,上图中找到了一个data,地址是0x601050,我们使用readelf看看在这个section里有没有什么数据
readelf -x .data write4
可以看到这个地址是空的,所以我们写入这里是ok的
接下来我们还是需要ropgadget找到特定的gadget让我们能够将字符串放入这儿
ROPgadget –binary write4,结合–only或grep进行筛选。
这里打印出了很多gadget,那么我们需要怎样的呢
首先这个gadget要能够将值写入内存地址,在汇编中一般是通过mov体现,比如MOV [r0],r1这样子,这条汇编的意思是将值从寄存器R1移动到寄存器R0所保存的内存地址处。
下图红色选中的就符合要求
地址是0x400820
现在我们可以将值写入内存了,但是我们还需要pop,才能将值写入寄存器中
地址为0x400890
我们还需要返回并获取system()的地址,并且为了将字符串的地址作为调用system()时的参数,还需要pop rdi
地址是0x400893
现在关键的地址都有了,可以编写我们的exp了
关键点包括:
使用pop gadget将字符串的地址和字符串放在寄存器中
使用mov gadget将字符串放入给出的内存地址中
使用pop rdi gadget将字符串的地址放入寄存器
调用system(),它使用已经保存了字符串地址的rdi寄存器作为参数寄存器
完整代码:
1 | from pwn import * |
python 4.py | ./write4,运行后如图,拿到了flag
badchars
先使用rabin2 -I看一下基础的信息
rabin2 -I badchars
通过r2加载调试分析,afl查看函数
r2 -AAA ./badchars
afl
可以看到在usefulFunction后还有两个函数,分别是nstrlen,checkBadchars
结合题目的提示
这函数应该是用于检查exp中是否有坏字符的
直接运行badchars,就可以看到坏字符了
./badchars
这些就是我们这次在开发exp时需要避免的
还是和以前一样,看看关键函数的反汇编
pdf @ sym.pwnme
可以看到在pwnme函数中已经给出了坏字符,同时还调用了新的两个函数以及fgets()
再看看usefulFunction
pdf @ sym.usefulFunction
和前面的关卡一样,也是调用了system,执行ls命令
接下来我们通过gdb分析得到覆盖rsp寄存器的偏移
pattern_create 512 input
pattern_search
可以看到输入40字节后将会覆盖rsp
回到坏字符的话题来,我们在写exp时用的是十六进制,所以先将这些坏字符转为16进制
另外,空格为
因此,坏字符的16进制分别是
0x62 0x69 0x63 0x2f 0x20 0x66 0x6e 0x73
在使用ROPgadget时通过–badbytes即可过滤掉包含坏字符的项
1 | ROPgadget --binary badchars --badbytes "62|69|63|2f|20|66|6e|73" |
接下来的任务和上一关就一样了,显示找到mov…ret的,然后找对应的pop
地址为0x400b34,0x400b3b
不过因为坏字符的原因,我们无法直接写入cat flag.txt
这时候常用的解决办法是异或
先找到xor的gadget
通过这个gadget我们可以将r14寄存器的值与内存中的值进行异或
所以我们的思路就来了:
我们强行凑对,我们的字符串不直接写cat flag.txt,而是用其他字符代替,这些字符与另外的特定字符异或后会得到cat flag.txt,这样就绕过了坏字符不允许我们直接传入cat flag.txt 的限制
以首字母c为例,哪些字符异或后可以得到c呢?
我们写一个简单的python脚本跑一下就知道了
完整代码:
1 | import string |
因为c是坏字符,这样的话我们就可以用a代替c然后与2异或,从而得到c
这样就解决了坏字符的限制
现在可以写exp了,关键点包括:
1)用aat!alag.txt代替cat flag.txt
2)使用pop gadgets来pop字符串中受限制字符的地址和对应的字符异或,从而得到所需的字符
3)为所有被替换了的字符做相应的操作
4)使用pop,将字符串的地址写入rdi寄存器
5)调用system()最后得到flag
完整代码:
1 | from pwn import * |
pivot
Got表和PLT表
操作系统通常使用动态链接的方法来提高程序运行的效率。在动态链接的情况下,程序加载的时候并不会把链接库中所有函数都一起加载进来,而是程序执行的时候按需加载,如果有函数并没有被调用,那么它就不会在程序生命中被加载进来。这样的设计就能提高程序运行的流畅度,也减少了内存空间。而且现代操作系统不允许修改代码段,只能修改数据段,那么GOT表与PLT表就应运而生。
当函数第一次被用到时才进行绑定(符号査找、重定位等),如果没有用到则不进行绑定。
为了提到cpude效率,在程序加载时并不会解析所有函数,而是在某个函数被调用时通过plt和got来对函数解析,然后将获得的函数地址放在got中,下一次调用就会直接使用got中的函数地址来对函数进行调用。
pivot
首先看一下基本信息
rabin2 -I pivot
r2 -AAA ./pivot;afl
先看看pwnme
pdf @ sym.pwnme
在上图中可以看到我们的exp应该需要两个chain
同时告诉我们从libpivot.so调用了ret2win()
再来看看uselessFunction
pdf @ sym.uselessFunction
可以看到调用了foothold_function,但是其自身没有被调用
接下来看看libpivot.so
r2 -AAA libpivot.so;afl
pdf @sym.ret2win
可以看到ret2win会打印flag
从题目的说明中
我们知道栈空间被限制了,但是我们具体可以放多少空间呢?
使用gdb进行分析
gdb ./pivot 输入r运行,然后第一次输入a,第二次输入一串A
看一下rsp的情况
可以看到一共是3个qword,即3个八字节
我们的任务就是通过这些空间,以某种方式把我们的空间pivot到一个更大的空间
我们注意到,在运行的时候,程序会打印出一个缓冲区的地址,这就是第一个fgets使用的。我们可以改变指向那个缓冲区的rsp寄存器的值,这个值将是我们ROPchain的第二段
我们先ropgadget看看可用的gadget
ROPgadget –binary pivot
我们可以使用pop rax,ret,然后将缓冲区的地址放在第二个位置上,最后xchg rax,rsp,交换值
这是第一段rop chain
接下来我们要解决的是由于ASLR机制,我们该如何得到ret2win函数的地址
由于plt表与got表的特性,函数第一次调用时plt表指向的got表中存储的执行在plt表中查找函数真实地址的函数地址,查找到函数真实地址后,存储到got表的原来表项中替换掉查找函数的指向地址。以题目为例, foothold_function函数,先调用利用利用plt表中的地址调用一次后,在plot表中会存储其真实地址,利用foothold_function函数与ret2win函数在libpivot32.so的便宜差,通过foothold_function真实地址,计算出ret2win函数的真实地址。
我们可以在pivot32的二进制找到foothold_function的plt和got表项,还可以在libpivot32.so找到ret2win这个函数。
因此解决办法是计算相对于foothold_function的偏移,然后在第二段中加上计算出来的值就可以了
回到r2分析libpivot.so分析时得到的地址
计算偏移为0x14e
然后我们要知道foothold_function在plt,got中的偏移
r2 -AAA ./pivot
ir
地址是0x602048
这样写第二段ropchain的准备工作也完成了
关键部分在于:
首先调用foothold_function来填充.got.plt
pop foothold_function的got到rax寄存器
向rax中添加偏移得到ret2win的
最后进行调用即可
代码:
1 | from pwn import * |
fluff
先看一下基本信息
rabin2 -I fluff
然后载入r2分析
r2 -AAA ./fluff;afl
分别看看pwnme和usefulFunction
pdf @ sym.pwnme
pdf @ sym.usefulFunction
重复gdb调试过程,可以发现,esp偏移还是40。
然后使用ropgadget找到有用的gadget
ROPgadget –binary fluff
首先我们要找到一个gadget用于将字符串写入内存。mov适合的似乎只有下面这一条mov适合的似乎只有下面这一条
如果用了这一条,那么下一个要解决的问题就是怎么将值写入r10、r11寄存器呢
似乎没有可直接写的办法,这时候我们常用的解决办法就是组合多个gadget以将值写入r10为例,我们看看该如何操作
那么这里就需要注意了,按照前面的默认命令,其实ropgadget的搜索深度是10。既然我们需要组合多个gadget,既然越多越多,所以我们可以加上–depth 20,将深度设为20
ROPgadget –binary fluff –depth 20
首先清空r11,有两个办法,要么置零,要么与自身异或
我们看看gadget里有没有符合的
接下来把地址pop到r12里
对应的gadget为
前面r11已经是0了,我们将r12与r11异或,这样其实就相当于间接地使用了mov,将值写入了r11
然后使用xchg交换r11和r10寄存器的值,这样就相当于将地址写到了r10寄存器中
做完这部分工作之后,我们只需pop rdi,ret,字符串的地址作为system()参数传入,再调用system()就可以了
代码:
1 | from pwn import * |
python 6.py | ./fluff
Ret2csu
没有合适的rop gadgets,该如何在没有pop rdx的情况下写入rdx呢
先看一下基本信息
rabin2 -I ret2csu
载入r2
r2 -AAA ret2csu; afl
看pwnme的反汇编
pdf @ sym.pwnme
可以看到要求rdx必须是指定的字符串
ret2win
我们先看看ropgadget
ROPgadget –binary ret2csu
可以看到和rdx相关的只有
ROPgadget –binary ret2csu | grep rdx
没有pop rdx,或mov rdx
所以理论上我们无法绕过关卡的限制,无法设置该寄存器。
此时的解决方案是returnto csu,这是blackhat2018的议题,通过一个通用的gadget来制作rop
在afl命令的输出中我们看到有一个函数,名为__libc_csu_init。x64 下的 __libc_csu_init 这个函数是用来对 libc 进行初始化操作的,而一般的程序用 libc 函数,所以这个函数一定会存在。
(不同版本的这个函数有一定的区别)
简单来说就是利用libc_csu_init中的两段代码片段来实现3个参数的传递(间接性的传递参数)
我们反汇编看看
pdf @ sym.__libc_csu_init
在其中我们找到了两个gadget
第一个:
第二个:
rdi是第一个参数,rsi是第二个参数,rdx是第三个参数
结合这两个gadget我们知道,rdi来自r13,rsi来自r14,rdx来自r15
前面提到我们要写0xdeadcafebabebeef到rdx,而从0x00400880可以看到写入r15就可以了。通过mov rdx,r15即可实现目的。
但是我们注意到第一个问题是第二个gadget的最后一条不是ret,而是call
call qword ptr [r12+rbx*8],由前可知,r12,rbx都是可控的,所以这个地址是可控的,不过为了控制目的地我们需要rbx和r12,这具体的值是什么呢?
IDA注意到
第二个gadget后面是上图的三条指令
在cmp之前,rbx+1了,所以简单起见,我们设置rbx为0,rbp为1,这样cmp得到的结果就是相等
后面紧接着就是add rsp,8
我们知道rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变rsp 的值即移动堆栈指针的位置来实现的。
这里的指令相当于增加了栈空间,我们可以随意填充相应大小即可。
在上面我们设置了rbx为0,所以call的地址就是r12指定了,但是直接把ret2win的地址放入r12会报SIGSEGV。而为了有效地使用movrdx,r15,我们必须确保调用QWORD PTR [r12 + rbx * 8]不是SIGSEGV,cmp rbx,rbp相等且最重要的是RDX的值不会改变。
gdb ret2csu
因为__init使用0x400560地址,我们的指针就是0x600e30 + 8
这些操作完成后,我们就可以正常地在栈上放入ret2win的地址
总结下我们做了哪些事情:
首先调用第一个gadget,地址是0x40089a
将需要的值放在栈上
r12寄存器上是指向__init地址的指针
r15寄存器是0xdeadcafebabebeef
rbx寄存器是0x0
rbp寄存器是0x1
第二个gadget地址是0x400880
因为有add rsp,8所以我们需要进行一些填充
将ret2win的值放在栈上
代码:
1 | from pwn import * |