HadgeROL 发表于 2014-10-2 14:21

UPX脱壳全过程分析

本帖最后由 Blackk 于 2014-11-29 22:06 编辑

小菜也只能拿这样简单的壳来练手啦 ^ ^。
简单说下个人认为的UPX壳解码时的流程,然后我就按流程一点一点写吧。

   1.先初始化
   2.进行代码还原
   3.进行CALL修复
   4.进行函数表还原
   5.节表初始化
   6.解码完毕飞向程序入口
第一步:初始化,这个好像没什么好解释的呃,看代码。
0049B4D0 > $60               pushad                                        ;//保存现场 //寄存器入栈 //ESP=12FFC4
0049B4D1   .BE 00204600      mov   esi, 00462000                         ;//ESI指向UPX1区段 //ESI=462000 //ESP=12FFA4
0049B4D6   .8DBE 00F0F9FF      lea   edi, dword ptr        ;//指向要还原的首地址UPX0区段 //EDI=401000
0049B4DC   .57               push    edi                                 ;//首地址入栈
0049B4DD   .83CD FF            or      ebp, 0xFFFFFFFF                     ;//初始化EBP //EBP用来判断第二次循环还原方式
0049B4E0   .EB 10            jmp   short 0049B4F2                        ;//初始化完毕


第二步:代码还原。

    接下来UPX壳进行代码还原,还原的方式我个人分为两种。
    一种是UPX壳无法压缩的代码,UPX老老实实的从UPX1区段将代码还原到UPX0区段;
    另一种就是UPX0中的重复指令,UPX壳根据UPX1中的KEY值找到UPX0中重复指令的位置,再进行的代码还原。具体过程如下:
