告别system(“/bin/sh“):深入浅出理解one-gadget在Linux PWN中的妙用与约束条件
告别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 [rsp0x30] ; 约束条件检查 xor esi, esi ; 设置argvNULL mov rdi, 0x7ffff7b95d88 ; /bin/sh地址 call execve ; 系统调用这些代码片段被称为one-gadget其核心特点是无需参数准备内置了/bin/sh字符串地址和参数清零操作单跳转触发只需控制EIP/RIP跳转到指定地址环境依赖需要特定寄存器或内存状态满足条件1.2 约束条件的类型分析使用one_gadget工具查询libc时输出的约束条件主要分为三类约束类型典型示例满足方法寄存器状态[rsp0x30] 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基址约束条件[rsp0x40] NULL适用版本glibc 2.27-2.292.2 约束条件主动满足策略当面对[rsp0x30] NULL这类约束时可通过以下ROP链设计清空栈空间# 清空[rsp0x30]区域的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 # 满足[rsp0x30]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.235[rsp0x30] NULL2.273rcx NULL2.311[rsp0x70] NULL4.2 动态环境下的约束满足在以下特殊场景需要特别注意线程环境fs:[0x30]约束在主线程中恒为非零ASLR影响堆地址随机化可能导致[rsi]约束失败IO缓冲printf等函数会修改栈结构破坏预设条件4.3 调试技巧使用GDB验证约束条件是否满足gdb-peda$ b *0x7ffff7a7b432 # 在one-gadget地址下断点 gdb-peda$ x/gx $rsp0x30 # 检查关键内存值 gdb-peda$ p $rcx # 检查寄存器状态在实际CTF比赛中遇到one-gadget失效时我会优先检查栈空间是否被其他函数调用污染。曾经在一道题目中由于忽略了__libc_start_main的初始化操作导致[rsp0x40]被写入非零值最终通过调整ROP链插入额外的read调用清空该区域才成功利用。