stucky 发表于 2011-5-12 17:48

谈谈VMP2.05的脱壳修复以及跨平台

本帖最后由 wgz001 于 2011-5-12 21:35 编辑

VMP的外壳部分不能说难,只能说烦。特别是IAT的修复,由于函数调用的错位(并非是规则的call xxxx nop或者nop call xxxx系列)、寄存器使用的随机性、大量的乱序等,使得写脚本修复是个非常蛋疼的事情,而且往往会修复错,修复漏,而VM代码里的IAT更是无法得到修复。于是,目前公开的方法中,最简单的也是最治本的修复方式,就是先生成张调用关系的表,然后写个DLL去读,在程序运行前,修正一些值,来达到跨平台。其他的,还真想不到有更完美的方法。或许牛人间有更好的处理方法,我这种菜鸟实在想不到了。。

至于修复IAT,牛人的文章不少了,比如kissy牛的2个视频,以及aa1ss2牛的学习笔记,这里就不再多说了。

下面写下个人脱壳的记录,当算是个简单的笔记,跟步骤的总结,没什么技术含量,一切都是按牛人的脚步学习的。

试练品是个XP记事本,加上VMP外壳的全选项,顺便把OEP处的代码也给和谐掉。下面开始脱壳:

1.CTRL+G,VirtualProtect,在VirtualProtectEx的调用处下好断点:

7C801AE3    E8 75FFFFFF          call kernel32.VirtualProtectEx

接着,在数据窗口定位到代码段,比如,这个样本的为01001000.主要是为了观察是否已经解码,找返回的时机。

2.一直F9,一直到代码段解码,取消断点。ALT+F9,返回到程序代码区。

3.HOOK VM_RETN,找程序的入口。

先来看下VM_RETN这条handler长成啥样吧:
01071753 Main   pushad                                  //VM_Retn
01071754 Main   xchg ax,di                              ; EAX=0000F694, EDI=0006000E
01071756 Main   jmp NOTEPAD_.0106FB88
0106FB88 Main   sub ch,bl                                 ; ECX=01072F53
0106FB8A Main   mov esp,ebp
0106FB8C Main   movzx cx,al                               ; ECX=01070094
0106FB90 Main   xadd cl,ah                              ; EAX=00009494, ECX=0107008A
0106FB93 Main   call NOTEPAD_.0107152C
0107152C Main   mov ecx,dword ptr ss:            ; ECX=00000246
01071530 Main   xchg eax,esi                              ; EAX=01018E26, ESI=00009494
01071531 Main   mov eax,dword ptr ss:            ; EAX=0006FFB0
01071535 Main   jmp NOTEPAD_.0107147E
0107147E Main   bswap cx                                  ; ECX=00000000
01071481 Main   aaa                                       ; EAX=0006FF00
01071482 Main   mov eax,dword ptr ss:            ; EAX=F035F4BE
01071486 Main   pushfd
01071487 Main   adc esi,A8EF9681                        ; ESI=A8F02B15
0107148D Main   jmp NOTEPAD_.0107163C
0107163C Main   std
0107163D Main   mov esi,dword ptr ss:             ; ESI=0100CB51
01071641 Main   xor di,dx                                 ; EDI=00060248
01071644 Main   neg di                                    ; EDI=0006FDB8
01071647 Main   shld si,sp,cl
0107164B Main   btr bp,0E                                 ; EBP=0006BF84
01071650 Main   mov edi,dword ptr ss:             ; EDI=524DB0B9
01071654 Main   sub esi,734AB4D1                        ; ESI=8DB61680
0107165A Main   lea ebp,dword ptr ds:       ; EBP=2DB839B8
01071660 Main   mov esi,dword ptr ss:             ; ESI=80A871EF
01071664 Main   inc bp                                    ; EBP=2DB839B9
01071667 Main   rcr bp,cl
0107166A Main   mov ebp,dword ptr ss:             ; EBP=E137F11A
0107166E Main   cmp bl,bh
01071670 Main   movzx bx,dl                               ; EBX=C71A0046
01071674 Main   mov cl,byte ptr ss:                ; ECX=00000098
01071678 Main   bt bx,0F
0107167D Main   mov ebx,dword ptr ss:             ; EBX=05B50161
01071681 Main   rol dh,cl
01071683 Main   mov ecx,dword ptr ss:             ; ECX=213EBC0B
01071687 Main   dec dx                                    ; EDX=00000245
0107168A Main   mov edx,dword ptr ss:             ; EDX=DA74A7B8
0107168E Main   clc
0107168F Main   add esp,30
01071692 Main   je NOTEPAD_.0106FD6C
01071698 Main   popfd
01071699 Main   pushad
0107169A Main   mov dword ptr ss:,AD22F63E
010716A1 Main   push dword ptr ss:
010716A4 Main   pushfd
010716A5 Main   push dword ptr ss:
010716A9 Main   retn 2C直接在010716A9下好断点,F9运行,断下后取消断点,F7,来到下面的地方:
0102A32B    9C                   pushfd
0102A32C    9C                   pushfd
0102A32D    8F0424               pop dword ptr ss:
0102A330    F5                   cmc
0102A331    81F5 EA0E31E1      xor ebp,E1310EEA
0102A337 ^ E9 29E3FEFF          jmp NOTEPAD_.01018665
0102A33C    AF                   scas dword ptr es:
4.继续F7几次,就可以来到下面的地方:
01007568    68 BA750001          push NOTEPAD_.010075BA
0100756D    64:A1 00000000       mov eax,dword ptr fs:
01007573    50                   push eax
01007574    8B4424 10            mov eax,dword ptr ss:
01007578    896C24 10            mov dword ptr ss:,ebp
0100757C    8D6C24 10            lea ebp,dword ptr ss:
01007580    2BE0               sub esp,eax
01007582    53                   push ebx
01007583    56                   push esi
01007584    57                   push edi
01007585    8B45 F8            mov eax,dword ptr ss:
01007588    8965 E8            mov dword ptr ss:,esp
0100758B    50                   push eax
0100758C    8B45 FC            mov eax,dword ptr ss:
0100758F    C745 FC FFFFFFFF   mov dword ptr ss:,-1
01007596    8945 F8            mov dword ptr ss:,eax
01007599    8D45 F0            lea eax,dword ptr ss:
0100759C    64:A3 00000000       mov dword ptr fs:,eax
010075A2    C3                   retn
这是多么熟悉的代码啊。因为VM并不支持CALL的嵌套,这就给我们可趁之机。先dump一份。

