利用 Debug API 编写一个简单的脱壳机
一般压缩壳,都可以脱!
作者: 一块三毛钱
邮件: zhongts@163.com
日期: 2005.2.22
脱壳的一般步骤是:查找入口点,中断在入口点,dump 进程,修复输入表。大家一般借助调试器来完成这几步。下面我就来介绍如何通过编程实现一个简单的脱壳机,自动完成上面的
几个步骤。
1. 查找入口点
查找入口点可以利用现有的工具来完成,如 PEiD、PE-Scan 等。通过对 PEiD 中的 GenOEP 插件的逆向工程我们可以找到如下方法来查找入口点。这种方法的根据就是每个编译器编译
出来的程序在入口点处的代码通常是一样的。比如说 VC6 编译的程序,入口点处的部分代码一般都是下面这个样子:
:00434E55 55 push ebp
:00434E56 8BEC mov ebp, esp
:00434E58 6AFF push FFFFFFFF
:00434E5A 68302E4500 push 00452E30
:00434E5F 68A83F4300 push 00433FA8
:00434E64 64A100000000 mov eax, dword ptr fs:
:00434E6A 50 push eax
:00434E6B 64892500000000 mov dword ptr fs:, esp
其中几个被 push 的具体的值可能不同。根据这一点我们就可以在进程中查找上面这部分代码,找到的地方就是入口点。下面来看看具体的代码实现:
.data
g_Delphi_Signsdb55h, 8Bh, 0ECh, 83h, 0C4h, 0, 53h, 0B8h, 0, 0, 0, 0, 0E8h, 0, 0,
0, 0, 8Bh, 1Dh, 0, 0, 0, 0, 8Bh, 3h, 0E8h, 0, 0, 0, 0, 8Bh, 3h
g_VC6_Signs db55h, 8Bh, 0ECh, 6Ah, 0FFh, 68h, 0, 0, 0, 0, 68h, 0, 0, 0, 0, 64h,
0A1h, 0, 0, 0, 0, 50h, 64h, 89h, 25h, 0, 0, 0, 0
.code
_GetOEP proc lpMem:DWORD, dwLen:DWORD
LOCAL dwOEP
pushad
invoke_InString, lpMem, dwLen, addr g_Delphi_Signs, 32
.if eax
jmp exit_1
.endif
invoke_InString, lpMem, dwLen, addr g_VC6_Signs, 29
.if eax
jmp exit_1
.endif
jmp exit_0
exit_1:
mov dwOEP, eax
popad
mov eax, dwOEP
ret
exit_0:
popad
xor eax, eax
ret
_GetOEP endp
_InString proc lpszStr:DWORD, dwStrLen:DWORD, lpszSubStr:DWORD, dwSubStrLen:DWORD
LOCAL dwPos
pushad
mov eax, dwStrLen
.if eax < dwSubStrLen
jmp exit_0
.endif
sub eax, dwSubStrLen
mov dwStrLen, eax
mov esi, lpszStr
mov edi, lpszSubStr
xor edx, edx
Loop1:
cmp edx, dwStrLen
jz exit_0
xor ecx, ecx
mov al, byte ptr
mov bl, byte ptr
cmp al, bl
jz Loop2
inc edx
jmp Loop1
Loop2:
inc ecx
inc edx
cmp ecx, dwSubStrLen
jz exit_1
mov al, byte ptr
mov bl, byte ptr
cmp al, bl
jz Loop2
test al, al
jz Loop2
sub edx, ecx
inc edx
jmp Loop1
exit_1:
sub edx, ecx
mov dwPos, edx
popad
mov eax, dwPos
ret
exit_0:
popad
xor eax, eax
ret
_InString endpg_Delphi_Signs 和 g_VC6_Signs 分别对应 Delphi 和 VC6 编译的程序,其中的 0 代表可能不确定的字节。_GetOEP 函数就是具体获得入口点的函数,分别在进程空间中查找每一个特定的
入口点特征代码,如果能找到就说明找到了入口点。查找特征代码又是由函数 _InString 来完成的,具体实现看看代码就清楚了。
2. 中断在入口点
找到了入口点后,需要中断在入口点处准备 dump 进程,通过 Windows 本身提供的 Debug API 可以实现这一点。
invokeCreateProcess, 0, addr szFile, 0, 0, 0, DEBUG_PROCESS + DEBUG_ONLY_THIS_PROCESS,
0, 0, addr StartupInfo, addr ProcInfo2
.if !eax
invoke_OutputInfo, g_hOutputCtl, CTXT("不能创建进程!!!")
jmp l_exit
.endif
.while TRUE
invokeWaitForDebugEvent, addr DbgEvent, INFINITE
.if DbgEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT
;下面这一行代码很重要,否则被调试进程不会完全退出
invokeContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.break
.elseif DbgEvent.dwDebugEventCode==EXCEPTION_DEBUG_EVENT
.if DbgEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_BREAKPOINT
inc dwCountBP
.if dwCountBP==1 ;第一次中断时在原始入口点处设置断点
invoke_OutputInfo, g_hOutputCtl, CTXT("在原始入口点设置断点...")
mov int3, 0CCh
invokeReadProcessMemory, ProcInfo2.hProcess, dwOrgOEP, addr org_code, 1, 0
invokeWriteProcessMemory, ProcInfo2.hProcess, dwOrgOEP, addr int3, 1, 0
.elseif dwCountBP==2 ;第二次中断,这次是中断在原始入口点,在 OEP 处设置硬件断点
invoke_OutputInfo, g_hOutputCtl, CTXT("到达原始入口点")
mov g_context.ContextFlags, CONTEXT_CONTROL
invokeGetThreadContext, ProcInfo2.hThread, addr g_context
dec g_context.regEip
invokeWriteProcessMemory, ProcInfo2.hProcess, dwOrgOEP, addr org_code, 1, 0
invokeSetThreadContext, ProcInfo2.hThread, addr g_context
mov g_context.ContextFlags, CONTEXT_DEBUG_REGISTERS
invokeGetThreadContext, ProcInfo2.hThread, addr g_context
m2m g_context.iDr0, dwOEP
mov g_context.iDr7, 1
invokeSetThreadContext, ProcInfo2.hThread, addr g_context
invokewsprintf, addr buf, CTXT("在 OEP: %08lXh 处设置硬件断点..."), dwOEP
invoke_OutputInfo, g_hOutputCtl, addr buf
.endif
invokeContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_CONTINUE
.continue
.elseif DbgEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP
;第三次中断,来到真正的入口点,抓取进程,然后终止进程
invokewsprintf, addr buf, CTXT("中断在 OEP: %08lXh 处"), dwOEP
invoke_OutputInfo, g_hOutputCtl, addr buf
invoke_OutputInfo, g_hOutputCtl, CTXT("清除硬件断点...")
mov g_context.ContextFlags, CONTEXT_FULL
invokeGetThreadContext, ProcInfo2.hThread, addr g_context
mov g_context.iDr0, 0
mov g_context.iDr7, 0
invokeSetThreadContext, ProcInfo2.hThread, addr g_context
invoke_OutputInfo, g_hOutputCtl, CTXT("抓取进程...")
invoke_Dump, ProcInfo2.hProcess, dwImageBase, dwSizeOfImage, lpMem
invokeTerminateProcess, ProcInfo2.hProcess, 0
invokeContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_CONTINUE
.continue
.endif
.endif
invokeContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invokeCloseHandle, ProcInfo2.hThread
invokeCloseHandle, ProcInfo2.hProcess
mov ProcInfo2.hProcess, 0关键技术就是要在入口点处设置一个硬件断点,从而中断在入口点处准备 dump 进程。这里要设置硬件断点而不能设置一个 int3 断点的原因是我们设置断点的时候外壳还没有解密程序代码
。如果我们在入口点处写入一个 0CCh 字节来设置一个 int3 断点,当外壳把程序代码解密后,入口点处的 0CCh 字节又会被解密后的代码覆盖,所以 int3 断点不起作用。
3. dump 进程
中断在入口点处了就可以 dump 进程,这个代码很简单
_Dump proc hProcess:DWORD, lpBaseAddress:DWORD, dwSize:DWORD, lpBuffer:DWORD
pushad
invokeReadProcessMemory, hProcess, lpBaseAddress, lpBuffer, dwSize, 0
popad
ret
_Dump endp4. 修复输入表
修复输入表可以利用 ImpREC.dll 来完成,这个也很简单,只需调用一个 RebuildImport 函数就可以搞定。
mov g_lpRebuildImport, 0
invokeLoadLibrary, CTXT("ImpREC.dll")
.if eax
mov ebx, eax
invokeGetProcAddress, ebx, CTXT("RebuildImport")
.if eax
mov g_lpRebuildImport, eax
.else
invoke_OutputInfo, g_hOutputCtl, CTXT("不能从 ImpREC.dll 中引入 RebuildImport 函数")
invoke_OutputInfo, g_hOutputCtl, CTXT("脱壳后的文件不能重建输入表!!!")
.endif
.else
invoke_OutputInfo, g_hOutputCtl, CTXT("找不到 ImpREC.dll 文件")
invoke_OutputInfo, g_hOutputCtl, CTXT("脱壳后的文件不能重建输入表!!!")
.endif
invokeCreateProcess, NULL, addr szFile, NULL, NULL, NULL, NORMAL_PRIORITY_CLASS, \
NULL, NULL, addr StartupInfo, addr ProcInfo3
invokeWaitForInputIdle, ProcInfo3.hProcess, -1
invoke_OutputInfo, g_hOutputCtl, CTXT("重建输入表...")
mov ecx, dwOEP
sub ecx, dwImageBase
lea eax, g_buffer
push eax
push 5
push 0
push ecx
push ProcInfo3.dwProcessId
call g_lpRebuildImport ;调用 ImpREC.dll 中的 RebuildImport 函数重建输入表
.if eax==0
invoke_OutputInfo, g_hOutputCtl, CTXT("重建输入表失败!!!")
.else
invokeDeleteFile, addr g_buffer
lea esi, g_buffer
invokelstrlen, esi
add esi, eax
sub esi, 4
invokelstrcpy, esi, CTXT("_.exe")
.endif
invokeTerminateProcess, ProcInfo3.hProcess, 0后记
除了上面介绍的几个步骤外,还有文件修正,文件结构优化等可以参考本文附件中给出的代码。这里实现的只不过是一个很简单的脱壳机,对付不了几个壳。在下是一个菜鸟,文章很简
单可能还有很多错误,只是希望这篇文章能够对大家有一点点帮助。谢谢! 怎么连UPX都脱不了啊:( 跟调试没什么区别,写个静态的脱壳机吧。
这样能学到更多的东西~~ 了不起,支持,啊
页:
[1]