第一类是大量 IsDebuggerPresent 检查。
你看到很多函数都有:
if ( !&IsDebuggerPresent )
exit(0);
但注意这个写法很怪:
!&IsDebuggerPresent-
它检查的是导入函数地址是否为空,不是调用 IsDebuggerPresent() 的返回值。静态看像反调试,实际更多是导入解析/混淆。正常 IAT 有地址时就不会退出。
第二类是异常/SEH 反调试:
SetUnhandledExceptionFilter
UnhandledExceptionFilter
RaiseException
AddVectoredExceptionHandler
这里最有用的是:
AddVectoredExceptionHandler
第三类是 API 动态解析:
.SCY 段里有很多 API 名:
kernel32.dll
GetProcAddress
LoadLibraryW
VirtualProtect
AddVectoredExceptionHandler
RemoveVectoredExceptionHandler
IsDebuggerPresent
IsBadReadPtr
IsBadWritePtr
这说明它有个小 runtime/protector,把关键 API 和流程藏了一层。
实战绕法:
- 不要 patch 掉
AddVectoredExceptionHandler
因为这个 handler 是题目真实算法入口。你如果禁掉它,程序反而走不到正确逻辑。
- 可以 patch 掉真正的
IsDebuggerPresent()返回
下面是SEH反调试的一个例题:
题目名称是:RC4
这个题真实是AES-128,RC4是不会执行的
- 不相信静态调用链
- 搜 .SCY / VEH / **SEH **/ VirtualProtect / RaiseException
- 看是否 patch 了某个 thunk
- 找异常 handler
- 看 handler 是否修改 ContextRecord->Rip
- 从 ContextRecord 还原真实参数
- 还原 handler 里的真实算法
- 逆算法求 flag
核心异常逻辑是这样:
sub_140017620()
它会注册 VEH:
VEH(向量化异常处理,Vectored Exception Handling)是 Windows 提供的一种异常处理机制。你可以通过 AddVectoredExceptionHandler 这个 API 来注册一个 VEH 处理函数,它在程序的异常处理流程中拥有很高的优先级
下图是VEH:

AddVectoredExceptionHandler(1, Handler);
然后把 sub_140011721 前 5 字节 patch 掉:
原来: jmp sub_1400122A0 // 跳到 RC4
改成: C6 04 25 00 00 ... // 故意访问 0 地址,触发异常
所以 main 里看起来调用的是:
sub_140011479
-> sub_14001B2B0
-> sub_140011721
-> sub_1400122A0 // 静态看像 RC4
但实际运行时不会进 RC4,而是:
调用 sub_140011721
↓
触发 0xC0000005 访问异常
↓
VEH Handler 接管
↓
Handler 自己加密输入
↓
修改 ContextRecord->Rip 到返回地址
↓
返回 EXCEPTION_CONTINUE_EXECUTION
handler 是:
Handler -> sub_140018670
它判断:
ExceptionCode == 0xC0000005
ContextRecord->Rip == sub_140011721
如果匹配,就从异常上下文里取原本函数参数:
Context.Rcx = key
Context.Rdx = input_data
Context.R8 = output_buffer
然后它不是调用 RC4,而是做两块 16 字节加密:
sub_140011735(key, block0, out0);
sub_14001133E(); // 修改 key
sub_140011735(key, block1, out1);
sub_14001133E() 实际跳到:
sub_14001BCA0
把 key 从:
Rc4_1s_very_S@f3
改成:
Rc4_n0t_5@f3_N0w(这里可以尝试自己动态调试)
所以真实算法是:
第 1 个 16 字节 block:用 Rc4_1s_very_S@f3 加密
第 2 个 16 字节 block:用 Rc4_n0t_5@f3_N0w 加密
sub_14001A930 是自定义 AES-128:
KeyExpansion
AddRoundKey
SubBytes
ShiftRows
MixColumns
其中 MixColumns 是标准 AES MixColumns 后再异或常量:
[DD, EE, AA, DD]
每列都加这个常量。
比较函数是:
sub_14001AD90
目标密文 32 字节:
target = bytes([
0xB1, 0x82, 0x7B, 0x2E,
0xA1, 0x12, 0xE2, 0x36,
0xEF, 0x2C, 0x38, 0x8D,
0x07, 0xFF, 0x41, 0x1F,
0x22, 0x86, 0x53, 0x1F,
0xA6, 0xF4, 0x2D, 0x11,
0x20, 0x76, 0xDC, 0xE6,
0x11, 0xA2, 0x19, 0x2A,
])
解法就是反过来:
decrypt block0 with key1 = Rc4_1s_very_S@f3
decrypt block1 with key2 = Rc4_n0t_5@f3_N0w
拼接 32 字节
得到:
b84d3ba0cq_3e286f44ab5_cCd591d25
补充:这个题当时是用来练upx手脱壳的,在ida中一直找running的函数直到跳到OEP后p冲定义函数就行
本人刚学逆向不久,有什么错误的地方请指出,感谢!
浙公网安备 33010602011771号