前段时间学习脱PeCompact 壳,做了一些笔记。
希望对初学者有所帮助。
PECompact完美脱壳
同Aspack一样,PECompact也是可以被完美脱壳的。原理是:外壳完整地保留了输入表,外壳加载时没有对IAT加密;外壳解压数据时,完整的输入表会在内存中出现,然后外壳用显示加载DLL的方式获得各个函数的地址,并将该地址填充到IAT中。脱壳时,可以在所有的区块都解压还原后抓取内存镜像,此时外壳还没有来得及破坏原始的输入表。然后用PE编辑器修复抓取的镜像文件(主要是修改程序入口点,输入表)。
举例说明。
查壳:
PEID PECompact 2.x -> Jeremy Collake
ExeInfo PEcompact ver.2.78a ~ 3.00
OD设置为不忽略所有异常,载入程序。
00401000 > $ B8 683B4700 mov eax, 00473B68
00401005 . 50 push eax
00401006 . 64:FF35 00000>push dword ptr fs:[0]
0040100D . 64:8925 00000>mov dword ptr fs:[0], esp
00401014 . 33C0 xor eax, eax
00401016 . 8908 mov dword ptr [eax], ecx
00401018 . 50 push eax
停在外壳入口点00401000处。Ctrl+G来到00473B68处,F2下断。F9运行。
00401016 . 8908 mov dword ptr [eax], ecx
00401018 . 50 push eax
00401019 . 45 inc ebp
0040101A . 43 inc ebx
在00401016处有写入异常。Shift+F9运行,就会停在我们刚才下的断点处。F2取消断点。然后F8单步执行。
00473B68 B8 ED280B00 mov eax, 0B28ED
00473B6D 8D88 9E123C00 lea ecx, dword ptr [eax+3C129E]
00473B73 8941 01 mov dword ptr [ecx+1], eax
00473B76 8B5424 04 mov edx, dword ptr [esp+4]
00473B7A 8B52 0C mov edx, dword ptr [edx+C]
00473B7D C602 E9 mov byte ptr [edx], 0E9
00473B80 83C2 05 add edx, 5
00473B83 2BCA sub ecx, edx
00473B85 894A FC mov dword ptr [edx-4], ecx
00473B88 33C0 xor eax, eax
00473B8A C3 retn
运行到00473B88处,在堆栈中找到异常处理回调函数的第一个参数,在上面点右键》Follow in Dump,
然后,查看数据窗口。
第4个DWORD就是异常发生的地址。在00401016处下断,F9运行,就会断在00401016处,F2取消断点。F8单步执行。
直到遇到指令call edi,F7跟进去(执行外壳第二段)。
00473BF2 52 push edx
00473BF3 8BF0 mov esi, eax
00473BF5 8B46 FC mov eax, dword ptr [esi-4]
00473BF8 83C0 04 add eax, 4
00473BFB 2BF0 sub esi, eax
00473BFD 8956 08 mov dword ptr [esi+8], edx
00473C00 8B4B 0C mov ecx, dword ptr [ebx+C]
00473C03 894E 14 mov dword ptr [esi+14], ecx
00473C06 FFD7 call edi //这里F7跟进
00473C08 8985 3F133C00 mov dword ptr [ebp+3C133F], eax
00473C0E 8BF0 mov esi, eax
00473C10 8B4B 14 mov ecx, dword ptr [ebx+14]
00473C13 5A pop edx
00473C14 EB 0C jmp short 00473C22
F8单步执行。
003C1164 8985 271F3C00 mov dword ptr [ebp+3C1F27], eax
003C116A 56 push esi
003C116B E8 F6030000 call 003C1566 ; 解压各区段
003C1170 8D8D BD1D3C00 lea ecx, dword ptr [ebp+3C1DBD]
003C1176 85C0 test eax, eax
003C1178 0F85 94000000 jnz 003C1212
003C117E 56 push esi
003C117F E8 40030000 call 003C14C4 ; 修复指令CALL的地址
003C1184 56 push esi
003C1185 E8 55020000 call 003C13DF
003C118A 90 nop
一直F8单步执行。
003C1198 8B4E 34 mov ecx, dword ptr [esi+34] ; ECX=IID的RVA
003C119B 85C9 test ecx, ecx
003C119D 0F84 89000000 je 003C122C ; 双击ZF使其为1,跳过外壳对IAT的填充
003C11A3 034E 08 add ecx, dword ptr [esi+8]
003C11A6 51 push ecx
003C11A7 56 push esi
003C11A8 E8 47060000 call 003C17F4
003C11AD 85C0 test eax, eax
003C11AF 74 7B je short 003C122C
003C11B1 8B95 AA1A3C00 mov edx, dword ptr [ebp+3C1AAA]
003C11B7 8B8D AE1A3C00 mov ecx, dword ptr [ebp+3C1AAE]
(重要说明:跳过了外壳对IAT的填充,自然也就跳过了对IAT的加密。因此此种脱壳法对加密了IAT的依然有效)
F8单步执行到指令ret。返回到外壳第一段。
00473C06 FFD7 call edi ; 这里要F7跟进去
00473C08 8985 3F133C00 mov dword ptr [ebp+3C133F], eax ; 外壳第二段返回到这里,此时EAX为OEP
00473C0E 8BF0 mov esi, eax
00473C10 8B4B 14 mov ecx, dword ptr [ebx+14]
00473C13 5A pop edx
00473C14 EB 0C jmp short 00473C22
00473C16 03CA add ecx, edx
00473C18 68 00800000 push 8000
00473C1D 6A 00 push 0
00473C1F 57 push edi
00473C20 FF11 call dword ptr [ecx]
00473C22 8BC6 mov eax, esi
00473C24 5A pop edx
00473C25 5E pop esi
00473C26 5F pop edi
00473C27 59 pop ecx
00473C28 5B pop ebx
00473C29 5D pop ebp
00473C2A FFE0 jmp eax
指令jmp eax,跳转到OEP。此时,用LoadPE 抓取内存镜像。
再用LoadPE的PE编辑功能修改几个参数将输入表修改为60000,入口点修改为271B0。
最后使用我编写的修复工具修复输入表就OK了。
By asdfslw [www.pediy.com]
附件里面有UnpackMe和脱壳笔记,下面给出脱壳后修复程序的C++代码(VC 6 下编译通过)
/* PeCompact 完美脱壳时抓取的内存镜像文件的输入表有些问题IID数组中 FirstThunk 指向的区域(IAT)全部为0。 这个程序用来修复输入表,修复方式很简单,就是: 将 OriginalFirstThunk 指向的内容(INT)全部拷贝到FirstThunk 指向的区域(IAT)。 created by asdfslw 2009.11.20 */ #include <windows.h> // 全局变量 HANDLE hFile; HANDLE hFileMap; PBYTE pbFile; PIMAGE_DOS_HEADER pDos; PIMAGE_NT_HEADERS pNt; PIMAGE_SECTION_HEADER pSec; PIMAGE_IMPORT_DESCRIPTOR pIID; // rva 转换为 offset DWORD RVA_2_Offset(DWORD rva) { DWORD offset; PIMAGE_SECTION_HEADER pSec1 = pSec + 1; if (rva < pSec->VirtualAddress) { offset = rva; return offset; } for (int i=0; i<pNt->FileHeader.NumberOfSections-1; i++) { if (rva<pSec1->VirtualAddress) { --pSec1; offset = rva - pSec1->VirtualAddress + pSec1->PointerToRawData; return offset; } ++pSec1; } --pSec1; offset = rva - pSec1->VirtualAddress + pSec1->PointerToRawData; return offset; } // 自定义的拷贝函数 void __declspec(naked) my_copy (DWORD dst, DWORD src) { __asm { push ebp mov ebp, esp push esi push edi mov edi, [ebp+8h] /* dst */ mov esi, [ebp+0ch] /* src */ _loop_begin: lods dword ptr [esi] test eax, eax jz _loop_end stos dword ptr [edi] jmp _loop_begin _loop_end: pop edi pop esi pop ebp retn } } int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd ) { char FileName[MAX_PATH] = {0}; OPENFILENAME ofn; ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = 0; ofn.lpstrFilter = "Exe file (*.exe)\0*.exe\00"; ofn.lpstrCustomFilter = NULL; ofn.nFilterIndex = 1; ofn.lpstrFile = FileName; ofn.nMaxFile = sizeof FileName; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = 0; ofn.lpstrInitialDir = NULL; ofn.lpstrTitle = "Pecompact 完美脱壳修复工具 by asdfslw"; ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_ENABLESIZING; ofn.lpstrDefExt = NULL; if(0==GetOpenFileName(&ofn)) { goto _error; } // 打开文件 hFile = CreateFileA(FileName, GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE==hFile) { MessageBoxA(NULL, "打开文件错误!", "错误", MB_ICONEXCLAMATION); goto _error; } // 创建文件映射 hFileMap = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, NULL, NULL, NULL); if (NULL==hFileMap) { MessageBoxA(NULL, "创建文件映射错误!", "错误", MB_ICONEXCLAMATION); CloseHandle(hFile); goto _error; } // 将文件数据映射到进程地址空间 pbFile = (PBYTE)MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0); if (NULL==pbFile) { MessageBoxA(NULL, "将文件数据映射到进程地址空间错误!", "错误", MB_ICONEXCLAMATION); CloseHandle(hFileMap); CloseHandle(hFile); goto _error; } // 获取PE结构相关数据 pDos = (PIMAGE_DOS_HEADER) pbFile; pNt = (PIMAGE_NT_HEADERS) ( (PBYTE)pDos + pDos->e_lfanew ); pSec = (PIMAGE_SECTION_HEADER) ( (PBYTE)pNt + 0xF8 ); pIID = (PIMAGE_IMPORT_DESCRIPTOR) ( RVA_2_Offset(pNt->OptionalHeader.DataDirectory[1].VirtualAddress) + (DWORD)pbFile ); while (NULL!=pIID->Name) { my_copy( RVA_2_Offset(pIID->FirstThunk)+ (DWORD)pbFile, RVA_2_Offset(pIID->OriginalFirstThunk) + (DWORD)pbFile ); ++pIID; } MessageBoxA(NULL, "成功", "呵呵", MB_OK); UnmapViewOfFile(pbFile); CloseHandle(hFileMap); CloseHandle(hFile); return 0; _error: ExitProcess(0); }
本人在脱壳方面也是一个初学者。笔记中有什么不对的地方,希望各位高手指正。谢谢

另外,大家可以参考看雪如下文章
http://bbs.pediy.com/showthread.php?t=10932
这篇文章的思路与我的相同(不过写这篇笔记之前我并没有看到这篇文章,之后才搜索到的)。但遗憾的是这篇文章里面没有对脱壳后的文件的输入表进行修复,我则找出了脱壳后的文件不能运行的问题所在输入表有问题,并编写了一个修复程序(虽然修复其实很简单)。
【版权声明】本文由asdfslw原创于看雪软件安全论坛,转载请注明出处,并保留版权声明。
更新 :添加了修复程序PeCompact_IAT_xiufu.rar。
关于修复程序的说明:如果你的电脑上安装有主动防御类软件或者其他安全软件,在修复程序运行的时候可能会报警。比如说,我的电脑上安装有微点主动防御软件,修复程序会修改可执行文件,默认情况下微点对修改可执行文件的程序都会报警。此时只要点允许,并把修复程序添加到可信程序中,重新运行一次修复程序就可以了。本人保证此修复程序是没有任何危害的。