菜鸟脱壳之脱壳的基础知识(三)——寻找OEP

时间:2023-03-08 17:31:21

这节我们来讲讲如何寻找一个程序的OEP,即Original Entry Point。一些PE加壳程序在被加密的程序上面加了一个区段(有的壳也会合并区段),当外壳代码执行完毕以后,会跳到程序的本身的代码来执行,所以我们可以依靠跨区段的转移指令来寻找程序的入口点。
我们来看看加壳之前的Delphi7.0的程序,用LordPE来打开Delphi7.0程序,我们看到程序的入口点是004C498:
菜鸟脱壳之脱壳的基础知识(三)——寻找OEP
看区段,没有任何的新加的区块:
    菜鸟脱壳之脱壳的基础知识(三)——寻找OEP 
我们来看看加了壳的程序的入口点,加过壳的入口点为000629D0:
菜鸟脱壳之脱壳的基础知识(三)——寻找OEP
区段变为三个了,很明显,壳将原程序的区段给合并了:
菜鸟脱壳之脱壳的基础知识(三)——寻找OEP
加了壳后,首先,各个区段都要被系统映射到内存中,因为现在的入口点是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