菜鸟脱壳之脱壳的基础知识(三)——寻找OEP
这节我们来讲讲如何寻找一个程序的OEP,即Original Entry Point。一些PE加壳程序在被加密的程序上面加了一个区段(有的壳也会合并区段),当外壳代码执行完毕以后,会跳到程序的本身的代码来执行,所以我们可以依靠跨区段的转移指令来寻找程序的入口点。
我们来看看加壳之前的Delphi7.0的程序,用LordPE来打开Delphi7.0程序,我们看到程序的入口点是004C498:
看区段,没有任何的新加的区块:
我们来看看加了壳的程序的入口点,加过壳的入口点为000629D0:
区段变为三个了,很明显,壳将原程序的区段给合并了:
加了壳后,首先,各个区段都要被系统映射到内存中,因为现在的入口点是000629D0,是指向外壳部分的,外壳拿到了控制权以后,通过LoadLibrary、GetProcaddresss、GetModuleHandle等函数来获得自身所需要的API的地址,来解密各个区段的信息,填充好IAT后,就要跳到程序的OEP了(Entry Point),此例是004C498,我们用Ollydbg载入,设置好各个选项(我是把暂停点停在了WinMain处了)。
Ollydbg暂停以后,加壳程序停在了004629D0处:
004629D0 > 60 pushad //保存现场(pushad 相当于 push 所有的寄存器)
004629D1 BE 00F04300 mov esi, 0043F000 //把代码段放到esi寄存器
004629D6 8DBE 0020FCFF lea edi, dword ptr [esi+FFFC2000] //得到基址
004629DC C787 9CC00400 7>mov dword ptr [edi+4C09C], 46CD167B//将第一个函数的地址放到[edi+ 4C09C]
004629E6 57 push edi //将基址压栈
004629E7 83CD FF or ebp, FFFFFFFF //将0012FFC0与 FFFFFFFF或
004629EA EB 0E jmp short 004629FA
004629EC 90 nop
004629ED 90 nop
004629EE 90 nop
004629EF 90 nop
004629F0 8A06 mov al, byte ptr [esi] //取出0043F004的一个字节
004629F2 46 inc esi //指向下一个字节
004629F3 8807 mov byte ptr [edi], al //从00401000开始,开始还原代码
004629F5 47 inc edi //指向下一个地址
004629F6 01DB add ebx, ebx //ebx + ebx,当ebx不等于零的时候跳转,下面的adc如果为,就取出下一个地址,并放到ebx中
004629F8 75 07 jnz short 00462A01
004629FA 8B1E mov ebx, dword ptr [esi] //将0043F000放到ebx中
004629FC 83EE FC sub esi, -4 //0043F000加4
004629FF 11DB adc ebx, ebx //进位加法器
00462A01 ^ 72 ED jb short 004629F0 // 向上跳转,ebx做为是否回跳的标志,循环处理代码
00462A03 B8 01000000 mov eax, 1 // eax = 1
00462A08 01DB add ebx, ebx // ebx依然作为循环的标志
00462A0A 75 07 jnz short 00462A13
00462A0C 8B1E mov ebx, dword ptr [esi] //esi指向的地址放到ebx里面
00462A0E 83EE FC sub esi, -4 //esi + 4
00462A11 11DB adc ebx, ebx//进位加法
00462A13 11C0 adc eax, eax //进位加法
00462A15 01DB add ebx, ebx //ebx + ebx
00462A17 73 0B jnb short 00462A24
00462A19 75 28 jnz short 00462A43 //跳到下面
00462A1B 8B1E mov ebx, dword ptr [esi]
00462A1D 83EE FC sub esi, -4
00462A20 11DB adc ebx, ebx
00462A22 72 1F jb short 00462A43
00462A24 48 dec eax
00462A25 01DB add ebx, ebx
00462A27 75 07 jnz short 00462A30
00462A29 8B1E mov ebx, dword ptr [esi]
00462A2B 83EE FC sub esi, -4
00462A2E 11DB adc ebx, ebx
00462A30 11C0 adc eax, eax
00462A32 ^ EB D4 jmp short 00462A08
00462A34 01DB add ebx, ebx
00462A36 75 07 jnz short 00462A3F
00462A38 8B1E mov ebx, dword ptr [esi]
00462A3A 83EE FC sub esi, -4
00462A3D 11DB adc ebx, ebx
00462A3F 11C9 adc ecx, ecx
00462A41 EB 52 jmp short 00462A95
00462A43 31C9 xor ecx, ecx // 清零ecx
00462A45 83E8 03 sub eax, 3 // eax - 3
00462A48 72 11 jb short 00462A5B
00462A4A C1E0 08 shl eax, 8
00462A4D 8A06 mov al, byte ptr [esi]
00462A4F 46 inc esi
00462A50 83F0 FF xor eax, FFFFFFFF
00462A53 74 75 je short 00462ACA
00462A55 D1F8 sar eax, 1
00462A57 89C5 mov ebp, eax
00462A59 EB 0B jmp short 00462A66
00462A5B 01DB add ebx, ebx
00462A5D 75 07 jnz short 00462A66
00462A5F 8B1E mov ebx, dword ptr [esi]
00462A61 83EE FC sub esi, -4
00462A64 11DB adc ebx, ebx
00462A66 ^ 72 CC jb short 00462A34
00462A68 41 inc ecx
00462A69 01DB add ebx, ebx
00462A6B 75 07 jnz short 00462A74
00462A6D 8B1E mov ebx, dword ptr [esi]
00462A6F 83EE FC sub esi, -4
00462A72 11DB adc ebx, ebx
00462A74 ^ 72 BE jb short 00462A34
00462A76 01DB add ebx, ebx
00462A78 75 07 jnz short 00462A81
00462A7A 8B1E mov ebx, dword ptr [esi]
00462A7C 83EE FC sub esi, -4
00462A7F 11DB adc ebx, ebx
00462A81 11C9 adc ecx, ecx
00462A83 01DB add ebx, ebx
00462A85 ^ 73 EF jnb short 00462A76
00462A87 75 09 jnz short 00462A92
00462A89 8B1E mov ebx, dword ptr [esi]
00462A8B 83EE FC sub esi, -4
00462A8E 11DB adc ebx, ebx
00462A90 ^ 73 E4 jnb short 00462A76
00462A92 83C1 02 add ecx, 2
00462A95 81FD 00FBFFFF cmp ebp, -500 //迷惑指令
00462A9B 83D1 02 adc ecx, 2// 进位加法
00462A9E 8D142F lea edx, dword ptr [edi+ebp] // edi + ebp的地址装载到edx,即原来的代码段的地址
00462AA1 83FD FC cmp ebp, -4 // 判断跳转标志,EBP小于等于-4就跳
00462AA4 76 0E jbe short 00462AB4
00462AA6 8A02 mov al, byte ptr [edx] //取出代码段的一字节
00462AA8 42 inc edx //指向下一个地址
00462AA9 8807 mov byte ptr [edi], al //取出的代码放到edi里面
00462AAB 47 inc edi //指向下一个代码
00462AAC 49 dec ecx //计数器
00462AAD ^ 75 F7 jnz short 00462AA6 //关于计数器(ecx)的跳转
00462AAF ^ E9 42FFFFFF jmp 004629F6 //向上面跳,跳到add ebx,ebx
00462AB4 8B02 mov eax, dword ptr [edx] // 处理输入表
00462AB6 83C2 04 add edx, 4 // edx + 4,指向下一个地址
00462AB9 8907 mov dword ptr [edi], eax //将代码放到edi
00462ABB 83C7 04 add edi, 4// edi + 4, 存放代码的地址
00462ABE 83E9 04 sub ecx, 4//ecx - 4
00462AC1 ^ 77 F1 ja short 00462AB4
00462AC3 01CF add edi, ecx // edi + ecx,指向接收代码的地址的最后一个字节
00462AC5 ^ E9 2CFFFFFF jmp 004629F6 //跳到 add ebx,ebx
00462ACA 5E pop esi
00462ACB 89F7 mov edi, esi
00462ACD B9 81260000 mov ecx, 2681
00462AD2 8A07 mov al, byte ptr [edi] //指向我们原来代码段的代码,取出到AL里面
00462AD4 47 inc edi //指向下一个字节
00462AD5 2C E8 sub al, 0E8 //处理CALL
00462AD7 3C 01 cmp al, 1 //判断al是否大于1
00462AD9 ^ 77 F7 ja short 00462AD2 //循环,到下一个CALL的第一个字节为止
00462ADB 803F 14 cmp byte ptr [edi], 14
00462ADE ^ 75 F2 jnz short 00462AD2
00462AE0 8B07 mov eax, dword ptr [edi] //取出里面的地址,里面的地址是定位CALL的绝对地址要用到的
00462AE2 8A5F 04 mov bl, byte ptr [edi+4] //得到下条地址的开始字节放到AL里面,CALL绝对地址就是下条指令开始+刚才上面取出的那个数字
00462AE5 66:C1E8 08 shr ax, 8 // ax右移8位
00462AE9 C1C0 10 rol eax, 10 //eax算术左移 8位
00462AEC 86C4 xchg ah, al //交换内容
00462AEE 29F8 sub eax, edi //eax - edi
00462AF0 80EB E8 sub bl, 0E8 //再减去E8
00462AF3 01F0 add eax, esi //eax + esi,其中 esi是代码段开始的地方
00462AF5 8907 mov dword ptr [edi], eax //这里处理CALL的地址,算出CALL的偏移到EDI里面
00462AF7 83C7 05 add edi, 5 //edi + 5,指向call的后面
00462AFA 88D8 mov al, bl //bl的内容放到al中
00462AFC ^ E2 D9 loopd short 00462AD7 //循环处理CALL,其中ecx作为计数器
00462AFE 8DBE 00F00500 lea edi, dword ptr [esi+5F000] //代码段的起始地址 + 5F000
00462B04 8B07 mov eax, dword ptr [edi] //现在EDI指向我们的代码的输入表
00462B06 09C0 or eax, eax //eax 或 eax ,判断eax是否为零
00462B08 74 3C je short 00462B46
00462B0A 8B5F 04 mov ebx, dword ptr [edi+4] //取得这个地址的数据放到ebx
00462B0D 8D8430 AC2D0600 lea eax, dword ptr [eax+esi+62DAC] // 取得外壳段的KERNEL32.DLL的地址放eax
00462B14 01F3 add ebx, esi //我们代码段的起始地址加上刚才取出的那个数据
00462B16 50 push eax //kernel32.dll的地址
00462B17 83C7 08 add edi, 8 //edi + 8
00462B1A FF96 4C2E0600 call dword ptr [esi+62E4C] //装载kernel32.dll
00462B20 95 xchg eax, ebp //交换数据,即eax指向kernel32.dll的地址
00462B21 8A07 mov al, byte ptr [edi] //取得现在的EDI的地址指向的数据放到AL
00462B23 47 inc edi //指向下一个函
00462B24 08C0 or al, al //al 或 al,判断al是否为零
00462B26 ^ 74 DC je short 00462B04
00462B28 89F9 mov ecx, edi //取出的函数的名字放到ecx里面
00462B2A 57 push edi //函数名字压栈
00462B2B 48 dec eax //eax - 1
00462B2C F2:AE repne scas byte ptr es:[edi]
00462B2E 55 push ebp //kernel32.dll的基址
00462B2F FF96 502E0600 call dword ptr [esi+62E50] //外壳的GetProcaddress
00462B35 09C0 or eax, eax //eax或eax,得到函数的地址
00462B37 74 07 je short 00462B40
00462B39 8903 mov dword ptr [ebx], eax //处理输入表
00462B3B 83C3 04 add ebx, 4 //ebx + 4,指向下一个输入表的地址
00462B3E ^ EB E1 jmp short 00462B21
00462B40 FF96 602E0600 call dword ptr [esi+62E60]
00462B46 8BAE 542E0600 mov ebp, dword ptr [esi+62E54] //VirtualProtect的地址放到ebp
00462B4C 8DBE 00F0FFFF lea edi, dword ptr [esi-1000] //指向PE头,即映像基址
00462B52 BB 00100000 mov ebx, 1000 //把1000放到ebx,即ebx = 1000
00462B57 50 push eax
00462B58 54 push esp
00462B59 6A 04 push 4
00462B5B 53 push ebx
00462B5C 57 push edi
00462B5D FFD5 call ebp //改变属性
00462B5F 8D87 1F020000 lea eax, dword ptr [edi+21F] //现在eax指向PE头中区段的偏移起始位置
00462B65 8020 7F and byte ptr [eax], 7F //改写区段名字
00462B68 8060 28 7F and byte ptr [eax+28], 7F //改写区块属性第一个区块的属性
00462B6C 58 pop eax
00462B6D 50 push eax
00462B6E 54 push esp
00462B6F 50 push eax
00462B70 53 push ebx
00462B71 57 push edi
00462B72 FFD5 call ebp
00462B74 58 pop eax
00462B75 61 popad //恢复现场
00462B76 8D4424 80 lea eax, dword ptr [esp-80]
00462B7A 6A 00 push 0
00462B7C 39C4 cmp esp, eax
00462B7E ^ 75 FA jnz short 00462B7A
00462B80 83EC 80 sub esp, -80
00462B83 ^ E9 109FFEFF jmp 0044CA98 //跨区段的转移,跳到OEP
00462B88 A0 2B4600B0 mov al, byte ptr [B000462B]
00462B8D 2B46 00 sub eax, dword ptr [esi]
00462B90 9C pushfd
Delphi7.0的OEP:
0044CA98 55 push ebp
0044CA99 8BEC mov ebp, esp
0044CA9B 83C4 F0 add esp, -10
0044CA9E B8 B8C84400 mov eax, 0044C8B8
0044CAA3 E8 2091FBFF call 00405BC8
0044CAA8 A1 B8DF4400 mov eax, dword ptr [44DFB8]
0044CAAD 8B00 mov eax, dword ptr [eax]
0044CAAF E8 9CE6FFFF call 0044B150
这个方法很简单,就是从壳的开始一直跟踪,直到来到OEP,没有什么技巧!大家应该熟悉各个程序的OEP,并且熟练的掌握这种方法!
完整版文档:
http://www.2cto.com/uploadfile/2012/1202/20121202073112522.zip