One-gadgets
One-gadget 是 libc 中存在的一些执行 execve(‘/bin/sh’, NULL, NULL)的片段。当我们知道 libc 的版本,并且通过信息泄露得到 libc 的基址,就可以通过控制 EIP/RIP(覆盖.got.plt 或者函数返回地址等)执行该 gadgets 达到远程代码的执行目的,获取 shell。
下载 one_gadgets
1 | sudo gem install one_gadget |
One_gadgets 的约束:
在二进制漏洞利用(Pwn)中,选择 one_gadget 的核心在于匹配约束条件(Constraints)。
当你运行 one_gadget /lib/x86_64-linux-gnu/libc.so.6 时,它会输出多个候选地址,每个地址下方都列出了执行该 gadget 必须满足的寄存器或内存状态。
以下是选定和调试 one_gadget 的实战步骤:
1. 优先选择“约束最弱”的 Gadget
通常输出中会排列出多个 gadget,有的约束非常复杂(需要多个寄存器为 NULL),有的则相对简单。
- 首选只要求
[rsp+0x30] == NULL或rax == NULL这种容易控制的。 - 避开需要
[[rbp-0x78]] == NULL这种涉及多级指针或远程内存地址的,除非你已经完全控制了相关区域
2. 观察当前的执行上下文
在调试器(如 GDB + GEF/pwndbg)中,当程序运行到你准备跳转到 one_gadget 的那一刻(例如 malloc_hook 或返回地址被覆盖时),停下来检查:
- 寄存器状态: 输入
info registers。看看 rax, rbx, rcx 等是否已经是 NULL。 - 栈状态: 输入 stack 20 或 x/20gx $rsp。看看栈上的特定偏移(如
rsp+0x40)是否已经是 0。 - 匹配: 找一个约束条件与当前状态最接近的 gadget。
3. 动态调整环境以满足约束
如果你心仪的某个 gadget 只需要 rax == NULL,但此时 rax 有值,你可以尝试:
- 寻找前置 Gadget: 在跳转到
one_gadget之前,先跳到一个能清空寄存器的代码片段(如xor rax, rax; ret)。 - 利用栈溢出填充: 如果约束是
[rsp+0x70] == NULL,而你通过栈溢出控制了程序流,可以直接在溢出数据中将对应的偏移位置填为\x00。
4. 常见问题:为什么所有 Gadget 都失败?
如果尝试了所有的 one_gadget 都无法直接拿 shell,通常有几种对策:
- 使用 -f 参数: 运行
one_gadget -f libc.so.6强制搜索更多不常用的 gadget(成功率较低,但值得一试)。 - 使用 -near 参数: 如果你知道某个特定的 libc 地址,可以用
one_gadget --near [address] libc.so.6寻找附近的 gadget。 - 回退到常规
system("/bin/sh"): 如果one_gadget实在难以满足约束,还是建议老老实实通过 ROP 链构造参数并调用system或execve。
总结建议
在 CTF 比赛中,最快的办法是挨个试。写一个 Python 脚本,用 pwntools 循环遍历 one_gadget 输出的所有偏移量,通常前三个中必有一个能用:
1 |
|
给个例题演示一下吧:
oneshot_tjctf_2016
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
直接用 0x45216 的 execve(“/bin/sh”)
1 | from pwn import * |
flag{51ab8ff5-809d-4741-bc28-da695868b60a}
更新: 2026-04-24 20:41:32
原文: https://www.yuque.com/idcm/wnemg9/giguc4w8v844bgek