newstar2023的题,在buu上还可以做,算是我正儿八经做的第一道pwn,太曲折了啊啊啊。
存在pop_rdi_ret的gadget可以利用
在x86_64架构中,函数调用的参数通过寄存器来传递,而不是通过栈来传递。前六个整数或者指针参数是通过
rdi,rsi,rdx,rcx,r8,r9这六个寄存器来传递的。如果有更多参数,剩下的参数通过栈传递。
若要使用ROP(Return Oriented Programming)来调用一个函数并且操控参数时,就要找到可以控制这些寄存器的gadget。比如
1 | 1. pop rdi;ret |
获取pop_rdi_ret的地址
1 | ROPgadget --binary /... --only "pop|ret" |

然后看一下buf的偏移量
找到偏移为0x20
buf是全局变量,被放在bss段上
在 C 语言中,一个变量是在 BSS 段还是在栈上,主要取决于它的声明位置和存储类别:
- 全局变量和静态变量:这些变量通常存储在 BSS 段。BSS 段用于存储程序中未初始化的全局变量和静态变量。
- 局部变量和函数参数:这些变量通常存储在栈上。当一个函数被调用时,它的局部变量和参数会被压入栈中。
可以使用objdump -h program//readelf -S program查看bss段的地址(不同环境下bss的地址可能不同)
使用peda的vmmap可以查看bss段有无执行权限
整理一下思路,我们可以先把buf塞满(0x20),然后塞rbp(0x8,因为在bss段所以可以直接塞bss的起始位置)。
1 | p.sendafter( |
然后因为64位程序的函数的第一个参数在rdi中,因此把栈顶的值弹出到rdi。在x86_64中,调用函数时,函数的第一个参数是通过rdi寄存器传递的。因此使用pop_rdi_ret这个gadget,把它的地址放在原本的return address处,那么程序就会跳转到pop_rdi_ret的地址处执行它。pop_rdi将此时的栈顶数据(puts函数的地址)弹给rdi,然后ret到plt表的puts处调用puts,那么puts就会找到rdi,打印rdi中puts的地址。puts执行之后,将ret回main_addr。
这样就完成了puts函数地址的泄露,可以用于泄露远程libc的版本。
这里我还另外打了read的地址。查libc版本的时候会准确一点。只有一个地址有时会有很多个版本的libc被检索到。https://libc.rip/
只搜索三个字节(12位)的原因:
即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变

这样就得到了libc的版本,可以看到有system和/bin/sh的偏移。所以我们计算libc_base,加上偏移得到需要的函数的地址。
1 | libc_base = puts_addr - 0x80970 |
这是手动计算的方法。还有其他两种办法(官方提供的)写在完整代码中了。
现在的目标就是执行system('/bin/sh')了。与上面类似,仍然利用栈溢出构造ROP链。
1 | bss-->pop_rdi_ret-->binsh_addr-->system_addr--main_addr |
所以可以:
1 | p.sendafter( |
然而这样执行程序崩溃了。为什么呢?
因为在ubuntu18版本以上调用system函数需要栈对齐。
64位的system命令有个movaps指令
Movaps:
movaps XMM,XMM/m128 movaps XMM/128,XMM
把源存储器内容值送入目的寄存器,当有m128时,必须对齐内存16字节,也就是内存地址低4位为0,保证rsp&0xf==0
(调用printf时也需要对齐)
我们加一个ret来栈对齐:
1 | p.sendafter( |
大功告成!
1 | from pwn import * |