【文章标题】: Petite脱壳详解
【文章作者】: index09
【作者邮箱】: cradiator@gmail.com
【作者主页】: http://hi.baidu.com/index09
【软件名称】: PeTite2.3 + OD + Stud_PE
--------------------------------------------------------------------------------
【详细过程】
已经分析了两个压缩壳了,这些天分析了第一款加密壳,果然比UPX之类的复杂很多。
PeTite是一款不错的加密压缩外壳,压缩分为9级,我用最强的级别加密了windows自带的计算器程序。
居然压了10多分钟 = =||| 压缩完了和UPX的比率差不多。闲话少说开始分析。
习惯用Stud_PE先看一下节表,不过事实证明这次没什么有用的信息。
No | Name | VSize | VOffset | RSize | ROffset | Charact. |
01 | .petite | 00015000 | 00001000 | 00006800 | 00000800 | E0000060 |
02 | .rsrc | 00009000 | 00016000 | 0000712D | 00007000 | C0000040 |
03 | | 000003CA | 0001F000 | 00000400 | 00000400 | E2000060 |
OD载入后,首先运行程序,出现异常。SHIFT+F9两次以后出现了计算器的界面。
看来加密壳果然还是在解压时动了一些手脚了。
开始分析,OD载入后代码如下
0101F046 > B8 00F00101 MOV EAX,calc.0101F000 ; eax = sec code(no name)
0101F04B 68 004A0101 PUSH calc.01014A00 ; 这里安装了SEH handler = 01014A00
0101F050 64:FF35 0000000>PUSH DWORD PTR FS:[0] ; 这时handler里面还没有代码
0101F057 64:8925 0000000>MOV DWORD PTR FS:[0],ESP
已经非常清楚了,这里安装了异常处理函数。哈,这也太明显了。记下来函数地址01014A00。
0101F05E 66:9C PUSHFW
0101F060 60 PUSHAD
0101F061 50 PUSH EAX
0101F062 8BD8 MOV EBX,EAX ; ebx = sec no name
0101F064 0300 ADD EAX,DWORD PTR DS:[EAX] ; eax = 函数指针数组
0101F066 68 C85F0000 PUSH 5FC8
0101F06B 6A 00 PUSH 0 ; GMEM_FIXED
0101F06D FF50 1C CALL DWORD PTR DS:[EAX+1C] ; GlobalAlloc
0101F070 8943 08 MOV DWORD PTR DS:[EBX+8],EAX ; [ebx+8] = 空间地址
继续往下,程序申请了一段缓冲区,并且存入了 03号段 + 8 这个偏移之中。
这段缓冲在以后的解压过程我们会看到。
后来程序用VirtualProtect将自己IAT的一部分复制到01000780
0101F091 56 PUSH ESI
0101F092 6A 02 PUSH 2
0101F094 50 PUSH EAX
0101F095 57 PUSH EDI
0101F096 6A 12 PUSH 12
0101F098 6A 0A PUSH 0A
0101F09A 56 PUSH ESI ; oldProtect
0101F09B 6A 04 PUSH 4 ; PAGE_READWRITE
0101F09D 50 PUSH EAX ; size = 880
0101F09E 57 PUSH EDI ; address = 1000780
0101F09F FFD3 CALL EBX ; VirtualProtect
0101F0A1 83EE 08 SUB ESI,8
0101F0A4 59 POP ECX ; ecx = 0x0A0
0101F0A5 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>; 这里重新建立了一个函数数组 地址01000780
0101F0A7 59 POP ECX ; ecx = 0x12
0101F0A8 66:83C7 58 ADD DI,58
0101F0AC 81C6 E0000000 ADD ESI,0E0 ; esi指向dll数组 地址01000780
0101F0B2 F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
0101F0B4 FFD3 CALL EBX ; VirtualProtect 恢复PAGE_READONLY
数据如下
01000780 7E4507EA USER32.MessageBoxA
01000784 7E41A8AD USER32.wsprintfA
01000788 00000002
0100078C 7C81CAFA kernel32.ExitProcess
01000790 7C801D7B kernel32.LoadLibraryA
01000794 7C80AE30 kernel32.GetProcAddress
01000798 7C801AD4 kernel32.VirtualProtect
0100079C 7C80FDBD kernel32.GlobalAlloc
010007A0 7C80FCBF kernel32.GlobalFree
010007A4 7C80B731 kernel32.GetModuleHandleA
010007A8 00000000
继续往下这里出现了一个很重要的数据结构
0101F0B7 8D90 A0010000 LEA EDX,DWORD PTR DS:[EAX+1A0] ; /////////////将元素1指向的数据复制到缓冲,大小为元素5/////////////////////////
0101F0BD 8B0A MOV ECX,DWORD PTR DS:[EDX] ; ecx = 元素1
0101F0BF 83C2 14 ADD EDX,14 ; 0x14字节的结构
0101F0C2 8B5A F0 MOV EBX,DWORD PTR DS:[EDX-10] ; ebx = 元素2
0101F0C5 85DB TEST EBX,EBX
0101F0C7 ^ 74 F4 JE SHORT calc.0101F0BD
0101F0C9 8B0424 MOV EAX,DWORD PTR SS:[ESP] ; eax = 一个偏移
0101F0CC 8D3401 LEA ESI,DWORD PTR DS:[ECX+EAX] ; esi = 不知什么
0101F0CF 8B6C24 04 MOV EBP,DWORD PTR SS:[ESP+4] ; ebp = sec no name
0101F0D3 8B6D 08 MOV EBP,DWORD PTR SS:[EBP+8] ; ebp = 刚才申请的空间
0101F0D6 8B4A FC MOV ECX,DWORD PTR DS:[EDX-4] ; ecx = 大小
0101F0D9 8BFD MOV EDI,EBP
0101F0DB 52 PUSH EDX
0101F0DC F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>; 复制到缓冲区里
0101F0DE 8BF5 MOV ESI,EBP ; esi = 那个缓冲
0101F0E0 8B7A F4 MOV EDI,DWORD PTR DS:[EDX-C] ; edi = 元素3
0101F0E3 03F8 ADD EDI,EAX
0101F0E5 EB 28 JMP SHORT calc.0101F10F ; .........................................................................
这里的EDX指向一个0x14字节的数据结构,源程序中有几个节就有几个这个结构,可见它和程序的解压过程密切相关。
因为过程十分复杂,并没有完全知道这个结构的具体意思。
大致是这样的。
struct{
PBYTE ptData1; //保存了一些数据
DWORD dwMagic; //控制了解压过程的一个数
DWORD unKnown;
PBYTE ptData2; //与ptData1共同进行解压过程
DWORD dwSize; //ptData1的数据大小
}
说实话解压过程没怎么看明白,各数据结构与各段的对应关系也每太搞清楚。
不过好在这并不影响我们脱壳。
在解压过程中由于程序只有三个刚才提到得struct,而程序却一直解压,到第四个的时候终于如愿的抛出了一场。
一场出现在这一句。
0101F137 A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
因为刚才安装了SEH,所以我们在那上面下断点。bp 01014A00 然后 shift + f9
来到了这里
01014A54 33C0 XOR EAX,EAX
01014A56 5E POP ESI ; esi = 刚才的返回地址
01014A57 64:8B18 MOV EBX,DWORD PTR FS:[EAX]
01014A5A 8B1B MOV EBX,DWORD PTR DS:[EBX]
01014A5C 8D63 D6 LEA ESP,DWORD PTR DS:[EBX-2A]
01014A5F 5D POP EBP
01014A60 8D8E CB020000 LEA ECX,DWORD PTR DS:[ESI+2CB]
01014A66 894B 04 MOV DWORD PTR DS:[EBX+4],ECX
01014A69 64:891D 0000000>MOV DWORD PTR FS:[0],EBX ; SEH handler = 01014CD0
01014A70 8B3C24 MOV EDI,DWORD PTR SS:[ESP] ; edi = sec no name
01014A73 FF77 08 PUSH DWORD PTR DS:[EDI+8]
01014A76 FF95 A0070000 CALL DWORD PTR SS:[EBP+7A0] ; 释放了缓冲区
这段代码释放了刚才申请的缓冲。
而且又看到了暗桩。又是个SEH,指向01014CD0
往下看不远处JMP EAX
到这里时EAX的值为0,于是又是异常。在01014CD0下断,忽略异常。来到这里
01014CD0 33C0 XOR EAX,EAX ; SEH2
01014CD2 64:8B18 MOV EBX,DWORD PTR FS:[EAX]
01014CD5 8B1B MOV EBX,DWORD PTR DS:[EBX] ; ebx = 下一个SEH
01014CD7 8D63 AE LEA ESP,DWORD PTR DS:[EBX-52]
01014CDA 61 POPAD
01014CDB 833E 00 CMP DWORD PTR DS:[ESI],0 ; esi是0x14字节的结构
01014CDE ^ 0F84 B6FDFFFF JE calc.01014A9A
01014CE4 8B7E 08 MOV EDI,DWORD PTR DS:[ESI+8] ; edi = 元素3(offset)
01014CE7 03FD ADD EDI,EBP
01014CE9 8B4E 0C MOV ECX,DWORD PTR DS:[ESI+C] ; ecx = 元素4
01014CEC D1F9 SAR ECX,1
01014CEE 51 PUSH ECX
01014CEF 72 15 JB SHORT calc.01014D06
01014CF1 037E 04 ADD EDI,DWORD PTR DS:[ESI+4] ; edi + magicnum ???
01014CF4 C1F9 02 SAR ECX,2
这段代码我没有仔细读。
不过通过对GetProcAddress下断,知道它包含了修复IAT的部分。
往后有一个
01014D51 E8 2FE87C07 CALL 087E3585
进入以后
0101F03D 5F POP EDI ; calc.01014D56
0101F03E F3:AA REP STOS BYTE PTR ES:[EDI]
0101F040 61 POPAD
0101F041 66:9D POPFW
0101F043 83C4 08 ADD ESP,8
0101F046 >- E9 2A34FFFF JMP calc.01012475 ; eax = sec code(no name)
最后一个 JMP跳转到OEP。
可以发现这个OEP也是我们程序本身的入口地址,看来壳中还包含了SMC。具体流在以后分析吧。
JMP后便可以OllyDump了,可以正常运行。
--------------------------------------------------------------------------------
【经验总结】
这个壳调的不是很清楚,有许多地方还莫名奇妙。
不过这是我第一次遇到真正的SEH,而且这里面有自修改代码,OD F2下断时,要修改内存指令。所以如果下早了,断点就会
被SMC覆盖掉,使断点失效甚至使代码解密过程出错。所以在产生异常时再下断会更加保险。
--------------------------------------------------------------------------------
2009年09月03日 11:30:18
- 标 题:Petite脱壳详解
- 作 者:cradiator
- 时 间:2009-09-03 15:11:22
- 链 接:http://bbs.pediy.com/showthread.php?t=97055