Themida的简单脱壳
很久没有写过文章了,看到还有人问Themida脱壳,简单写几句。不涉及VM。
脱的是NP1012的GameMon,Ring0玩不过,郁闷,更郁闷的是眼睛吃不消。
1. Dump点搜索:
以前的版本,挂在用CreateFile加载的kernel32!Sleep,就很容易找到dump
位置,这个似乎不同了,也不知道是什么版本.
先随便找个地方下断,比如对第1个区段下写入断点,目的是等VM所在内存分
配,然后在VM内搜索出口:
popad
popfd
ret
对找到的所有结果下断,写个脚本记录一下调用过程:
var counter
var ret_addr
mov counter,0
eob l_record
eoe l_record
run
l_record:
mov ret_addr,[esp]
log ret_addr
esto
结果:
........
00DDCBD3 Breakpoint at 00DDCBD3
ret_addr = 0088A795
00E42EF0 Breakpoint at 00E42EF0
ret_addr = 0088B084
00E4F835 Breakpoint at 00E4F835
ret_addr = 0088BAA2 <---- 这个
00E42EF0 Breakpoint at 00E42EF0
ret_addr = 02FA1A7E
00E4F835 Breakpoint at 00E4F835
ret_addr = 0046228E
00DDCBD3 Breakpoint at 00DDCBD3
ret_addr = 00402750
00E4F835 Breakpoint at 00E4F835
ret_addr = 00460327
00E42EF0 Breakpoint at 00E42EF0
ret_addr = 00402790
00E5D56F Breakpoint at 00E5D56F
ret_addr = 00410D1E
Thread 000005E4 terminated, exit code 0
Thread 00000184 terminated, exit code 0
Thread 000003DC terminated, exit code 0
最后几项的返回地址在原程序了,还有个02FA1A7E是模仿的GetVersion。
Ctrl-F2重来,在0088BAA2下断,还有一两次SMC解码(大概有,记不清了),
很容易找到dump点:
0088C432 /0F86 01000000 jbe GameMon.0088C439
0088C438 |F5 cmc
0088C439 \9D popfd
0088C43A C3 retn
-> 这里:
007D4BF3 68 54235132 push 32512354
007D4BF8 ^ E9 B9C6FFFF jmp GameMon.007D12B6
007D4BFD 68 C4711C55 push 551C71C4
007D4C02 ^ E9 AFC6FFFF jmp GameMon.007D12B6
007D4C07 FB sti
007D4C08 D125 06E03800 shl dword ptr ds:[38E006],1
2. 避开IAT加密
写脚本挂到CreateFile加载的kernel32!VirtualAlloc,记录分配size,
当出现连续分配0x1000,0x2000,0x10000,最后的1个出来就到了.
00EDA072 Hardware breakpoint 1 at 00EDA072
counter = 00000007
ret_addr = 007D787D
cbSize = 00001000
00EDA072 Hardware breakpoint 1 at 00EDA072
counter = 00000008
ret_addr = 007D789F
cbSize = 00002000
00EDA072 Hardware breakpoint 1 at 00EDA072
counter = 00000009
ret_addr = 007D78BF <---- 这里
cbSize = 00010000 | UNICODE "=::=::\"
007D78BF 8985 A9271406 mov dword ptr ss:[ebp+61427A9],eax
007D78C5 8BB5 31321406 mov esi,dword ptr ss:[ebp+6143231] ; GameMon.007CE59A
007D78CB 8B9D 41051406 mov ebx,dword ptr ss:[ebp+6140541]
007D78D1 89B5 05081406 mov dword ptr ss:[ebp+6140805],esi ; GameMon.007CE59A
007D78D7 899D C9021406 mov dword ptr ss:[ebp+61402C9],ebx
007D78DD 8B9D 41051406 mov ebx,dword ptr ss:[ebp+6140541]
007D78E3 8B0B mov ecx,dword ptr ds:[ebx]
007D78E5 83F9 00 cmp ecx,0
007D78E8 0F84 DF0A0000 je GameMon.007D83CD
其中007D78C5 mov esi,dword ptr ss:[ebp+6143231] 为IAT相关数据.
007CE59A 84B059E0 DD0B4D7D 4000A3B1 E000A52F
007CE5AA A000A54A E000C9EF FFFFFFFF DDDDDDDD
007CE5BA 10945DAF FD0B4D7D 200043CA 8000BC4E
........
.......
007D065A EEEEEEEE DDDDDDDD F684BB2D DD0B4D91
007D066A 0001BDBE AAAAAAAA FFFFFFFF DDDDDDDD
007D067A EEEEEEEE DDDDDDDD 00000000 00000000
007D068A 07B540EB 0823007D 087A007D FFFF007D
数据以FFFFFFFF DDDDDDDD EEEEEEEE DDDDDDDD结束,包括API Name的Hash,
对应要patch的JMP [IAT]代码地址。fly的做法是patch代码,对于
JMP [IAT]代码的修复,我是另写的程序,代码后面贴。
把这段数据binary copy保存下来,Themida用过要清0。先取干净的IAT。
走几步:
007D7A5B 61 popad
007D7A5C B9 5D71A45E mov ecx,5EA4715D <--------- key1
007D7A61 BA 6D5D107F mov edx,7F105D6D <--------- key2
007D7A66 AD lods dword ptr ds:[esi]
007D7A67 89B5 05081406 mov dword ptr ss:[ebp+6140805],esi ; kernel32.77EAF541
记下这2个值。下面和以前一样,避开IAT加密,NOP 4个JE。
007D7B83 83BD 4D251406 01 cmp dword ptr ss:[ebp+614254D],1
007D7B8A 0F84 39000000 je GameMon.007D7BC9
007D7B90 3B8D 712E1406 cmp ecx,dword ptr ss:[ebp+6142E71] ; kernel32.77E40000
007D7B96 0F84 2D000000 je GameMon.007D7BC9
007D7B9C 3B8D 85041406 cmp ecx,dword ptr ss:[ebp+6140485] ; USER32.77D10000
007D7BA2 0F84 21000000 je GameMon.007D7BC9
007D7BA8 3B8D 650E1406 cmp ecx,dword ptr ss:[ebp+6140E65] ; ADVAPI32.77DA0000
007D7BAE 0F84 15000000 je GameMon.007D7BC9
007D7BB4 8D9D BA422606 lea ebx,dword ptr ss:[ebp+62642BA]
007D7BBA FFD3 call ebx ; GameMon.007C5348
PATCH自校验:
007D7A28 C1E9 08 shr ecx,8
007D7A2B 33C8 xor ecx,eax
007D7A2D 4A dec edx
007D7A2E ^ 0F85 EAFFFFFF jnz GameMon.007D7A1E
007D7A34 8BC1 mov eax,ecx
007D7A36 F7D0 not eax
007D7A38 3985 E5021406 cmp dword ptr ss:[ebp+61402E5],eax <----- 校验
007D7A3E 0F84 17000000 je GameMon.007D7A5B
007D7A44 83BD D11E1406 00 cmp dword ptr ss:[ebp+6141ED1],0
得到干净的IAT后,保存tree。
Ctrl-F2重来,停在dump点。把前面保存的数据binary paste
到原位置(这些步骤可以写脱壳机来做,原来也搞过,但有些细节老变,有bound dll的
也不同,就算了)。运行Patch修复被改为NOP/CALL的原JMP [IAT]代码,Dump,Fix IAT。
代码以前的,有点乱,懒得去改了.
int main()
{
HANDLE hThemida = 0;
DWORD dwBegin = 0x007CE59A; // iat数据起始地址
DWORD dwEnd = 0x007D0682; // iat数据结束地址
PDWORD lpBuff = 0;
DWORD dwCounter = (dwEnd - dwBegin) / 4;
DWORD imageBase = 0x00400000;
//
hThemida = OpenProcess(PROCESS_ALL_ACCESS,FALSE,0x68C);
if(!hThemida)
{
printf("Failed to open the process!\r\n");
return 0;
}
lpBuff = (PDWORD)new BYTE[dwEnd - dwBegin];
ReadProcessMemory(hThemida,
(PVOID)dwBegin,
lpBuff,
dwEnd - dwBegin,
NULL);
DWORD i = 0;
DWORD key1 = 0x5EA4715D; // ecx ; NP1012 GameMon数据,什么版本的Themida?
DWORD key2 = 0x7F105D6D; // edx
DWORD lastData = 0; // 上1个数据
DWORD currData = 0; // 当前数据
DWORD iatAddr; // IAT地址
DWORD patchAddr; // PATCH地址
BYTE opcode[6];
while(TRUE) // 每轮循环处理1个API
{
if(i >= dwCounter)
break;
// 0xEEEEEEEE,0xDDDDDDDD dll结束标记
if((0xEEEEEEEE == lpBuff[i]) && (0xDDDDDDDD == lpBuff[i + 1]))
{
i += 2; // 跳过2个DWORD
continue;
}
// 取DWORD
currData = lpBuff[i] ^ lastData;
_asm mov eax,currData
_asm ror eax,3
_asm mov currData,eax
currData -= key2;
_asm mov eax,currData
_asm rol eax,0x10
_asm mov currData,eax
currData ^= key1;
lastData = lpBuff[i];
i++;
if(currData >= 0x10000) // currData为API名hash
{
//
// 取API地址,若为特殊API另行处理(4个JE)...
//
iatAddr = lpBuff[i];
_asm mov eax,iatAddr
_asm rol eax,5
_asm mov iatAddr,eax
iatAddr += key1;
iatAddr += imageBase;
i++;
//
// 将API地址保存到IAT...
//
// 若后续的2个DWORD为FFFFFFFF,DDDDDDDD代表该API
// 不需要PATCH代码(JMP [IAT])
while(!((0xFFFFFFFF == lpBuff[i]) && (0xDDDDDDDD == lpBuff[i + 1])))
{
// 下一个数据为Patch地址
patchAddr = lpBuff[i];
_asm mov eax,patchAddr
_asm rol eax,3
_asm mov patchAddr,eax
patchAddr += imageBase;
if(0xAAAAAAAA == lpBuff[i + 1]) // jmp [iat]
{
*((PWORD)opcode) = 0x25FF;
i += 2; // 跳过patch地址及AAAAAAAA
}
else // call [iat]
{
*((PWORD)opcode) = 0x15FF;
i += 1; // 跳过patch地址
}
*(PDWORD)&opcode[2] = iatAddr;
WriteProcessMemory(hThemida,(PVOID)patchAddr,opcode,6,NULL);
}
// 到这里,即发现FFFFFFFF,DDDDDDDD
i += 2; // 跳过标记
}
else
{
if(0xBBBBBBBB == lpBuff[i])
{
//
// 不处理的API,将API真实地址送入IAT...
//
i++; // 跳过BBBBBBBB
iatAddr = lpBuff[i];
_asm mov eax,iatAddr
_asm rol eax,5
_asm mov iatAddr,eax
iatAddr += key1;
iatAddr += imageBase;
i++; // 跳过IAT地址
//
while(!((0xFFFFFFFF == lpBuff[i]) && (0xDDDDDDDD == lpBuff[i + 1])))
{
patchAddr = lpBuff[i];
_asm mov eax,patchAddr
_asm rol eax,3
_asm mov patchAddr,eax
patchAddr += imageBase;
if(0xAAAAAAAA == lpBuff[i + 1]) // jmp [iat]
{
*((PWORD)opcode) = 0x25FF;
i += 2; // 跳过patch地址及AAAAAAAA
}
else // call [iat]
{
*((PWORD)opcode) = 0x15FF;
i += 1; // 跳过patch地址
}
*(PDWORD)&opcode[2] = iatAddr;
WriteProcessMemory(hThemida,(PVOID)patchAddr,opcode,6,NULL);
}
// 到这里,即发现FFFFFFFF,DDDDDDDD
i += 2; // 跳过标记
}
else
{
//
// 不满足currData >= 0x10000,但下1个值又不是BBBBBBBB
// 断不下,实际代码的处理是落到
// if(currData >= 0x10000)同样的代码
//
TRACE("Wrong\n");
}
}
}
delete [] (PBYTE)lpBuff;
CloseHandle(hThemida);
//
return nRetCode;
}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课