0049B4E8   > /8A06               mov   al, byte ptr                   ;//依次取UPX1区段的数据进行还原
0049B4EA   . |46               inc   esi                                 ;//指向下一个要还原的数据 //ESI=ESI+1
0049B4EB   . |8807               mov   byte ptr , al                  ;//还原数据
0049B4ED   . |47               inc   edi                                 ;//EDI=EDI+1
0049B4EE   > |01DB               add   ebx, ebx                              ;//用EBX做判断,是否继续还原
0049B4F0   . |75 07            jnz   short 0049B4F9
0049B4F2   > |8B1E               mov   ebx, dword ptr                   ;//取循环变量KEY //存放在EBX=0xFF36DEEF
0049B4F4   . |83EE FC            sub   esi, -0x4                           ;//ESI=ESI+4
0049B4F7   . |11DB               adc   ebx, ebx                              ;//用EBX做判断,是否开始进行还原代码操作
0049B4F9   >^\72 ED            jb      short 0049B4E8
0049B4FB   .B8 01000000      mov   eax, 0x1                              ;//EAX=1
0049B500   >01DB               add   ebx, ebx
0049B502   .75 07            jnz   short 0049B50B
0049B504   .8B1E               mov   ebx, dword ptr
0049B506   .83EE FC            sub   esi, -0x4
0049B509   .11DB               adc   ebx, ebx
0049B50B   >11C0               adc   eax, eax                              ;//使用EAX判断还原方式
0049B50D   .01DB               add   ebx, ebx
0049B50F   .73 0B            jnb   short 0049B51C
0049B511   .75 28            jnz   short 0049B53B
0049B513   .8B1E               mov   ebx, dword ptr
0049B515   .83EE FC            sub   esi, -0x4
0049B518   .11DB               adc   ebx, ebx
0049B51A   .72 1F            jb      short 0049B53B
0049B51C   >48               dec   eax
0049B51D   .01DB               add   ebx, ebx
0049B51F   .75 07            jnz   short 0049B528
0049B521   .8B1E               mov   ebx, dword ptr
0049B523   .83EE FC            sub   esi, -0x4
0049B526   .11DB               adc   ebx, ebx
0049B528   >11C0               adc   eax, eax
0049B52A   .^ EB D4            jmp   short 0049B500
0049B52C   >01DB               add   ebx, ebx
0049B52E   .75 07            jnz   short 0049B537
0049B530   .8B1E               mov   ebx, dword ptr
0049B532   .83EE FC            sub   esi, -0x4
0049B535   .11DB               adc   ebx, ebx
0049B537   >11C9               adc   ecx, ecx
0049B539   .EB 52            jmp   short 0049B58D
0049B53B   >31C9               xor   ecx, ecx                              ;//循环变量清零 //ECX=0
0049B53D   .83E8 03            sub   eax, 0x3                              ;//EAX=0xFFFFFFFF为单字节代码还原 //EAX!=0xFFFFFFFF为多字节还原代码
0049B540   .72 11            jb      short 0049B553
0049B542   .C1E0 08            shl   eax, 0x8                              ;//EAX左移8位 //EAX清零
0049B545   .8A06               mov   al, byte ptr                   ;//取多字节循环还原地址KEY
0049B547   .46               inc   esi                                 ;//ESI+1
0049B548   .83F0 FF            xor   eax, 0xFFFFFFFF                     ;//EAX和0xFFFFFFFF异或 //循环出口
0049B54B   .74 75            je      short 0049B5C2
0049B54D   .D1F8               sar   eax, 1                              ;//EAX右移一位
0049B54F   .89C5               mov   ebp, eax                              ;//EBP=EAX //EBP的值为:~(KEY/2)
0049B551   .EB 0B            jmp   short 0049B55E
0049B553   >01DB               add   ebx, ebx
0049B555   .75 07            jnz   short 0049B55E
0049B557   .8B1E               mov   ebx, dword ptr
0049B559   .83EE FC            sub   esi, -0x4
0049B55C   .11DB               adc   ebx, ebx
0049B55E   >^ 72 CC            jb      short 0049B52C
0049B560   .41               inc   ecx
0049B561   .01DB               add   ebx, ebx
0049B563   .75 07            jnz   short 0049B56C
0049B565   .8B1E               mov   ebx, dword ptr
0049B567   .83EE FC            sub   esi, -0x4
0049B56A   .11DB               adc   ebx, ebx
0049B56C   >^ 72 BE            jb      short 0049B52C
0049B56E   >01DB               add   ebx, ebx
0049B570   .75 07            jnz   short 0049B579
0049B572   .8B1E               mov   ebx, dword ptr
0049B574   .83EE FC            sub   esi, -0x4
0049B577   .11DB               adc   ebx, ebx
0049B579   >11C9               adc   ecx, ecx
0049B57B   .01DB               add   ebx, ebx
0049B57D   .^ 73 EF            jnb   short 0049B56E
0049B57F   .75 09            jnz   short 0049B58A
0049B581   .8B1E               mov   ebx, dword ptr
0049B583   .83EE FC            sub   esi, -0x4
0049B586   .11DB               adc   ebx, ebx
0049B588   .^ 73 E4            jnb   short 0049B56E
0049B58A   >83C1 02            add   ecx, 0x2                              ;//循环变量递增
0049B58D   >81FD 00FBFFFF      cmp   ebp, -0x500
0049B593   .83D1 02            adc   ecx, 0x2                              ;//循环变量递增
0049B596   .8D142F             lea   edx, dword ptr             ;//以EDI作为代码还原的基地址,用EBP指向已还原的代码,将相同代码进行还原
0049B599   .83FD FC            cmp   ebp, -0x4                           ;//再次判断循环还原的方式
0049B59C   .76 0E            jbe   short 0049B5AC                        ;//如果还原处和解码处相距距离小于4个字节则进行单字节还原
0049B59E   >8A02               mov   al, byte ptr                   ;//相同字节单字节还原循环
0049B5A0   .42               inc   edx                                 ;//EDX+1
0049B5A1   .8807               mov   byte ptr , al                  ;//循环还原
0049B5A3   .47               inc   edi                                 ;//EDI+1
0049B5A4   .49               dec   ecx                                 ;//判断循环是否结束 //ECX
0049B5A5   .^ 75 F7            jnz   short 0049B59E
0049B5A7   .^ E9 42FFFFFF      jmp   0049B4EE
0049B5AC   >8B02               mov   eax, dword ptr                   ;//多字节循环还原
0049B5AE   .83C2 04            add   edx, 0x4
0049B5B1   .8907               mov   dword ptr , eax
0049B5B3   .83C7 04            add   edi, 0x4
0049B5B6   .83E9 04            sub   ecx, 0x4                              ;//判断循环是否结束 //ECX
0049B5B9   .^ 77 F1            ja      short 0049B5AC
0049B5BB   .01CF               add   edi, ecx
0049B5BD   .^ E9 2CFFFFFF      jmp   0049B4EE
0049B5C2   >5E               pop   esi                                 ;//还原完毕 //首地址出栈入ESI


    整个过程中,ECX一般作为循环变量,EBX是绝对的王者,它掌控整个还原的节奏。
    关于EBX是怎样计算出来的,现在技术不够逆不出来。
    所以现在分析的都只不过是皮毛,UPX壳压缩的核心还是没能出来。