5.下面就来补代码:

此时看堆栈窗口:
0006FFB8   0101654F 返回到 NOTEPAD_.0101654F 来自 NOTEPAD_.0101BFA7 //CALL的出口,注意
0006FFBC   01001898 NOTEPAD_.01001898
0006FFC0   00000070
于是,在代码段,随便找个地址,补入如下代码:
01008890 > 6A 70                push 70
01008892    68 98180001          push dumped1.01001898
01008897    68 4F650101          push dumped1.0101654F
0100889C ^ E9 C7ECFFFF          jmp dumped1.01007568
010088A1    90                   nop
补完后,保存一下,顺便把OEP设置成8890.

OK,程序能够正常。不过此时还不能跨平台。

6.下面开始修复跨平台,这部分我就不说了,自己看牛人的文章跟视频,跑份IAT调用的对应关系表,部分如下:
1013360,DE02099D,comdlg32,PageSetupDlgW
102B3D6,E53997CF,comdlg32,ChooseFontW
1014ABD,4443359,comdlg32,PrintDlgExW
103107A,F8A5F191,comdlg32,ReplaceTextW
1019B4F,8F70D755,comdlg32,GetFileTitleW
101403B,AB122473,comdlg32,FindTextW
101CF16,7495EB47,comdlg32,CommDlgExtendedError
1016371,D8A0FB6B,comdlg32,GetOpenFileNameW
1014D62,BED81FBF,shell32,DragAcceptFiles
10230B9,A0524363,shell32,DragFinish
1022832,F10CB8C9,shell32,DragQueryFileW
101D8F2,4AD7BE0D,comdlg32,GetSaveFileNameW
10314C3,F6D99DC5,shell32,ShellAboutW
102A015,5B0DDC5B,winspool,GetPrinterDriverW
1023366,181A1537,winspool,OpenPrinterW
7.下面就简单的写个DLL,来完成工作,原理很简单,比如某个函数调用中,关键代码如下:
01031019    BA A2390001          mov edx,dump.010039A2
010328AE    8B92 FD430000      mov edx,dword ptr ds: //注意这里
010328BE    8D92 DB73ECD6      lea edx,dword ptr ds: //这句出真实的地址
ds:=A594FBB8
edx=010039A2 (dump.010039A2)
找下对应的关系表:
1007D9F,D6EC73DB,kernel32,GetCommandLineW
可知,在地址01007D9F中存着解密的key,而解密的key为多少,即为真实的API地址-表中对用的key

其实也就是这句的逆过程:lea edx,dword ptr ds:

由于不同的平台,API的地址有差异,所有想要跨平台,就需要修正这个解密的key值,方法是在当前的用户平台上,先获取当前用户平台的API地址,再减去表中对应的那个KEY值即可。

知道了原理,就可以写代码了,代码写的很挫,仅供参考:
#include <windows.h>
#include <stdio.h>
extern "C" BOOL __declspec(dllexport) FkVMPIAT();
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,LPVOID lpReserved)
{
if (ul_reason_for_call==DLL_PROCESS_ATTACH)
{
   FkVMPIAT();
}
return TRUE;
}
BOOL FkVMPIAT()
{
DWORD dwImageBase=(DWORD)GetModuleHandle(NULL);
PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)dwImageBase;
if (pDosHeader->e_magic!=IMAGE_DOS_SIGNATURE)
{
   return FALSE;
}
    PIMAGE_NT_HEADERS pNtHeader=(PIMAGE_NT_HEADERS)(dwImageBase+pDosHeader->e_lfanew);
