UltraProtect 1.x 的脱壳与修复 - 雪狐提醒簿 V3.0

软件名称:雪狐提醒簿 V3.0

实例下载:http://www.onlinedown.net/soft/2469.htm

软件大小:4644KB

软件语言:简体中文

软件类别:国产软件/免费版/闹铃时钟

运行环境:Win9x/Me/NT/2000/XP

调试环境:Win2000, Ollydbg1.10, LordPE, ImportREC 1.6.2, PEid v0.93

作   者: blackeyes

作者声明:初学Crack,只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!



1. Dump文件,寻找OEP

首先用PEiD v0.93: 
UltraProtect 1.x -> RISCO Software Inc.

打开OllyDbg,载入 RemindBook.exe

009A6000 >  60              PUSHAD                                   ; 壳入口
009A6001    EB 01           JMP SHORT RemindBo.009A6004
009A6003    7B 66           JPO SHORT RemindBo.009A606B
009A6005    BD A98966C1     MOV EBP,C16689A9

按F9运行,有 INT3, 停在009B799F
009B799B    64:8920         MOV DWORD PTR FS:[EAX],ESP
009B799E    CC              INT3
009B799F    90              NOP                                      ; 停在这
009B79A0    64:67:8F06 0000 POP DWORD PTR FS:[0]
009B79A6    83C4 04         ADD ESP,4

按1次Shift+F9, 程序运行

Ctrl+F2 重来, CODE段下f2断点, F9运行, Shift+F9 通过INT3, 停在 OEP

00729280 >  55              PUSH EBP                                 ; 程序 OEP
00729281    8BEC            MOV EBP,ESP
00729283    B9 05000000     MOV ECX,5
00729288    6A 00           PUSH 0
0072928A    6A 00           PUSH 0

F7 单步 跟一下, 很快找到IAT

IAT:  0074F26C-0074FD6B
size: 0B00

这时打开 ImportREC, 选 Process RemindBook.exe
填上下列信息,
    OEP:  329280
    RVA:  0034F26C, Size:0B00
按 Get Imports, 很多无效的, 直接用 ImportREC 修复不了,
随便选一个, 右键==>Disassemble/HexView
009A6010    68 8DF3FB5A     PUSH 5AFBF38D
009A6015    813424 D8A5032D XOR DWORD PTR SS:[ESP],2D03A5D8
009A601C    C3              RETN

这几行等于 jmp far 77F85655,     // 5AFBF38D XOR 2D03A5D8 = 77F85655
变形的跳转.

从 009A6010 到 009A67FB, 全是这样, ImportREC 不能处理, 
要将它们变成 jmp xxxx,


Ctrl+F2 重来, 到 OEP 后, 在CODE段找一段空白地方, 写上一段代码修复

00729F00    60              PUSHAD
00729F01    BF 10609A00     MOV EDI,RemindBo.009A6010                ; 起始地址
00729F06    B9 EC070000     MOV ECX,7EC                              ; Size
00729F0B    33C0            XOR EAX,EAX
00729F0D    B0 68           MOV AL,68                                ; 先找 68
00729F0F    F2:AE           REPNE SCAS BYTE PTR ES:[EDI]
00729F11    83F9 00         CMP ECX,0
00729F14    74 2C           JE SHORT RemindBo.00729F42
00729F16    8B1F            MOV EBX,DWORD PTR DS:[EDI]               ; push XXXX
00729F18    8B47 04         MOV EAX,DWORD PTR DS:[EDI+4]
00729F1B    25 FFFFFF00     AND EAX,0FFFFFF
00729F20    3D 81342400     CMP EAX,243481                           ; 再找 813424
00729F25  ^ 75 E4           JNZ SHORT RemindBo.00729F0B
00729F27    8B57 07         MOV EDX,DWORD PTR DS:[EDI+7]             ; xor [esp], YYYY
00729F2A    33DA            XOR EBX,EDX
00729F2C    8A47 0B         MOV AL,BYTE PTR DS:[EDI+B]
00729F2F    3C C3           CMP AL,0C3                               ; 是 C3 吗?, RETN
00729F31  ^ 75 D8           JNZ SHORT RemindBo.00729F0B
00729F33    C647 FF E9      MOV BYTE PTR DS:[EDI-1],0E9              ; 修改成 JMP FAR ZZZZ
00729F37    83EB 04         SUB EBX,4
00729F3A    2BDF            SUB EBX,EDI
00729F3C    891F            MOV DWORD PTR DS:[EDI],EBX
00729F3E  ^ EB CB           JMP SHORT RemindBo.00729F0B
00729F40    90              NOP
00729F41    90              NOP
00729F42    61              POPAD
00729F43    90              NOP