第三步:CALL修复
0049B5C3   .89F7               mov   edi, esi
0049B5C5   .B9 73250000      mov   ecx, 0x2573                           ;//循环变量 //ECX=0x2573
0049B5CA   >8A07               mov   al, byte ptr
0049B5CC   .47               inc   edi
0049B5CD   .2C E8            sub   al, 0xE8
0049B5CF   >3C 01            cmp   al, 0x1                               ;//判断是否为0xE8
0049B5D1   .^ 77 F7            ja      short 0049B5CA
0049B5D3   .803F 13            cmp   byte ptr , 0x13                  ;//判断0xE8后面是否为0x13 //判断是否为CALL
0049B5D6   .^ 75 F2            jnz   short 0049B5CA
0049B5D8   .8B07               mov   eax, dword ptr
0049B5DA   .8A5F 04            mov   bl, byte ptr                 ;//使用BL判断下一个指令是否是CALL
0049B5DD   .66:C1E8 08         shr   ax, 0x8                               ;//AX右移8位 //AL清零,AL=AH
0049B5E1   .C1C0 10            rol   eax, 0x10                           ;//EAX左移循环
0049B5E4   .86C4               xchg    ah, al                              ;//AH和AL交换
0049B5E6   .29F8               sub   eax, edi
0049B5E8   .80EB E8            sub   bl, 0xE8
0049B5EB   .01F0               add   eax, esi
0049B5ED   .8907               mov   dword ptr , eax                  ;//CALL地址还原
0049B5EF   .83C7 05            add   edi, 0x5
0049B5F2   .88D8               mov   al, bl
0049B5F4   .^ E2 D9            loopd   short 0049B5CF                        ;//循环还原CALL地址


    关于CALL修复。在第二步代码还原时已经把CALL的特征码还原了,这里只是对CALL的地址做进一步的修正。
    它的算法我还是有点不明白。我看到在还原代码时,CALL后面的四个字节标志了CALL地址KEY,通过上面的CALL算法将KEY还原成正确地址。请问这个将KEY还原的代码用高级语言应该怎样表示呢?


