ximo大虾和谐VMP2.05

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:[esp+4]              ; ECX=00000246
01071530 Main     xchg eax,esi                              ; EAX=01018E26, ESI=00009494
01071531 Main     mov eax,dword ptr ss:[esp+8]              ; 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:[esp+C]              ; 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:[esp+14]             ; 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:[esp+18]             ; EDI=524DB0B9
01071654 Main     sub esi,734AB4D1                          ; ESI=8DB61680
0107165A Main     lea ebp,dword ptr ds:[edi+DB6A88FF]       ; EBP=2DB839B8
01071660 Main     mov esi,dword ptr ss:[esp+1C]             ; ESI=80A871EF
01071664 Main     inc bp                                    ; EBP=2DB839B9
01071667 Main     rcr bp,cl
0107166A Main     mov ebp,dword ptr ss:[esp+20]             ; EBP=E137F11A
0107166E Main     cmp bl,bh
01071670 Main     movzx bx,dl                               ; EBX=C71A0046
01071674 Main     mov cl,byte ptr ss:[esp+4]                ; ECX=00000098
01071678 Main     bt bx,0F
0107167D Main     mov ebx,dword ptr ss:[esp+24]             ; EBX=05B50161
01071681 Main     rol dh,cl
01071683 Main     mov ecx,dword ptr ss:[esp+28]             ; ECX=213EBC0B
01071687 Main     dec dx                                    ; EDX=00000245
0107168A Main     mov edx,dword ptr ss:[esp+2C]             ; 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:[esp],AD22F63E
010716A1 Main     push dword ptr ss:[esp]
010716A4 Main     pushfd
010716A5 Main     push dword ptr ss:[esp+28]
010716A9 Main     retn 2C

直接在010716A9下好断点,F9运行,断下后取消断点,F7,来到下面的地方:

0102A32B    9C                   pushfd
0102A32C    9C                   pushfd
0102A32D    8F0424               pop dword ptr ss:[esp]
0102A330    F5                   cmc
0102A331    81F5 EA0E31E1        xor ebp,E1310EEA
0102A337 ^ E9 29E3FEFF          jmp NOTEPAD_.01018665
0102A33C    AF                   scas dword ptr es:[edi]

4.继续F7几次,就可以来到下面的地方:

01007568    68 BA750001          push NOTEPAD_.010075BA
0100756D    64:A1 00000000       mov eax,dword ptr fs:[0]
01007573    50                   push eax
01007574    8B4424 10            mov eax,dword ptr ss:[esp+10]
01007578    896C24 10            mov dword ptr ss:[esp+10],ebp
0100757C    8D6C24 10            lea ebp,dword ptr ss:[esp+10]
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:[ebp-8]
01007588    8965 E8              mov dword ptr ss:[ebp-18],esp
0100758B    50                   push eax
0100758C    8B45 FC              mov eax,dword ptr ss:[ebp-4]
0100758F    C745 FC FFFFFFFF     mov dword ptr ss:[ebp-4],-1
01007596    8945 F8              mov dword ptr ss:[ebp-8],eax
01007599    8D45 F0              lea eax,dword ptr ss:[ebp-10]
0100759C    64:A3 00000000       mov dword ptr fs:[0],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:[edx+43FD] //注意这里
010328BE    8D92 DB73ECD6        lea edx,dword ptr ds:[edx+D6EC73DB] //这句出真实的地址

ds:[01007D9F]=A594FBB8
edx=010039A2 (dump.010039A2)

找下对应的关系表:

1007D9F,D6EC73DB,kernel32,GetCommandLineW

可知,在地址01007D9F中存着解密的key,而解密的key为多少,即为真实的API地址-表中对用的key

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

由于不同的平台,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[256]={0};
while(!feof(fp))
{
fgets(buf,256,fp);
DWORD dwIATAddr,dwKey;
char szIATAddr[10]={0};
char szKey[10]={0};
char szDllName[50]={0};
char szFunName[100]={0};
char *p;
int i=0;
if ((p=strchr(buf,’,’))==NULL)
{
continue;
}
while(buf[i]!=’,’)
{
szIATAddr[i]=buf[i];
i++;
}
sscanf(szIATAddr,”%08x”,&dwIATAddr);
p=strchr(buf,’,’);
i=0;
while(p[i+1]!=’,’)
{
szKey[i]=p[i+1];
i++;
}
sscanf(szKey,”%08x”,&dwKey);

p=strchr(p+1,’,’);
i=0;
while(p[i+1]!=’,’)
{
szDllName[i]=p[i+1];
i++;
}
p=strchr(p+1,’,’);
i=0;
while(p[i+1]!=0x0A)
{
szFunName[i]=p[i+1];
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 [eax],ecx
popad
}
}
fclose(fp);

return TRUE;
}

8,最后就是用PE工具把这个DLL加到脱壳后文件的导入表中了。这个用LORDPE,CFF等工具都能搞定了。

然后把3个文件:脱壳后文件,DLL,以及对应关系的txt这3个文件放在一起,就OK了。放到其他平台测试一下,OK了。

脱壳也就到此结束了。

附上试练品,脱壳后的文件,DLL等吧。

http://u.115.com/file/t063e0b7a9