上面代码的Binary Format, 在OLLYDBG 中, 可在CPU的数据窗直接用 Binary Copy/Binary Paste, 挺方便的.
60 BF 10 60 9A 00 B9 EC 07 00 00 33 C0 B0 68 F2 AE 83 F9 00 74 2C 8B 1F 8B 47 04 25 FF FF FF 00
3D 81 34 24 00 75 E4 8B 57 07 33 DA 8A 47 0B 3C C3 75 D8 C6 47 FF E9 83 EB 04 2B DF 89 1F EB CB
90 90 61 90 00 00 00 00 00 00 00 00 00 00 00 00


写完代码后, 让这段代码运行一遍, 再回到OEP, 用OllyDump 插件 Dump 

这时打开ImportREC, 再进行IAT修复, 用Level 1后, 只有3个不能修复, 并且指向壳中

0  0034F330  ?  0000  009A6280
0  0034F844  ?  0000  009B4E31
0  0034F878  ?  0000  009B4E4B


2. Debug, 修复IAT

Ctrl+F2 重来, 到 OEP 后, 直接转到 009A6280/009B4E31, 然后 F7 单步跟踪


先跟 009B4E31
009B4E31    837C24 04 FF             CMP DWORD PTR SS:[ESP+4],-1
009B4E36    74 13                    JE SHORT RemindBo.009B4E4B
009B4E38    90                       NOP
009B4E39    90                       NOP
009B4E3A    90                       NOP
009B4E3B    90                       NOP
009B4E3C    55                       PUSH EBP
009B4E3D    E8 BE120000              CALL RemindBo.009B6100                   ; EBP = 005A5000
009B4E42    8BC5                     MOV EAX,EBP
009B4E44    5D                       POP EBP
009B4E45  - FFA0 C4FD4000            JMP DWORD PTR DS:[EAX+40FDC4]            ; user32.RegisterHotKey
所以 009B4E31 实际上就是 user32.RegisterHotKey,

再跟009A6280, 实际上与009B4E4B一样了
009A6280   /E9 C6EB0000              JMP RemindBo.009B4E4B

从现在起, 下面有很多的花指令, 以及很多的垃圾指令, 来干扰跟踪, 而且其中最主要的 CODE 还是
加密的, 总是先解密, 然后执行, 接着又加密回去, 下面只将有用的指令列出:
009B4E4B    60                       PUSHAD
009B4E4C    FC                       CLD
009B4E4D    78 03                    JS SHORT RemindBo.009B4E52
009B4E4F    79 01                    JNS SHORT RemindBo.009B4E52
...

009B4F13    68 F64F9B00              PUSH RemindBo.009B4FF6                   ; 解密的起始地址

009B4F1F    5B                       POP EBX                                  ; RemindBo.009B4FF6

009B4F31    BF 535ED47F              MOV EDI,7FD45E53                         ; 解密的 Key

009B4F40    B9 691549E2              MOV ECX,E2491569
009B4F45    03EF                     ADD EBP,EDI
009B4F47    81C1 D6EAB61D            ADD ECX,1DB6EAD6                         ; ECX = 3F, 解密的长度, DWORD

009B4F56    8B03                     MOV EAX,DWORD PTR DS:[EBX]               ; 读出加密的CODE, 一个 DWORD

009B4F5D    33C7                     XOR EAX,EDI                              ; 变换

009B4F6D    C1C0 11                  ROL EAX,11                               ; 变换

009B4F76    2B43 04                  SUB EAX,DWORD PTR DS:[EBX+4]             ; 变换

009B4F91    8903                     MOV DWORD PTR DS:[EBX],EAX               ; 写回解密后的CODE, 一个 DWORD

009B4FA5    81C7 DBF7E8FD            ADD EDI,FDE8F7DB                         ; Key变换