if (pNtHeader->Signature!=IMAGE_NT_SIGNATURE)
{
   return FALSE;
}
UINT NumOfSection=pNtHeader->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER pSectionHeader=(PIMAGE_SECTION_HEADER)((UINT_PTR)pNtHeader+0x18+
   (UINT_PTR)(pNtHeader->FileHeader.SizeOfOptionalHeader));
for(int i=0;i<NumOfSection;i++)
{
   DWORD dwSecVA=pSectionHeader->VirtualAddress+dwImageBase;
   DWORD dwSecSize=pSectionHeader->Misc.VirtualSize;
   DWORD dwOldProtect;
   VirtualProtect((LPVOID)dwSecVA,dwSecSize,PAGE_EXECUTE_READWRITE,&dwOldProtect);
   pSectionHeader=(PIMAGE_SECTION_HEADER)((DWORD)pSectionHeader+sizeof(IMAGE_SECTION_HEADER));
}
FILE *fp;
fp=fopen("FkIAT.txt","r");
    if (fp==NULL)
    {
   MessageBox(NULL,"请把fkiat.txt放当前目录下!","ximo",MB_OK|MB_ICONINFORMATION);
    }
char buf={0};
while(!feof(fp))
{
       fgets(buf,256,fp);
    DWORD dwIATAddr,dwKey;
    char szIATAddr={0};
    char szKey={0};
    char szDllName={0};
    char szFunName={0};
    char *p;
    int i=0;
    if ((p=strchr(buf,','))==NULL)
    {
   continue;
    }
    while(buf!=',')
    {
   szIATAddr=buf;
   i++;
    }
    sscanf(szIATAddr,"%08x",&dwIATAddr);
    p=strchr(buf,',');
    i=0;
    while(p!=',')
    {
   szKey=p;
   i++;
    }
    sscanf(szKey,"%08x",&dwKey);
   
    p=strchr(p+1,',');
    i=0;
    while(p!=',')
    {
   szDllName=p;
   i++;
    }
    p=strchr(p+1,',');
    i=0;
    while(p!=0x0A)
    {
   szFunName=p;
   i++;
    }
    //特殊处理下,不知道是否还有其他动态库也是如此的,想不出好的,只能硬编码了,汗
   if (strcmp(szDllName,"winspool")==0)
    {
   wsprintf(szDllName,"%s","winspool.drv");
    }
    //特殊处理2
    /*
    if ((p=strchr(szDllName,0x20))!=0)
    {
   wsprintf(szDllName,"%s",p+1);
    }*/
    if ((p=strstr(szDllName,"offset"))!=0)
    {
   wsprintf(szDllName,"%s",p+7);
    }
    HMODULE hdll=GetModuleHandle(szDllName);
    if (!hdll)
    {
   hdll=LoadLibrary(szDllName);
    }
   DWORD dwIAT=(DWORD)GetProcAddress(hdll,(LPCSTR)szFunName);
   DWORD dwWrite=dwIAT-dwKey;
   __asm
   {
    pushad
    mov eax,dwIATAddr
    mov ecx,dwWrite
    mov dword ptr ,ecx
    popad
   }
}
fclose(fp);
return TRUE;
}
8,最后就是用PE工具把这个DLL加到脱壳后文件的导入表中了。这个用LORDPE,CFF等工具都能搞定了。
然后把3个文件:脱壳后文件,DLL,以及对应关系的txt这3个文件放在一起,就OK了。放到其他平台测试一下,OK了。
脱壳也就到此结束了。

cu629944 发表于 2011-5-12 18:05

呃..这个我怎么觉的我看过哈~好像是超哥的~支持ximo大大~

小败 发表于 2011-5-12 18:11

很好很强大,给分吧,没了

闭幕 发表于 2011-5-12 18:12

如果是教程那该多好啊!

Emil 发表于 2011-5-12 19:16

谢谢分享! 支持了

a408115319 发表于 2011-5-12 19:33

貌似超哥发过了呃···

xiaocainiaok 发表于 2011-5-12 19:37

给的很详细 学习~~

xiaocainiaok 发表于 2011-5-12 19:39

回复 stucky 的帖子

想知道 导入了dll后 他是怎么加载的就算导入dll也应该有导出函数吧脱壳后的程序 怎么加载 导出函数修复iat的呢?

792404868@qq.co 发表于 2011-5-12 20:36

好东西。。支持+收藏了

李明1900 发表于 2011-5-13 21:30

很好很强大,给分吧,没了
页: [1] 2 3
查看完整版本: 谈谈VMP2.05的脱壳修复以及跨平台