飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 22562|回复: 12

[原创] 闲来无事,分析一下UPX壳

  [复制链接]
  • TA的每日心情
    开心
    2015-8-2 16:07
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2015-2-24 15:22:19 | 显示全部楼层 |阅读模式
    本帖最后由 F8LEFT 于 2015-2-24 15:20 编辑

           UPX是公认的一款优秀的压缩壳。无论是兼容性还是压缩率都是非常让人满意的。当然,想用它来保护您的软件大概是不可能的了。今天闲来无事,就顺手分析了一下UPX的壳,就当做学习一下这种壳该如何弄。下面是一点小小的心得,就分享给大家了。
          加壳软件为 UPX309w,目标文件为XP32下的记事本(多次躺枪的软件{:soso_e113:}),保护方式为默认保护,使用的逆向工具为PYG版的OD,以上,下面正式开始。
          首先说一下我的想法,UPX的压缩原理是利用了重复字符的存储方法。比如说,要存储10个a,如aaaaaaaaaa,只需要保存一个a以及它连续出现了10次的状况就可以了。即使不是相邻的,如 aabbbaaccc,那么就要保存a,然后a重复一次,接着在偏移为3的地方重复aa一次。这样来保存文件,将有效的减少文件的大小,这就是UPX的压缩理论。当然实际上,它使用的方法要更为巧妙。


          众所周知,UPX压缩后的文件有两个区段,UPX0,UPX1,其中UPX0是一个空的区段,是原程序用来保存代码的地方。而UPX1则是保存被压缩后的程序数据的地方,这是一个二进制的数据流。UPX的解码代码便是使用该数据来进行解码而得到原本的程序。好了,知道了这些便开始分析吧。
    首先是UPX的入口
    1. 01014260 >  60              PUSHAD                                      ; 保存寄存器
    2. 01014261    BE 00000101     MOV     ESI, notepad_.01010000              ; 取UPX1段地址, ESI <- UPX0
    3. 01014266    8DBE 0010FFFF   LEA     EDI, DWORD PTR DS:[ESI+0xFFFF1000]  ; 取UPX0段地址, EDI <- UPX1
    4. 0101426C    57              PUSH    EDI                                 ; 保存UPX0段到堆栈中
    5. 0101426D    83CD FF         OR      EBP, 0xFFFFFFFF                     ; SET EBP = -1,原地址与目的地址的偏移
    6. 01014270    EB 10           JMP     SHORT notepad_.01014282             ; 跳转到解码代码
    复制代码
    进行了一下代码的初始化以后,便直接跳向了解码代码段
    1. 01014276    90              NOP                                         ; EDI目的地址 EDX源地址 ECX 复制长度 EBX二进制数据流
    2. 01014277    90              NOP                                         ; UPX1段为二进制数据流,根据二进制来进行流程的控制与解码
    3. 01014278    8A06            MOV     AL, BYTE PTR DS:[ESI]               ; 从ESI中取1字节数据到EDI去
    4. 0101427A    46              INC     ESI
    5. 0101427B    8807            MOV     BYTE PTR DS:[EDI], AL
    6. 0101427D    47              INC     EDI
    7. 0101427E >  01DB            ADD     EBX, EBX                            ; 移动EBX的高位到CF标志中去
    8. 01014280    75 07           JNZ     SHORT notepad_.01014289
    9. 01014282    8B1E            MOV     EBX, DWORD PTR DS:[ESI]             ; EBX <- DWORD:UPX1[i]
    10. 01014284    83EE FC         SUB     ESI, -0x4                           ; i++,此时CF = 1,目的是令下面从组EBX时,保证最低位肯定为1,使得流程控制中使用掉EBX的所有位
    11. 01014287    11DB            ADC     EBX, EBX                            ; 重组EBX,CF标志的数值排到EBX的低位,同时EBX的高位排到CF高位
    12. 01014289  ^ 72 ED           JB      SHORT notepad_.01014278             ; 高位为1,跳转,从ESI取1byte到EDI去 (CF标志)
    13. 0101428B    B8 01000000     MOV     EAX, 0x1                            ; 高位不为1,不跳转,给EAX1
    14. 01014290    01DB            ADD     EBX, EBX                            ; EBX高位进CF寄存器
    15. 01014292    75 07           JNZ     SHORT notepad_.0101429B
    16. 01014294    8B1E            MOV     EBX, DWORD PTR DS:[ESI]             ; 取下一组数据
    17. 01014296    83EE FC         SUB     ESI, -0x4
    18. 01014299    11DB            ADC     EBX, EBX
    19. 0101429B    11C0            ADC     EAX, EAX                            ; CF标志位左移进EAX
    20. 0101429D    01DB            ADD     EBX, EBX                            ; EBX高位进CF标志位中
    21. 0101429F  ^ 73 EF           JNB     SHORT notepad_.01014290             ; CF为0,跳转,继续移动EBX高位到EAX中,CF为1,不跳转,进行下一步
    22. 010142A1    75 09           JNZ     SHORT notepad_.010142AC
    23. 010142A3    8B1E            MOV     EBX, DWORD PTR DS:[ESI]
    24. 010142A5    83EE FC         SUB     ESI, -0x4
    25. 010142A8    11DB            ADC     EBX, EBX
    26. 010142AA  ^ 73 E4           JNB     SHORT notepad_.01014290
    27. 010142AC    31C9            XOR     ECX, ECX                            ; 初始化ECX = 0 (计算得到CopyLen)
    28. 010142AE    83E8 03         SUB     EAX, 0x3
    29. 010142B1    72 0D           JB      SHORT notepad_.010142C0             ; 如果EAX >= 11,不跳转,(流程分派)
    30. 010142B3    C1E0 08         SHL     EAX, 0x8                            ; EAX <<= 8
    31. 010142B6    8A06            MOV     AL, BYTE PTR DS:[ESI]               ; 从ESI中取出byte
    32. 010142B8    46              INC     ESI
    33. 010142B9    83F0 FF         XOR     EAX, 0xFFFFFFFF                     ; XOR 求出源数据与目标数据间的差距(偏移)
    34. 010142BC    74 74           JE      SHORT <notepad_.End Decode>         ; 得到的结果为0时退出解码阶段
    35. 010142BE    89C5            MOV     EBP, EAX                            ;  写入偏移大小
    36. 010142C0    01DB            ADD     EBX, EBX                            ; 取得EBX高位到CF标志
    37. 010142C2    75 07           JNZ     SHORT notepad_.010142CB
    38. 010142C4    8B1E            MOV     EBX, DWORD PTR DS:[ESI]
    39. 010142C6    83EE FC         SUB     ESI, -0x4
    40. 010142C9    11DB            ADC     EBX, EBX
    41. 010142CB    11C9            ADC     ECX, ECX                            ; CF标志左移入ECX中(CopyLen)
    42. 010142CD    01DB            ADD     EBX, EBX                            ; 取得EBX高位到CF标志
    43. 010142CF    75 07           JNZ     SHORT notepad_.010142D8
    44. 010142D1    8B1E            MOV     EBX, DWORD PTR DS:[ESI]
    45. 010142D3    83EE FC         SUB     ESI, -0x4
    46. 010142D6    11DB            ADC     EBX, EBX
    47. 010142D8    11C9            ADC     ECX, ECX                            ; CF标志左移入ECX中(CopyLen),到此总共移动了2位EBX到ECX中,后面进入流程判断
    48. 010142DA    75 20           JNZ     SHORT notepad_.010142FC
    49. 010142DC    41              INC     ECX                                 ; 若ECX为0,则进入该流程,需要额外计算得到ECX的大小并且令ECX = 1
    50. 010142DD    01DB            ADD     EBX, EBX                            ; 将EBX高位进CF标志
    51. 010142DF    75 07           JNZ     SHORT notepad_.010142E8
    52. 010142E1    8B1E            MOV     EBX, DWORD PTR DS:[ESI]
    53. 010142E3    83EE FC         SUB     ESI, -0x4
    54. 010142E6    11DB            ADC     EBX, EBX
    55. 010142E8    11C9            ADC     ECX, ECX                            ; CF标志左移入ECX低位
    56. 010142EA    01DB            ADD     EBX, EBX                            ; EBX高位入CF标志
    57. 010142EC  ^ 73 EF           JNB     SHORT notepad_.010142DD             ; CF为0,跳转,继续移动EBX高位到ECX中,CF为1,不跳转,进入下一个流程
    58. 010142EE    75 09           JNZ     SHORT notepad_.010142F9
    59. 010142F0    8B1E            MOV     EBX, DWORD PTR DS:[ESI]
    60. 010142F2    83EE FC         SUB     ESI, -0x4
    61. 010142F5    11DB            ADC     EBX, EBX
    62. 010142F7  ^ 73 E4           JNB     SHORT notepad_.010142DD
    63. 010142F9    83C1 02         ADD     ECX, 0x2                            ; ECX += 2
    64. 010142FC    81FD 00F3FFFF   CMP     EBP, -0xD00                         ;  比较源数据地址与目标数据地址的偏移
    65. 01014302    83D1 01         ADC     ECX, 0x1
    66. 01014305    8D142F          LEA     EDX, DWORD PTR DS:[EDI+EBP]         ; 取源数据地址
    67. 01014308    83FD FC         CMP     EBP, -0x4                           ;  如果偏移小于4,则需要一个一个字节的复制,否则可以4个字节4个字节的复制(效率问题)
    68. 0101430B    76 0F           JBE     SHORT notepad_.0101431C
    69. 0101430D    8A02            MOV     AL, BYTE PTR DS:[EDX]               ; memcpy((byte*)EDI, (byte*)EDX, (DWORD)ECX)
    70. 0101430F    42              INC     EDX                                 ; 从EDX中复制ECX长度数据到EDI去
    71. 01014310    8807            MOV     BYTE PTR DS:[EDI], AL
    72. 01014312    47              INC     EDI
    73. 01014313    49              DEC     ECX
    74. 01014314  ^ 75 F7           JNZ     SHORT notepad_.0101430D
    75. 01014316  ^ E9 63FFFFFF     JMP     <notepad_.Loop>
    复制代码
         这里,通过对二进制代码流UPX1的处理,解码出原程序代码存放到UPX0中去,汇编代码可能会有点乱,下面给出伪代码:
    1. byte* UPX0,*UPX1;                                //UPX1数据要解码到UPX0中
    2. DWORD iCopyLen(ECX),Dst(EDI) = 0,Src(EDX) = 0;        //复制Src到Est中
    3.        //复制数据后,Dst与Src自动加上iCopyLen的大小。
    4. DWORD pPoint(ESI) = 0;
    5. DWORD Data(EBX);                                //从UPX1中读取的二进制数据流
    6.        // 每当Data取完8个二进制数据时(肯定可以,见①),便会Data = UPX1[pPoint],pPoing += 4;为了方便,后面不再写出
    7.        // 每当Data & 1000 0000B 判断高位数据是否为1,便自动左移一位 即 Data <<= 1,后面也不写出
    8. DWORD ptrNow = -1, ptrNew = 0;                        //源地址与目标地址的偏移
    9. jmp @Loop;
    10. while(true)
    11. {
    12. @newChar:
    13.     memcpy(&UPX0[Dst], &UPX1[pPoint], 1);
    14. @Loop:
    15.     if(Data & 1000 0000B)
    16.         jmp @newChar;
    17.     ptrNew = 1;
    18. @ptrRun:
    19.     ptrNew </ Data;                                //这里假定 </ 表示从Data高位左移到ptrNew的低位中去
    20.     if(Data & 0000 0000B)
    21.         jmp @ptrRun;                                //高位为0时,不断移位
    22.     ptrNew -= 3;
    23.     if(ptrNew >= 0)
    24.     {
    25.         ptrNew <<= 4;
    26.         ptrNew += byte:UPX1[pPoint];
    27.         ptrNew ^= -1;
    28.         if(ptrNew == 0)
    29.             break;
    30.         ptrNow = ptrNew;
    31.     }
    32.     iCopyLen = 0;
    33.     iCopyLen </ Data;
    34.     iCopyLen </ Data;                                //从Data中左移2位到iCopyLen中,0~3
    35.     if(iCopyLen == 0)                                //证明复制的长度大于3,需要从新计算长度
    36.     {
    37. @CopyLenRun:
    38.        iCopyLen = 1;
    39.        iCopyLen </ Data;
    40.        if(Data & 0000 0000B)                        //高位为0时,继续移动高位到iCopyLen中
    41.            jmp @CopyLenRun;
    42.        iCopyLen += 2;
    43.     }
    44.     if(ptrNow > 0xFFFFF300)                        //作用?
    45.        iCopyLen += 1;
    46.     else iCopyLen += 2;
    47.     Src = Dst + ptrNow;
    48.     memcpy(Dst, Src, iCopyLen);
    49. }

    50. PS:
    51. ①EBX的更新的汇编代码
    52. ADD     EBX, EBX                            ; 移动EBX的高位到CF标志中去
    53. JNZ     NEXT
    54. MOV     EBX, DWORD PTR DS:[ESI]             ; EBX <- DWORD:UPX1[i]
    55. SUB     ESI, -0x4                           ; i++,此时CF = 1,目的是令下面从组EBX时,保证最低位肯定为1,使得流程控制中使
    56. 用掉EBX的所有位
    57. ADC     EBX, EBX                            ; 重组EBX,CF标志的数值排到EBX的低位(保证低位为1),同时EBX的高位排到CF高位
    58. NEXT:
    复制代码
           好了,做完解码的工作以后,便是进行call与jmp的重定位,首先是解密程序内的call与jmp
    1. 01014332 >  5E              POP     ESI                                 ; 解密已结束,此时进行从定位
    2. 01014333    89F7            MOV     EDI, ESI                            ; 恢复UPX0的位置, ESI,EDI <- UPX0
    3. 01014335    B9 32010000     MOV     ECX, 0x132                          ;  共有132个函数被变形,修程序内的jmp与call
    4. 0101433A    8A07            MOV     AL, BYTE PTR DS:[EDI]               ; 从代码段中寻找E8,E9的位置
    5. 0101433C    47              INC     EDI                                 ; E8 <- CALL ???
    6. 0101433D    2C E8           SUB     AL, 0xE8                            ; E9 <- jmp
    7. 0101433F    3C 01           CMP     AL, 0x1
    8. 01014341  ^ 77 F7           JA      SHORT notepad_.0101433A
    9. 01014343    803F 01         CMP     BYTE PTR DS:[EDI], 0x1              ; 需要修复的CALL或jmp均被解压为 E9 01 或 E8 01,故需要判断后面是否为01
    10. 01014346  ^ 75 F2           JNZ     SHORT notepad_.0101433A
    11. 01014348    8B07            MOV     EAX, DWORD PTR DS:[EDI]             ; 取4个字节
    12. 0101434A    8A5F 04         MOV     BL, BYTE PTR DS:[EDI+0x4]           ;  取下一个判断位置
    13. 0101434D    66:C1E8 08      SHR     AX, 0x8                             ; AX >>= 8,丢弃低位01
    14. 01014351    C1C0 10         ROL     EAX, 0x10                           ; EAX <<= 10 (ROL),高位变低位
    15. 01014354    86C4            XCHG    AH, AL                              ; XCHG AH,AL -> 总的来说就是把01去掉,EAX整个翻转,求得目标位置距离UPX0起始位置的偏移
    16. 01014356    29F8            SUB     EAX, EDI                            ; 减去当前call的位置
    17. 01014358    80EB E8         SUB     BL, 0xE8                            ;   ->下一轮的判断数据
    18. 0101435B    01F0            ADD     EAX, ESI                            ; 加上UPX0起始位置,求得目标位置距离当前位置的偏移
    19. 0101435D    8907            MOV     DWORD PTR DS:[EDI], EAX             ; 写回
    20. 0101435F    83C7 05         ADD     EDI, 0x5
    21. 01014362    88D8            MOV     AL, BL
    22. 01014364  ^ E2 D9           LOOPD   SHORT notepad_.0101433F             ; 循环
    复制代码
         因为UPX对call与jmp也是存在一定的变形的,故需要进行还原
          然后进行API的重定位。

    1. 01014366    8DBE 00200100   LEA     EDI, DWORD PTR DS:[ESI+0x12000]     ; 从定位API函数
    2. 0101436C    8B07            MOV     EAX, DWORD PTR DS:[EDI]
    3. 0101436E    09C0            OR      EAX, EAX
    4. 01014370    74 3C           JE      SHORT notepad_.010143AE
    5. 01014372    8B5F 04         MOV     EBX, DWORD PTR DS:[EDI+0x4]         ; 取源程序导入表的偏移
    6. 01014375    8D8430 14AE0100 LEA     EAX, DWORD PTR DS:[EAX+ESI+0x1AE14] ; 定位到从定位表中,取dll名
    7. 0101437C    01F3            ADD     EBX, ESI                            ;  + 源程序基址得到导入表地址
    8. 0101437E    50              PUSH    EAX
    9. 0101437F    83C7 08         ADD     EDI, 0x8
    10. 01014382    FF96 DCAE0100   CALL    NEAR DWORD PTR DS:[ESI+0x1AEDC]     ; LoadLibraryA,加载dll
    11. 01014388    95              XCHG    EAX, EBP                            ; 保存dll位置
    12. 01014389    8A07            MOV     AL, BYTE PTR DS:[EDI]
    13. 0101438B    47              INC     EDI
    14. 0101438C    08C0            OR      AL, AL
    15. 0101438E  ^ 74 DC           JE      SHORT notepad_.0101436C
    16. 01014390    89F9            MOV     ECX, EDI                            ; 取函数名
    17. 01014392    57              PUSH    EDI
    18. 01014393    48              DEC     EAX
    19. 01014394    F2:AE           REPNE   SCAS BYTE PTR ES:[EDI]
    20. 01014396    55              PUSH    EBP
    21. 01014397    FF96 E0AE0100   CALL    NEAR DWORD PTR DS:[ESI+0x1AEE0]     ; GetProcAddress,从定位API函数
    22. 0101439D    09C0            OR      EAX, EAX
    23. 0101439F    74 07           JE      SHORT notepad_.010143A8
    24. 010143A1    8903            MOV     DWORD PTR DS:[EBX], EAX             ; 写入原程序的导入表中
    25. 010143A3    83C3 04         ADD     EBX, 0x4
    26. 010143A6  ^ EB E1           JMP     SHORT notepad_.01014389
    复制代码
           非常简单得调用LoadLibraryA与GetProcAddress来进行API重定位,这点相信大家可以很好的处理。        最后便是返回源程序OEP
    1. 010143A8    FF96 F0AE0100   CALL    NEAR DWORD PTR DS:[ESI+0x1AEF0]     ; ExitProcess 发生错误退出程序
    2. 010143AE    8BAE E4AE0100   MOV     EBP, DWORD PTR DS:[ESI+0x1AEE4]
    3. 010143B4    8DBE 00F0FFFF   LEA     EDI, DWORD PTR DS:[ESI-0x1000]
    4. 010143BA    BB 00100000     MOV     EBX, 0x1000
    5. 010143BF    50              PUSH    EAX
    6. 010143C0    54              PUSH    ESP
    7. 010143C1    6A 04           PUSH    0x4
    8. 010143C3    53              PUSH    EBX
    9. 010143C4    57              PUSH    EDI
    10. 010143C5    FFD5            CALL    NEAR EBP                            ; VirtualProtect 新建内存
    11. 010143C7    8D87 FF010000   LEA     EAX, DWORD PTR DS:[EDI+0x1FF]
    12. 010143CD    8020 7F         AND     BYTE PTR DS:[EAX], 0x7F
    13. 010143D0    8060 28 7F      AND     BYTE PTR DS:[EAX+0x28], 0x7F
    14. 010143D4    58              POP     EAX
    15. 010143D5    50              PUSH    EAX
    16. 010143D6    54              PUSH    ESP
    17. 010143D7    50              PUSH    EAX
    18. 010143D8    53              PUSH    EBX
    19. 010143D9    57              PUSH    EDI
    20. 010143DA    FFD5            CALL    NEAR EBP                            ; VirtualProtect
    21. 010143DC    58              POP     EAX
    22. 010143DD    61              POPAD
    23. 010143DE    8D4424 80       LEA     EAX, DWORD PTR SS:[ESP-0x80]
    24. 010143E2    6A 00           PUSH    0x0
    25. 010143E4    39C4            CMP     ESP, EAX
    26. 010143E6  ^ 75 FA           JNZ     SHORT notepad_.010143E2
    27. 010143E8    83EC 80         SUB     ESP, -0x80                          ; 清空堆栈中残留的数据
    28. 010143EB  - E9 AD2FFFFF     JMP     notepad_.0100739D                   ; 跳向源程序OEP
    复制代码
           好了,导出为止这个Nopad的加壳便算是分析完了。可以看到,UPX虽然简单,但却包含了加壳最基本的事情,包括代码变形,代码压缩,API重定位等思想,所以作为学习壳的入门我认为也是非常不错的。附件为加壳程序以及OD的分析,欢迎大家下载。
           因为360报毒,我就加了PYG的默认密码www.chinapyg.com,至于是否真的有毒就看大家的判断吧。

    UPX分析.rar

    77.23 KB, 下载次数: 28, 下载积分: 飘云币 -2 枚

    评分

    参与人数 9威望 +40 飘云币 +56 收起 理由
    飘云丶路人 + 4 不错 点赞
    Cyan + 4 + 4 很给力!
    点点星 + 4 很给力!
    冷月孤心 + 20 赞一个!
    syer + 4 很给力!
    lpxx + 4 + 4
    [PYG]版务督察 + 8 + 20 PYG有你更精彩!
    夜的静night + 4 赞一个!
    逍遥枷锁 + 8 + 8 PYG有你更精彩!

    查看全部评分

    PYG19周年生日快乐!
  • TA的每日心情
    开心
    11 小时前
  • 签到天数: 959 天

    [LV.10]以坛为家III

    发表于 2015-2-24 18:09:25 | 显示全部楼层
    不错,感谢分享心得~~~
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2015-2-24 20:34:53 | 显示全部楼层
    好文。{:soso_e179:}
    学习了!


    PYG19周年生日快乐!
  • TA的每日心情
    无聊
    1 小时前
  • 签到天数: 2574 天

    [LV.Master]伴坛终老

    发表于 2015-2-24 22:48:38 | 显示全部楼层
    分析的很有道理
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2024-3-7 15:25
  • 签到天数: 442 天

    [LV.9]以坛为家II

    发表于 2015-2-25 08:54:51 | 显示全部楼层
    如果做成视频教程就更好了 {:soso_e113:}

    点评

    我也是这么认为的,可惜环境不允许  发表于 2015-2-25 14:25
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2021-7-6 21:24
  • 签到天数: 7 天

    [LV.3]偶尔看看II

    发表于 2015-2-26 09:22:16 | 显示全部楼层
    很好很详细的分析 感谢分享
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2017-12-13 16:13
  • 签到天数: 621 天

    [LV.9]以坛为家II

    发表于 2015-2-26 11:34:11 | 显示全部楼层
    高手的经验之谈,不错,学习学习
    PYG19周年生日快乐!

    该用户从未签到

    发表于 2015-2-26 12:06:31 | 显示全部楼层
    好强悍,现在还无法学习·拜摸下~
    PYG19周年生日快乐!
  • TA的每日心情

    2017-3-19 21:54
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2015-2-26 12:52:59 | 显示全部楼层
    UPX已经年纪比较大了吧,最近年轻人(壳?)都是加密壳了
    PYG19周年生日快乐!
  • TA的每日心情

    2020-5-5 07:09
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2015-2-27 23:27:12 | 显示全部楼层
    貌似upx变形的好多  上回我还有个没破出来
    UPX 0.89.6 - 1.02 / 1.05 - 1.24 -> Markus & Laszlo
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表