009B4FDA    83E9 01                  SUB ECX,1
009B4FDD  ^ 0F85 73FFFFFF            JNZ RemindBo.009B4F56                    ; 循环
...

下面是解密后的CODE, 没有花指令, 没有垃圾指令, 看起来舒服多了
009B4FF6    61                       POPAD
009B4FF7    55                       PUSH EBP
009B4FF8    E8 03110000              CALL RemindBo.009B6100
009B4FFD    8BC5                     MOV EAX,EBP                              ; EAX = RemindBo.005A5000
009B4FFF    5D                       POP EBP
009B5000    837C24 04 FF             CMP DWORD PTR SS:[ESP+4],-1
009B5005    74 25                    JE SHORT RemindBo.009B502C
009B5007   |90                       NOP
009B5008   |90                       NOP
009B5009   |90                       NOP
009B500A   |90                       NOP
009B500B   |8B98 2C854100            MOV EBX,DWORD PTR DS:[EAX+41852C]        ; user32.MessageBoxA
到这已经得到了API, 可以修复IAT 了

再跟一跟这段代码
009B5011    803B CC                  CMP BYTE PTR DS:[EBX],0CC                ; MessageBoxA 上有断点吗?
009B5014    0F84 DE000000            JE RemindBo.009B50F8                     ; 有就跳, 最后会到005A5000
009B501A    807B 01 CC               CMP BYTE PTR DS:[EBX+1],0CC              ; MessageBoxA+1 上有断点吗?
009B501E    0F84 D4000000            JE RemindBo.009B50F8                     ; 有就跳, 最后会到005A5000
009B5024    8BC3                     MOV EAX,EBX                              ; EAX <== API Address
009B5026    60                       PUSHAD
009B5027    E9 CC000000              JMP RemindBo.009B50F8
...
如果跟踪原程序, 假如在MessageBoxA, 或者MessageBoxA+1上下断点, 壳会检测到的!!


下面这段CODE 就是把 009B4FF6-009B50F1 再加密回去
009B50F8    60                       PUSHAD
009B50F9    E8 00000000              CALL RemindBo.009B50FE
009B50FE    5E                       POP ESI
009B50FF    83EE 06                  SUB ESI,6
009B5102    B9 02010000              MOV ECX,102
009B5107    29CE                     SUB ESI,ECX
009B5109    BA 5D6540FE              MOV EDX,FE40655D
009B510E    C1E9 02                  SHR ECX,2
009B5111    83E9 02                  SUB ECX,2
009B5114    83F9 00                  CMP ECX,0
009B5117    7C 1A                    JL SHORT RemindBo.009B5133
009B5119    8B048E                   MOV EAX,DWORD PTR DS:[ESI+ECX*4]
009B511C    8B5C8E 04                MOV EBX,DWORD PTR DS:[ESI+ECX*4+4]
009B5120    03C3                     ADD EAX,EBX
009B5122    C1C8 11                  ROR EAX,11
009B5125    33C2                     XOR EAX,EDX
009B5127    81EA DBF7E8FD            SUB EDX,FDE8F7DB
009B512D    89048E                   MOV DWORD PTR DS:[ESI+ECX*4],EAX
009B5130    49                       DEC ECX
009B5131  ^ EB E1                    JMP SHORT RemindBo.009B5114
009B5133    61                       POPAD

009B5134    61                       POPAD                                    ; 对应009B5026 的PUSHAD
009B5135    837C24 04 FF             CMP DWORD PTR SS:[ESP+4],-1
009B513A    74 06                    JE SHORT RemindBo.009B5142
009B513C    90                       NOP
009B513D    90                       NOP
009B513E    90                       NOP
009B513F    90                       NOP
009B5140  - FFE0                     JMP EAX                                  ; user32.MessageBoxA
009B5142    C2 1000                  RETN 10
从 009B5140 跳到API 中执行.

跟踪结果:
0  0034F330  ?  0000  009A6280      // user32.MessageBoxA
0  0034F844  ?  0000  009B4E31      // user32.RegisterHotKey
0  0034F878  ?  0000  009B4E4B      // user32.MessageBoxA


3. 脱壳后的再修复

脱壳, IAT修复后,再运行, 还是有错

用 OLLYDBG 载入dumped_.exe