第四步:函数表还原,没什么好解释的,看代码。
0049B5F6    8DBE 00800900      lea   edi, dword ptr           ; //获取函数表还原基地址
0049B5FC    8B07               mov   eax, dword ptr                   ; //获取函数表还原偏移
0049B5FE    09C0               or      eax, eax
0049B600    74 45                je      short 0049B647                        ; //函数表还原完了进入下一个模块的跳转
0049B602    8B5F 04            mov   ebx, dword ptr             ; //获取函数存放位置偏移
0049B605    8D8430 B0C80900      lea   eax, dword ptr       ; //获取函数所在DLL名称
0049B60C    01F3               add   ebx, esi
0049B60E    50                   push    eax
0049B60F    83C7 08            add   edi, 0x8
0049B612    FF96 B4C90900      call    dword ptr                ; //载入DLL到内存
0049B618    95                   xchg    eax, ebp
0049B619    8A07               mov   al, byte ptr
0049B61B    47                   inc   edi
0049B61C    08C0               or      al, al
0049B61E^ 74 DC                je      short 0049B5FC                        ; //还原DLL后进入下一个DLL的还原跳转
0049B620    89F9               mov   ecx, edi
0049B622    79 07                jns   short 0049B62B
0049B624    0FB707               movzx   eax, word ptr
0049B627    47                   inc   edi
0049B628    50                   push    eax
0049B629    47                   inc   edi
0049B62A    B9 5748F2AE          mov   ecx, 0xAEF24857
0049B62F    55                   push    ebp
0049B630    FF96 B8C90900      call    dword ptr                ; //获取函数所在地址
0049B636    09C0               or      eax, eax
0049B638    74 07                je      short 0049B641
0049B63A    8903               mov   dword ptr , eax                  ; //进行函数表地址还原
0049B63C    83C3 04            add   ebx, 0x4
0049B63F^ EB D8                jmp   short 0049B619                        ; //循环还原函数表


第五步:节表初始化,看代码。
0049B641    FF96 C8C90900      call    dword ptr
0049B647    8BAE BCC90900      mov   ebp, dword ptr           ; //指向VirtualProtect函数地址
0049B64D    8DBE 00F0FFFF      lea   edi, dword ptr          ; //指向文件头位置
0049B653    BB 00100000          mov   ebx, 0x1000                           ; //文件头大小
0049B658    50                   push    eax
0049B659    54                   push    esp
0049B65A    6A 04                push    0x4                                 ; //PAGE_EXECUTE_READWRITE
0049B65C    53                   push    ebx
0049B65D    57                   push    edi
0049B65E    FFD5               call    ebp                                 ; 使用VirtualProtect把文件头设置为PAGE_EXECUTE_READWRITE,获得文件头的写权限
0049B660    8D87 0F020000      lea   eax, dword ptr
0049B666    8020 7F            and   byte ptr , 0x7F
0049B669    8060 28 7F         and   byte ptr , 0x7F             ; //完成节表的初始化
0049B66D    58                   pop   eax                                 
0049B66E    50                   push    eax
0049B66F    54                   push    esp
0049B670    50                   push    eax
0049B671    53                   push    ebx
0049B672    57                   push    edi
0049B673    FFD5               call    ebp                                 ; //用VirtualProtect恢复文件头属性


最后:在0x0049B684飞向程序入口。
0049B675    58                   pop   eax
0049B676    61                   popad
0049B677    8D4424 80            lea   eax, dword ptr
0049B67B    6A 00                push    0x0
0049B67D    39C4               cmp   esp, eax
0049B67F^ 75 FA                jnz   short 0049B67B
0049B681    83EC 80            sub   esp, -0x80
0049B684- E9 C89AFAFF          jmp   00445151


至此UPX壳解码分析完毕了。第一次逆向壳,写下文章留个记录。
以上文章只是个人见解,难免有误,还希望各路友人帮忙指正。


Bad丶Boy 发表于 2014-10-2 15:41

UPX分析都这么多好麻烦 看不懂

游戏而已 发表于 2014-10-2 15:05


不错,大牛作品,必属精品

lwj一辈子 发表于 2014-10-2 15:03

不错,大牛作品,必属精品

hao516677 发表于 2014-10-2 14:45

谢谢你的分析,我来看看

HadgeROL 发表于 2014-10-2 14:22

本帖最后由 Blackk 于 2014-10-2 14:29 编辑

按照国际惯列,沙发自己坐。试炼程序:

520Kelly 发表于 2014-10-2 22:15

大神求VMP脱壳全分析 坐等人肉还原

manbajie 发表于 2014-10-3 08:08

来学习一下的说

Nisy 发表于 2014-10-7 17:15

不错哦~

旋冰 发表于 2014-10-7 19:10

大牛的作品,小白暂时看不透!
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: UPX脱壳全过程分析