能力值:
( LV2,RANK:10 )
|
-
-
53 楼
-->>invoke DialogBoxParam,eax,IDD_DLGMAIN,NULL,addr DlgProc,@ThreadConText.regEcx
....
.if eax==WM_INITDIALOG
mov ebx,lParam
;assume ebx:ptr CONTEXT ;ebx作为CONTEXT类型指针使用
;要显示什么数据,怎么显示,那你就去分析debugee的注册算法吧,我这里只是示例
;invoke ReadProcessMemory,PI.hProcess,RegKeyAddr,addr Buffer,sizeof Buffer,NULL ;从特定内存地址中取注册码
;;ZWCAD2007
;invoke ReadProcessMemory,PI.hProcess,RegKeyAddr,addr Buffer,8,NULL ;从特定内存地址中取注册码
;invoke wsprintf,addr Buffer,addr szFmt,ebx;[ebx].regEcx ;从特定寄存器中取注册码
invoke ReadProcessMemory,PI.hProcess,ebx,addr Buffer,8,NULL ;从特定内存地址中取注册码
;invoke wsprintf,addr Buffer,addr szFmt,TimesBreak ;打印中断次数
invoke SetDlgItemText,hWin,IDC_REGKEY,addr Buffer
|
能力值:
( LV9,RANK:330 )
|
-
-
60 楼
偶然间在网上找到一篇同效的文章,其思路清晰代码漂亮,不敢独享,转贴上来给大家多个参考 来源:http://www.zhongts.net/MyEssay/EasyUnpack.htm
利用 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:[00000000] :00434E6A 50 push eax :00434E6B 64892500000000 mov dword ptr fs:[00000000], esp 其中几个被 push 的具体的值可能不同。根据这一点我们就可以在进程中查找上面这部分代码,找到的地方就是入口点。下面来看看具体的代码实现:
.data g_Delphi_Signs db 55h, 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 db 55h, 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 [edi+ecx] mov bl, byte ptr [esi+edx] cmp al, bl jz Loop2 inc edx jmp Loop1 Loop2: inc ecx inc edx cmp ecx, dwSubStrLen jz exit_1 mov al, byte ptr [edi+ecx] mov bl, byte ptr [esi+edx] 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 endp g_Delphi_Signs 和 g_VC6_Signs 分别对应 Delphi 和 VC6 编译的程序,其中的 0 代表可能不确定的字节。_GetOEP 函数就是具体获得入口点的函数,分别在进程空间中查找每一个特定的入口点特征代码,如果能找到就说明找到了入口点。查找特征代码又是由函数 _InString 来完成的,具体实现看看代码就清楚了。
2. 中断在入口点
找到了入口点后,需要中断在入口点处准备 dump 进程,通过 Windows 本身提供的 Debug API 可以实现这一点。
invoke CreateProcess, 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 invoke WaitForDebugEvent, addr DbgEvent, INFINITE .if DbgEvent.dwDebugEventCode==EXIT_PROCESS_DEBUG_EVENT ;下面这一行代码很重要,否则被调试进程不会完全退出 invoke ContinueDebugEvent, 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 invoke ReadProcessMemory, ProcInfo2.hProcess, dwOrgOEP, addr org_code, 1, 0 invoke WriteProcessMemory, ProcInfo2.hProcess, dwOrgOEP, addr int3, 1, 0 .elseif dwCountBP==2 ;第二次中断,这次是中断在原始入口点,在 OEP 处设置硬件断点 invoke _OutputInfo, g_hOutputCtl, CTXT("到达原始入口点") mov g_context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext, ProcInfo2.hThread, addr g_context dec g_context.regEip invoke WriteProcessMemory, ProcInfo2.hProcess, dwOrgOEP, addr org_code, 1, 0 invoke SetThreadContext, ProcInfo2.hThread, addr g_context mov g_context.ContextFlags, CONTEXT_DEBUG_REGISTERS invoke GetThreadContext, ProcInfo2.hThread, addr g_context m2m g_context.iDr0, dwOEP mov g_context.iDr7, 1 invoke SetThreadContext, ProcInfo2.hThread, addr g_context invoke wsprintf, addr buf, CTXT("在 OEP: %08lXh 处设置硬件断点..."), dwOEP invoke _OutputInfo, g_hOutputCtl, addr buf .endif invoke ContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_CONTINUE .continue .elseif DbgEvent.u.Exception.pExceptionRecord.ExceptionCode==EXCEPTION_SINGLE_STEP ;第三次中断,来到真正的入口点,抓取进程,然后终止进程 invoke wsprintf, addr buf, CTXT("中断在 OEP: %08lXh 处"), dwOEP invoke _OutputInfo, g_hOutputCtl, addr buf invoke _OutputInfo, g_hOutputCtl, CTXT("清除硬件断点...") mov g_context.ContextFlags, CONTEXT_FULL invoke GetThreadContext, ProcInfo2.hThread, addr g_context mov g_context.iDr0, 0 mov g_context.iDr7, 0 invoke SetThreadContext, ProcInfo2.hThread, addr g_context invoke _OutputInfo, g_hOutputCtl, CTXT("抓取进程...") invoke _Dump, ProcInfo2.hProcess, dwImageBase, dwSizeOfImage, lpMem invoke TerminateProcess, ProcInfo2.hProcess, 0 invoke ContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_CONTINUE .continue .endif .endif invoke ContinueDebugEvent, DbgEvent.dwProcessId, DbgEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED .endw invoke CloseHandle, ProcInfo2.hThread invoke CloseHandle, 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 invoke ReadProcessMemory, hProcess, lpBaseAddress, lpBuffer, dwSize, 0 popad ret _Dump endp
4. 修复输入表
修复输入表可以利用 ImpREC.dll 来完成,这个也很简单,只需调用一个 RebuildImport 函数就可以搞定。
mov g_lpRebuildImport, 0 invoke LoadLibrary, CTXT("ImpREC.dll") .if eax mov ebx, eax invoke GetProcAddress, 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
invoke CreateProcess, NULL, addr szFile, NULL, NULL, NULL, NORMAL_PRIORITY_CLASS, \ NULL, NULL, addr StartupInfo, addr ProcInfo3 invoke WaitForInputIdle, 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 invoke DeleteFile, addr g_buffer lea esi, g_buffer invoke lstrlen, esi add esi, eax sub esi, 4 invoke lstrcpy, esi, CTXT("_.exe") .endif invoke TerminateProcess, ProcInfo3.hProcess, 0 后记
除了上面介绍的几个步骤外,还有文件修正,文件结构优化等可以参考本文附件中给出的代码。这里实现的只不过是一个很简单的脱壳机,对付不了几个壳。在下是一个菜鸟,文章很简单可能还有很多错误,只是希望这篇文章能够对大家有一点点帮助。谢谢!
|