Ctrl+F2 重来, 在壳的CODE段下f2断点, F9运行, 断在下面, 说明脱壳后, CODE 与壳还有联系.

下面的CODE, 要用F7跟踪, 有很多的花指令, 很多的垃圾指令, 来干扰跟踪, 而且其中主要的 CODE 还是
加密的, 总是先解密, 然后执行, 接着又加密回去,与上面的MessageBoxA 那段CODE很象,

下面只将有用的指令列出, 花指令/垃圾指令 都略过.

009A702E    60                       PUSHAD
009A702F    F9                       STC
009A7030    87C8                     XCHG EAX,ECX
...
009A70D5    BB D9719A00              MOV EBX,RemindBo.009A71D9                ; 解密的起始地址
...
009A70F1    BE B0E08A2A              MOV ESI,2A8AE0B0
009A70F6    C1EA C4                  SHR EDX,0C4                              ; Shift constant out of range 1..31
009A70F9    81C6 073A95D0            ADD ESI,D0953A07                         ; ESI = FB201AB7

009A710F    BF 39CECA88              MOV EDI,88CACE39
009A7114    8BC6                     MOV EAX,ESI
009A7116    81F7 12CECA88            XOR EDI,88CACE12                         ; EDI = 002B, 解密的长度, DWORD

009A712E    8B2B                     MOV EBP,DWORD PTR DS:[EBX]               ; 读出加密的 DWORD
...
009A7140    03EE                     ADD EBP,ESI                              ; 变换
...
009A7155    C1C5 10                  ROL EBP,10                               ; 变换
...
009A7166    036B 04                  ADD EBP,DWORD PTR DS:[EBX+4]             ; 变换
...
009A7179    892B                     MOV DWORD PTR DS:[EBX],EBP               ; 写回解密后的DWORD
...
009A7189    81F6 3D309808            XOR ESI,898303D
...
009A71A7    81EB FCFFFFFF            SUB EBX,-4
...
009A71C3    83EF 01                  SUB EDI,1
009A71C6  ^ 0F85 62FFFFFF            JNZ RemindBo.009A712E                    ; 循环
009A71CC   /7E 03                    JLE SHORT RemindBo.009A71D1
009A71CE   |7F 01                    JG SHORT RemindBo.009A71D1

009A71D1   /E9 03000000              JMP RemindBo.009A71D9

从009A71D9到009A7284为解密的CODE
009A71D9    E8 22EF0000              CALL RemindBo.009B6100                    ; 壳用来重定位的一个地址 EBP=005A5000
009A71DE    8B4424 20                MOV EAX,DWORD PTR SS:[ESP+20]             ; 返回地址 404FB0 ==> EAX
009A71E2    33C9                     XOR ECX,ECX                               ; ECX = 0, index
009A71E4    8B9C8D 812E4000          MOV EBX,DWORD PTR SS:[EBP+ECX*4+402E81]   ; Offset 保存在从009A7E81开始的地址
009A71EB    039D 46F84000            ADD EBX,DWORD PTR SS:[EBP+40F846]         ; 加上 Module Base 00400000
009A71F1    3BC3                     CMP EAX,EBX                               ; 与返回地址比较
009A71F3    74 07                    JE SHORT RemindBo.009A71FC                ; 相等吗?
009A71F5    90                       NOP
009A71F6    90                       NOP
009A71F7    90                       NOP
009A71F8    90                       NOP
009A71F9    41                       INC ECX                                   ; index 加 1 
009A71FA  ^ EB E8                    JMP SHORT RemindBo.009A71E4               ; 循环

009A71FC    8DB5 615D4000            LEA ESI,DWORD PTR SS:[EBP+405D61]         ; ESI=009AAD61, 被抽掉的 Code 保存的

起始地址
009A7202    B8 0A000000              MOV EAX,0A                                ; 每块被抽掉的 Code 保存为 0x0a bytes 
009A7207    F7E1                     MUL ECX                                   ; index
009A7209    03F0                     ADD ESI,EAX                               ; 对应于返回地址的 被抽Code的保存起始, 

源地址
009A720B    8DBD 07184000            LEA EDI,DWORD PTR SS:[EBP+401807]         ; EDI = 009A6807, 目的地址, 共用的, 

