分享一次 C++ PWN 出题经历——深入研究异常处理机制
字数 1593 2025-08-20 18:17:07
C++异常处理机制与PWN出题实践深度解析
1. C++异常处理机制基础
1.1 SEH (Structured Exception Handling)
SEH是Windows操作系统提供的结构化异常处理机制,主要由以下组件构成:
- EXCEPTION_REGISTRATION_RECORD:异常处理链节点结构
- EXCEPTION_DISPOSITION:异常处理函数返回值类型
- _EXCEPTION_POINTERS:包含异常信息的结构体
关键数据结构:
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD* Next;
PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;
typedef enum _EXCEPTION_DISPOSITION {
ExceptionContinueExecution,
ExceptionContinueSearch,
ExceptionNestedException,
ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord;
} EXCEPTION_POINTERS;
1.2 C++异常处理实现
C++异常处理在Windows上通过__CxxFrameHandler3实现,主要流程:
- 异常发生时,系统遍历SEH链
- 调用每个处理器的Handler函数
__CxxFrameHandler3检查是否能处理当前异常类型- 若能处理则执行栈展开(Stack Unwind)和catch块代码
2. 异常处理机制逆向分析
2.1 关键函数调用链
异常处理的核心调用链:
_KiUserExceptionDispatcher
→ RtlDispatchException
→ RtlpExecuteHandlerForException
→ __CxxFrameHandler3
→ CatchIt (找到匹配的catch块)
→ __CxxCallCatchBlock
2.2 栈展开过程
栈展开(Stack Unwind)的关键步骤:
- 调用当前栈帧中局部对象的析构函数
- 调用
_UnwindNestedFrames进行嵌套展开 - 更新SEH链和线程环境块(TEB)中的信息
逆向分析要点:
- 查找
__unwindfunclet$开头的函数,这些是展开时调用的析构函数 scopetable数组存储了每个try块的展开信息FuncInfo结构体包含异常处理的关键信息
3. C++异常处理漏洞利用
3.1 常见攻击面
- SEH链覆盖:通过缓冲区溢出覆盖SEH链中的Handler指针
- 虚函数表篡改:利用异常处理期间的对象析构过程
- 异常处理信息篡改:修改FuncInfo或scopetable数据
3.2 利用技术细节
SEH链覆盖利用步骤:
- 通过溢出覆盖SEH链节点
- 将Handler指针指向攻击者控制的shellcode或ROP链
- 触发异常使控制流跳转到篡改的Handler
虚函数表攻击步骤:
- 溢出覆盖对象的虚函数表指针
- 触发异常导致对象析构
- 析构时通过虚函数表调用被篡改的函数指针
4. PWN题目设计实践
4.1 题目设计思路
基于C++异常处理机制的PWN题目典型设计:
- 实现一个存在缓冲区溢出漏洞的C++类
- 在类中使用异常处理机制(try-catch)
- 精心构造内存布局使溢出可覆盖关键异常处理结构
4.2 示例题目分析
class Vulnerable {
public:
Vulnerable(int size) {
buffer = new char[size];
bufferSize = size;
}
~Vulnerable() {
delete[] buffer;
}
void SetData(const char* data, unsigned int len) {
if (len > bufferSize) {
throw std::runtime_error("Buffer overflow");
}
memcpy(buffer, data, len); // 漏洞点:无边界检查的拷贝
}
private:
char* buffer;
int bufferSize;
};
void Test() {
try {
Vulnerable v(32);
char data[64];
memset(data, 'A', sizeof(data));
v.SetData(data, sizeof(data)); // 触发溢出
} catch (std::exception& e) {
// 异常处理
}
}
4.3 漏洞利用链构造
- 溢出覆盖虚函数表指针或SEH Handler
- 通过异常触发控制流转移
- 利用ROP或shellcode实现权限提升
5. 防护与检测技术
5.1 现代防护机制
- SafeSEH:校验SEH Handler是否在合法模块中
- SEHOP:验证SEH链完整性
- CFG (Control Flow Guard):保护间接调用
- 堆栈Cookie:防止栈缓冲区溢出
5.2 绕过技术
- ROP链构造:使用合法模块中的gadget
- 堆喷射:在可预测地址布置shellcode
- 信息泄露:绕过ASLR保护
6. 调试与分析技巧
6.1 关键调试命令
Windbg常用命令:
!exchain # 查看SEH链
.frame # 切换栈帧
!heap # 查看堆信息
!vprot # 查看内存页属性
6.2 异常处理断点
设置关键断点:
bm /a *!__CxxFrameHandler3
bm /a *!_CxxThrowException
7. 进阶研究方向
- C++异常处理与CFG的交互:研究控制流保护下的异常处理
- 跨平台异常处理差异:对比Windows SEH与Linux DWARF实现
- 异常处理与虚拟化保护:分析加壳程序中的异常处理机制
- C++20协程与异常处理:研究新特性对异常机制的影响
附录:关键数据结构与函数原型
FuncInfo结构体
struct FuncInfo {
int magicNumber;
int maxState;
int pUnwindMap; // 展开映射表
int pTryBlockMap; // try块映射表
int dwFrameSize; // 帧大小
// ... 其他成员
};
scopetable条目
struct scopetable_entry {
int enclosingLevel; // 封闭层级
int filterFunc; // 过滤器函数
int handlerFunc; // 处理函数
};
_CxxThrowException函数
void __stdcall _CxxThrowException(
void* pExceptionObject,
_ThrowInfo* pThrowInfo
);
通过深入理解这些机制,安全研究人员可以更好地分析C++程序的异常处理流程,发现潜在漏洞,并设计出高质量的PWN题目。