(本文章纯属虚构,如有雷同纯属巧合)
这个SEH记录小工具是学习脱壳时的“副产品”。大多数加密壳都少不了用SEH进行反跟踪、反调试,不过前辈们说过“这些连续的SEH给调试者指明了一条通往正确目标的道路”,所以对一个壳的SEH了解应该是比较必要的。但是在OD上不断重复重复再重复地“shift+F9”毕竟是一件比较烦人的事,所以就写了这个小工具,让其自动记录SEH发生异常的地址以及其对应的Handler地址,这样一下子就能知道第几个SEH后就开始处理输入表、第几个SEH后就会跳到OEP,直达代码核心,还能根据Handler地址正确设置断点,这样“Shift+F9”就保证不会跑飞。好,废话少说,下面跟大家来分享。
一,一个脱壳实例
看雪学院编著的《软件加密技术内幕》第三章第二节有一个Hying前辈写的tElock0.98脱壳机源码,KANXUE编著的《加密与解密(第二版)》第十一章第七节脱壳实例有两个用tElock0.98加了壳的样本,分别是Note_tElock.exe、No-Anti.exe。然后用Hying前辈写的脱壳机去脱这两个壳,Note_tElock.exe脱壳成功,No-Anti.exe脱壳失败。
这个脱壳机会在第16次SEH时候设置断点保护输入表,第20次SEH时候DUMP文件。用SEHstat.exe(附件中)打开Note_tElock.exe及No-Anti.exe,很快就生成两份SEH数据。如下:
----------------------------------------------------------------------------------------------
Note_tElock.exe
ImageBase:00400000h StartAddress:0040DBD6h ProcessId:1256 ThreadId:388
...
---------------------------------main start-----------------------------------
Exception 01 Address:0040DA1Dh(-441) Handler:0040DA0E(-456) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 02 Address:0040DA74h(-354) Handler:0040DA58(-382) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 03 Address:0040C08Ch(-6986) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_BREAKPOINT
Exception 04 Address:0040C090h(-6982) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 05 Address:0040C099h(-6973) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 06 Address:0040C09Eh(-6968) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 07 Address:0040C0A3h(-6963) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 08 Address:0040C0A7h(-6959) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_INT_DIVIDE_BY_ZERO
Exception 09 Address:0040C6A8h(-5422) Handler:0040C68A(-5452) ExcptionCode:EXCEPTION_ILLEGAL_INSTRUCTION
Exception 10 Address:0040CAA1h(-4405) Handler:0040CA90(-4422) ExcptionCode:EXCEPTION_INT_DIVIDE_BY_ZERO
Exception 11 Address:0040CAE4h(-4338) Handler:0040CAC2(-4372) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 12 Address:0040CB27h(-4271) Handler:0040CB03(-4307) ExcptionCode:EXCEPTION_BREAKPOINT
Exception 13 Address:0040CB67h(-4207) Handler:0040CB41(-4245) ExcptionCode:EXCEPTION_INT_DIVIDE_BY_ZERO
Exception 14 Address:0040CBA6h(-4144) Handler:0040CB84(-4178) ExcptionCode:EXCEPTION_ACCESS_VIOLATION
Exception 15 Address:0040CBF0h(-4070) Handler:0040CBC4(-4114) ExcptionCode:EXCEPTION_BREAKPOINT
Exception 16 Address:0040CE0Dh(-3529) Handler:0040CDFE(-3544) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 17 Address:0040CE49h(-3469) Handler:0040CE2D(-3497) ExcptionCode:EXCEPTION_SINGLE_STEP
Load Dll 7D590000h C:\WINDOWS\system32\SHELL32.dll
Load Dll 77F40000h C:\WINDOWS\system32\SHLWAPI.dll
Load Dll 77180000h C:\WINDOWS\WinSxS\...\comctl32.dll
Load Dll 5D170000h C:\WINDOWS\system32\comctl32.dll
Load Dll 76320000h C:\WINDOWS\system32\comdlg32.dll
Exception 18 Address:0040D6F1h(-1253) Handler:0040D6FF(-1239) ExcptionCode:EXCEPTION_ILLEGAL_INSTRUCTION
Exception 19 Address:0040D7E1h(-1013) Handler:0040D7D2(-1028) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 20 Address:0040D817h(-959) Handler:0040D7FB(-987) ExcptionCode:EXCEPTION_SINGLE_STEP
;这里之后程序就运行了
--------------------------------------------------------------------------------------------------
No-Anti.exe
ImageBase:00400000h StartAddress:0040DBD6h ProcessId:1612 ThreadId:1632
...
---------------------------------main start-----------------------------------
Exception 01 Address:0040D9FCh(-474) Handler:0040D9ED(-489) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 02 Address:0040DA37h(-415) Handler:0040DA1B(-443) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 03 Address:0040C08Ch(-6986) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_BREAKPOINT
Exception 04 Address:0040C090h(-6982) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 05 Address:0040C099h(-6973) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 06 Address:0040C09Eh(-6968) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 07 Address:0040C0A3h(-6963) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 08 Address:0040C0A7h(-6959) Handler:0040C0C5(-6929) ExcptionCode:EXCEPTION_INT_DIVIDE_BY_ZERO
Exception 09 Address:0040C6A8h(-5422) Handler:0040C68A(-5452) ExcptionCode:EXCEPTION_ILLEGAL_INSTRUCTION
Exception 10 Address:0040CAA1h(-4405) Handler:0040CA90(-4422) ExcptionCode:EXCEPTION_INT_DIVIDE_BY_ZERO
Exception 11 Address:0040CAE4h(-4338) Handler:0040CAC2(-4372) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 12 Address:0040CDF0h(-3558) Handler:0040CDE1(-3573) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 13 Address:0040CE28h(-3502) Handler:0040CE0C(-3530) ExcptionCode:EXCEPTION_SINGLE_STEP
Load Dll 7D590000h C:\WINDOWS\system32\SHELL32.dll
Load Dll 77F40000h C:\WINDOWS\system32\SHLWAPI.dll
Load Dll 77180000h C:\WINDOWS\WinSxS\...\comctl32.dll
Load Dll 5D170000h C:\WINDOWS\system32\comctl32.dll
Load Dll 76320000h C:\WINDOWS\system32\comdlg32.dll
Exception 14 Address:0040D6F1h(-1253) Handler:0040D6FF(-1239) ExcptionCode:EXCEPTION_ILLEGAL_INSTRUCTION
Exception 15 Address:0040D7C8h(-1038) Handler:0040D7B9(-1053) ExcptionCode:EXCEPTION_SINGLE_STEP
Exception 16 Address:0040D806h(-976) Handler:0040D7EA(-1004) ExcptionCode:EXCEPTION_SINGLE_STEP
;这里之后程序就运行了
(说明:Address表示SEH异常发生的地址,Handler表示SEH处理函数的地址,括号里面的数据表示该地址相对程序入口点的偏移,ExcptionCode就是异常类型)
---------------------------------------------------------------------------------------------------------
由上面两份SEH记录很明显可以看出,Note_tElock.exe的SEH很符合Hying前辈的统计数据,所以很顺利就脱了。但是第二份SEH明显就不符合,它总共才发生16次SEH异常而不是20次,在第13次SEH之后就开始处理输入表了,所以这个脱壳机报“locked.exe可能不是TeLock 0.98的外壳。”。
知道失败原因,我们就可以对源代码进行修改,使它“兼容性”更好一些。由上面数据看来,在本例中根据第几次SEH脱壳可能不是个好主意,我的做法是直接对LoadLibraryA、GetModuleHandleA、VirtualProtectEx下断点(不是INT3哦),当程序第一次执行到LoadLibraryA或GetModuleHandleA时,说明开始处理输入表了,然后在函数返回地址向前80个字节范围内搜索“test esi, esi;je ****;”两条指令(不是硬编码偏移值),将函数返回地址直接修改为je ****中的****,这样当LoadLibraryA或GetModuleHandleA执行完毕直接就跳过后面的加密处理代码了(不用修改程序代码哦),而这时的ESI正是原来的输入表地址,可以直接记录下来。当程序执行到VirtualProtectEx时就可以DUMP了(趁PE头还没有被修改),然后大功告成,两个样本程序均能成功脱壳。(附件中有这个tElock0.98_Unpacker.exe)
二、SEH记录小工具的实现
lea ebx,DBEvent.u
.while TRUE
invoke WaitForDebugEvent,addr DBEvent,INFINITE
mov dwDebugOperation,DBG_CONTINUE
.if DBEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT ;进程结束
...
.elseif DBEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT ;进程创建
assume ebx:ptr CREATE_PROCESS_DEBUG_INFO
mov eax,[ebx].lpStartAddress
mov dwStartAddress,eax
...
invoke _SetBreakPoint,pi.hProcess,dwStartAddress,addr pOldStartAddressCode ;在程序入口点设置INT3断点
.elseif DBEvent.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT ;线程结束
...
.elseif DBEvent.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT ;线程创建
...
.elseif DBEvent.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT ;DLL装载
assume ebx:ptr LOAD_DLL_DEBUG_INFO
invoke ReadProcessMemory,pi.hProcess,[ebx].lpImageName,addr pTempBuffer,4,NULL
mov eax,dword ptr pTempBuffer
invoke ReadProcessMemory,pi.hProcess,eax,addr pDllName,256,NULL
...
.elseif DBEvent.dwDebugEventCode == UNLOAD_DLL_DEBUG_EVENT ;DLL卸载
...
.elseif DBEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT ;进程异常
assume ebx:ptr EXCEPTION_DEBUG_INFO
mov dwDebugOperation,DBG_EXCEPTION_NOT_HANDLED ;对不是预期的异常 通通都不处理
invoke _GetExceptionText,[ebx].pExceptionRecord.ExceptionCode,addr szExceptionText
.if [ebx].pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
.if bFirstBreakPoint == 1 ;第一次系统断点
invoke lstrcat,addr szExceptionText,addr szNtdllBreakPoint
mov dwDebugOperation,DBG_CONTINUE ;继续执行
mov bFirstBreakPoint,0
.endif
mov eax,[ebx].pExceptionRecord.ExceptionAddress
.if eax == dwStartAddress ;在程序入口点中断 记录一下
invoke _CleanBreakPoint,pi.hProcess,dwStartAddress,offset pOldStartAddressCode ;清除INT3断点
invoke _ContinueExecute,pi.hThread,dwStartAddress ;INT3之后需要修改EIP
mov dwDebugOperation,DBG_CONTINUE ;继续执行
invoke _Hide,pi.hProcess,pi.dwThreadId ;隐藏调试器,目前只能防止IsDebuggerPresent检测 :-)
invoke _AddRecord,addr szMain
jmp @MyBreakPoint
.endif
.endif
;除了自己设置的程序入口断点,其他的异常就开始记录
invoke _FindSEHHandler,pi.hProcess,DBEvent.dwThreadId ;查找SEH处理函数地址
mov edi,eax
mov esi,edi
sub esi,dwStartAddress ;计算相对程序入口点偏移
mov eax,[ebx].pExceptionRecord.ExceptionAddress ;异常发生地址
mov edx,eax
sub edx,dwStartAddress ;计算相对程序入口点偏移
invoke wsprintf,addr pTempBuffer,addr szExceptionInfo,nExceptionCounter,eax,edx,edi,esi,addr szExceptionText
invoke _AddRecord,addr pTempBuffer ;把记录写到文件中去
inc nExceptionCounter ;SEH计数器
@MyBreakPoint:
.endif
invoke ContinueDebugEvent,DBEvent.dwProcessId,DBEvent.dwThreadId,dwDebugOperation
.endw
;查找异常处理函数所在地址
_FindSEHHandler proc uses esi edi ebx hProcess,dwThreadID
LOCAL Teb:DWORD,SEHPtrAddress:DWORD
invoke _FindTeb,dwThreadID ;查找该线程对应的Teb
.if eax == FALSE
mov eax,-1
jmp @ExitFindSEHHandler
.endif
mov Teb,eax
invoke ReadProcessMemory,hProcess,Teb,addr SEHPtrAddress,4,NULL ;获取当前SEH handler指针所在地址
invoke ReadProcessMemory,hProcess,SEHPtrAddress,addr pTempBuffer,8,NULL ;获取当前EXCEPTION_REGISTRATION结构
mov eax,dword ptr [pTempBuffer+4] ;得到SEH handler
@ExitFindSEHHandler:
ret
_FindSEHHandler endp
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课