与index无关
009A7211    0FB6840D C9224000        MOVZX EAX,BYTE PTR SS:[EBP+ECX+4022C9]    ; 从地址009A72C9开始记录各处CALL到壳

中的次数
009A7219    FEC0                     INC AL                                    ; 次数加 1
009A721B    88840D C9224000          MOV BYTE PTR SS:[EBP+ECX+4022C9],AL       ; 保存次数
009A7222    3C 20                    CMP AL,20                                 ; 已经 0x20 次了吗?
009A7224    75 13                    JNZ SHORT RemindBo.009A7239               ; 不够 0x20 次, 跳到009A7239
009A7226    90                       NOP
009A7227    90                       NOP
009A7228    90                       NOP
009A7229    90                       NOP

009A722A    8BBD 4AF84000            MOV EDI,DWORD PTR SS:[EBP+40F84A]         ; 如果已经0x20 次, 目的地址从

[009B484A]中取出
                                                                               ; 为00134938
009A7230    B8 0A000000              MOV EAX,0A
009A7235    F7E1                     MUL ECX
009A7237    03F8                     ADD EDI,EAX                               ; 再加上偏移量, 0x0a * index, 与

index相关, 不重叠
009A7239    8A9D 1E204000            MOV BL,BYTE PTR SS:[EBP+40201E]           ; SS:[009A701E]=BA, 解密的Key
009A723F    B9 0A000000              MOV ECX,0A                                ; 0x0a bytes
009A7244    AC                       LODS BYTE PTR DS:[ESI]                    ; 从源地址取一byte
009A7245    32C3                     XOR AL,BL                                 ; 变换
009A7247    AA                       STOS BYTE PTR ES:[EDI]                    ; 保存到目的地址
009A7248  ^ E2 FA                    LOOPD SHORT RemindBo.009A7244             ; 循环 0x0a 次
009A724A    83EF 0A                  SUB EDI,0A                                ; 目的地址
009A724D    57                       PUSH EDI                                  ; 目的地址入栈
009A724E    8DB5 07184000            LEA ESI,DWORD PTR SS:[EBP+401807]         ; ESI = 009A6807, 共用的目的地址
009A7254    33F7                     XOR ESI,EDI
009A7256    74 19                    JE SHORT RemindBo.009A7271                ; 目的地址 是 009A6807? 是就跳
009A7258    90                       NOP
009A7259    90                       NOP
009A725A    90                       NOP
009A725B    90                       NOP

下面的几行, 把程序中的 CALL 009A702E, 改成 CALL ( 00134938 + 0x0a * index )
可能主要是为了提高速度, 因为 CALL 到 009A702E, 要执行这么多的指令, 而实际上的指令只有 0x0a byte
009A725C    8B7424 24                MOV ESI,DWORD PTR SS:[ESP+24]
009A7260    83EE 04                  SUB ESI,4
009A7263    AD                       LODS DWORD PTR DS:[ESI]
009A7264    81EF 2E204000            SUB EDI,RemindBo.0040202E
009A726A    2BFD                     SUB EDI,EBP
009A726C    03C7                     ADD EAX,EDI
009A726E    8946 FC                  MOV DWORD PTR DS:[ESI-4],EAX

009A7271    5F                       POP EDI
009A7272    57                       PUSH EDI
009A7273    33C9                     XOR ECX,ECX
009A7275    83F9 08                  CMP ECX,8                                 ; 循环 8 次, 将目的地址挪到 PUSHAD 进

栈的8个DWORD 下面
009A7278    74 0E                    JE SHORT RemindBo.009A7288
009A727A    90                       NOP
009A727B    90                       NOP
009A727C    90                       NOP
009A727D    90                       NOP
009A727E    8B448C 04                MOV EAX,DWORD PTR SS:[ESP+ECX*4+4]
009A7282    89048C                   MOV DWORD PTR SS:[ESP+ECX*4],EAX
009A7285    41                       INC ECX
009A7286  ^ EB ED                    JMP SHORT RemindBo.009A7275
009A7288    893C8C                   MOV DWORD PTR SS:[ESP+ECX*4],EDI

009A728B    60                       PUSHAD                                   ; 重新加密 009A71D9-009A7284
009A728C    E8 00000000              CALL RemindBo.009A7291
009A7291    5E                       POP ESI
009A7292    83EE 06                  SUB ESI,6
009A7295    B9 B2000000              MOV ECX,0B2
009A729A    29CE                     SUB ESI,ECX
009A729C    BA B71A20FB              MOV EDX,FB201AB7
009A72A1    C1E9 02                  SHR ECX,2
009A72A4    83E9 02                  SUB ECX,2
009A72A7    83F9 00                  CMP ECX,0
009A72AA    7C 1A                    JL SHORT RemindBo.009A72C6
009A72AC    8B048E                   MOV EAX,DWORD PTR DS:[ESI+ECX*4]
009A72AF    8B5C8E 04                MOV EBX,DWORD PTR DS:[ESI+ECX*4+4]
009A72B3    2BC3                     SUB EAX,EBX
009A72B5    C1C8 10                  ROR EAX,10
009A72B8    2BC2                     SUB EAX,EDX
009A72BA    81F2 3D309808            XOR EDX,898303D
009A72C0    89048E                   MOV DWORD PTR DS:[ESI+ECX*4],EAX
009A72C3    49                       DEC ECX
009A72C4  ^ EB E1                    JMP SHORT RemindBo.009A72A7
009A72C6    61                       POPAD                                   ; 加密结束

009A72C7    61                       POPAD
009A72C8    C3                       RETN                                    ; 返回 目的地址

目的地址, 0x0a bytes, 这就是程序 CALL 009A702E 真正要执行的 CODE
009A6807    33FD                     XOR EDI,EBP                             ; 干扰指令
009A6809    33DB                     XOR EBX,EBX                             ; 从原程序中抽掉的指令
009A680B    8B40 04                  MOV EAX,DWORD PTR DS:[EAX+4]            ; 从原程序中抽掉的指令
009A680E    33FD                     XOR EDI,EBP                             ; 干扰指令
009A6810    C3                       RETN                                    ; 返回 00404FB0

其中有两条干扰指令, 真正起作用的指令只有两条, 为 5 BYTES

壳实际上是把真正的原程序中的
          33DB                     XOR EBX,EBX
          8B40 04                  MOV EAX,DWORD PTR DS:[EAX+4]
抽掉, 再加上两条干扰指令, 然后加密保存起来, 再把原程序中被抽掉的地方改成
            CALL 009A702E
从 009A702E 开始的CODE, 就是根据返回地址, 找到保存的加密code, 解密, 然后执行.

看看从原程序中都抽掉了多少CODE

009A72C9 - 009A7E80, 每块为 1 BYTE,   保存次数
009A7E81 - 009AAD60, 每块为 1 DWORD,  保存Offset
009AAD61 - 009B2290, 每块为 10 BYTES, 保存加密的CODE

总共是0BB8块, 用10进制是3000, 够变态的吧?!

而且每块被抽的 5 BYTES, 不仅加密, 还加上两条干扰指令, 搞的不能很简单的自动还原, 脱了壳, 
还得带着壳的一些CODE 运行, 够狠的!


先说脱壳后的问题:

脱壳后的程序, 如果运行到 009A722A, 从[009B484A]中取出的地址00134938,

已经不可以使用了, 这时候如果把解密的CODE 放到其中, 不出问题才怪!

[009B484A]的地址00134938, 应该是壳在解码程序, 运行到OEP之前分配的内存, 一脱壳就把这脱没了.


再说一说如何修复吧:

用OLLYDBG 载入dumped_.exe, F2 在009A702E 下断, F9 运行, 停在009A702E

再 F2 在009A728B 下断, F9 运行, 停在009A728B, 

这时壳009A71D9-009A7284的CODE 已解密,

然后将下面的三个地方改掉,

009A702E    60                       PUSHAD
009A702F    E9 A5010000              JMP RemindBo.009A71D9                  ; 直接 跳过垃圾指令与壳解密的指令

009A7222    3C 20                    CMP AL,20
009A7224   /EB 13                    JMP SHORT RemindBo.009A7239            ; 总是使用共用的目的地址

009A728B   /EB 3A                    JMP SHORT RemindBo.009A72C7            ; 直接 跳过壳加密指令


最后将 009A702E - 009A6810 的区域DUMP出来, 再用16进制编辑器, 覆盖 dumped_.exe 的相应地方, 

然后运行, 一切正常.