solly 发表于 2019-6-16 12:25

160 个 CrackMe 之 043 -- Ding Boy 之脱壳及patch过程

本帖最后由 solly 于 2019-6-16 16:32 编辑

160 个 CrackMe 之 043 -- Ding Boy 是一个加了壳的 CrackMe,并且这个壳只能在 Win9x 下运行,因为其壳在搜索第一个API(LoadLibraryA)的入口时,使用了判断API头4个字节特征码的方法,在 Windows NT 系列操作系统上无效,导致取不到API入口。如果取到LoadLibraryA()的入口,则GetProcAddress()取一个固定值(应该只能针对某个版本的Windows)其它API入口,则由GetProcAddress()取得。
脱了壳以后,Crackme可以在Window 10 下正常运行。但跟踪过程是在虚拟win98下进行的。
先看看带壳时的文件信息。


直接运行会弹出一个”此软件试用期已过。。。”的NAG,然后退出。

看标题是一个叫 PE-lock的壳,找不到相关信息,手动脱壳,用OD载入,如下图:


第一条指令是 PUSHFD,不过 PUSHAD,ESP定律用不上,三个 jmp后面的代码较乱,应该是加密了的代码数据。
按F8执行,执行三段代码(中间通过jmp衔接),如下:
;程序壳代码的入口:
00419319 >9C            pushfd
0041931A    55            push    ebp
0041931B    E8 EC000000   call    0041940C
00419320    87D5            xchg    ebp, edx
00419322    5D            pop   ebp
00419323    60            pushad
00419324    87D5            xchg    ebp, edx
00419326    80BD 15274000 0>cmp   byte ptr , 1
0041932D    74 39         je      short 00419368
0041932F    C685 15274000 0>mov   byte ptr , 1
00419336    E9 E4000000   jmp   0041941F

;/////////////////////////////////////////////////////////////

0041941F    E8 1B000000   call    0041943F
00419424    8B6424 08       mov   esp, dword ptr
00419428    E8 DFFFFFFF   call    0041940C
0041942D    C685 CC264000 C>mov   byte ptr , 0C3
00419434    E8 4BFFFFFF   call    00419384
00419439^ E9 16FFFFFF   jmp   00419354

;/////////////////////////////////////////////////////////////

00419354    8DBD 24264000   lea   edi, dword ptr
0041935A    B9 3B000000   mov   ecx, 3B
0041935F    F3:AA         rep   stos byte ptr es:
00419361    64:67:8F06 0000 pop   dword ptr fs:
00419367    5A            pop   edx
00419368    8B85 0D274000   mov   eax, dword ptr
0041936E    0185 09274000   add   dword ptr , eax
00419374    61            popad
00419375    9D            popfd
00419376    8B9A 09274000   mov   ebx, dword ptr
0041937C    898A 09274000   mov   dword ptr , ecx
00419382    FFE3            jmp   ebx                              ; ebx = 0x00419000
最后一条 JMP ebx,ebx = 0x00419000,通过前面程序信息中的节信息,可以看出这是一个代码节(节名“NET”)的起点,跳转过去后如下图所示:

这里也包括明文代码和加密代码,从 0x00419025 开始的代码是加密的,而明文代码的作用就是解密(解密函数调用 call 00419309)。
解密函数内容如下:
/00419309    60            pushad
|0041930A    03F1            add   esi, ecx
|0041930C    4E            dec   esi
|0041930D    8A06            mov   al, byte ptr
|0041930F    3246 01         xor   al, byte ptr
|00419312    8806            mov   byte ptr , al
|00419314    4E            dec   esi
|00419315^ E2 F6         loopd   short 0041930D
|00419317    61            popad      ; F4 到这里,执行前面的循环
\00419318    C3            retn       ; 返回到 DING_BOY.00419025 来自00419020 call 00419309

解密方法也简单,就是xor下一字节(code[ i ] = code[ i ] xor code[ i+1 ])。
如上图所示,那个call 00419309 需要按 F7 跟入,然后在 00419317 popad 处按F4 执行解密过程,几个F8返回。解密后的代码如下:
; 下面进入第一阶段的时间检查和解码过程
00419000    55            push    ebp
00419001    57            push    edi
00419002    56            push    esi
00419003    52            push    edx
00419004    51            push    ecx
00419005    53            push    ebx
00419006    E8 00000000   call    0041900B
0041900B    5D            pop   ebp
0041900C    8BD5            mov   edx, ebp
0041900E    81ED 23354000   sub   ebp, 00403523
00419014    BE 3D354000   mov   esi, 0040353D
00419019    03F5            add   esi, ebp
0041901B    B9 E4020000   mov   ecx, 2E4
00419020    E8 E4020000   call    00419309                        ; 必须 F7 进入调用
;-----------------------------------------------------------------------------------
; 上面的调用 retn 到这里,进行时间过期检查,通过则继续解码            
00419025    E8 99000000   call    004190C3                        ; 取得 GetSystemTime, MessageBoxA 等 API 的入口
0041902A    83BD 8F374000 0>cmp   dword ptr , 0
00419031    74 4C         je      short 0041907F
00419033    2B95 55364000   sub   edx, dword ptr
00419039    83EA 0B         sub   edx, 0B
0041903C    8995 5D364000   mov   dword ptr , edx
00419042    BE B7374000   mov   esi, 004037B7
00419047    03F5            add   esi, ebp
00419049    56            push    esi
0041904A    FF95 93374000   call    dword ptr                      ; GetSystemTime
00419050    66:83BD B937400>cmp   word ptr , 1                   ; 1月
00419058    72 31         jb      short 0041908B
0041905A    66:81BD B737400>cmp   word ptr , 7D0               ; 2000年
00419063    72 26         jb      short 0041908B                           ; 必须跳转,必须跳转,必须跳转
00419065    B8 69374000   mov   eax, 00403769
0041906A    03C5            add   eax, ebp
0041906C    BB F9374000   mov   ebx, 004037F9
00419071    03DD            add   ebx, ebp
00419073    6A 00         push    0
00419075    50            push    eax
00419076    53            push    ebx
00419077    6A 00         push    0
00419079    FF95 8F374000   call    dword ptr                      ; 显示过期NAG
0041907F    B8 FFFFFFFF   mov   eax, -1
00419084    5B            pop   ebx
00419085    59            pop   ecx
00419086    5A            pop   edx
00419087    5E            pop   esi
00419088    5F            pop   edi
00419089    5D            pop   ebp
0041908A    C3            retn
0041908B    BE 61364000   mov   esi, 00403661                              ; 没有过期继续解码
00419090    03F5            add   esi, ebp
00419092    833E 00         cmp   dword ptr , 0
00419095    74 16         je      short 004190AD
00419097    8B9D 5D364000   mov   ebx, dword ptr
0041909D    031E            add   ebx, dword ptr
0041909F    8B4E 04         mov   ecx, dword ptr
004190A2    802B 01         sub   byte ptr , 1
004190A5    43            inc   ebx
004190A6^ E2 FA         loopd   short 004190A2
004190A8    83C6 08         add   esi, 8
004190AB^ EB E5         jmp   short 00419092
004190AD    8B85 59364000   mov   eax, dword ptr                 ; F4 到这里,执行前面的循环
004190B3    8B9D 5D364000   mov   ebx, dword ptr
004190B9    03C3            add   eax, ebx
004190BB    5B            pop   ebx
004190BC    59            pop   ecx
004190BD    5A            pop   edx
004190BE    5E            pop   esi
004190BF    5F            pop   edi
004190C0    5D            pop   ebp004190C1    FFE0            jmp   eax                                        ; eax = 00418000
上面代码中解码的第一条指令 00419025    E8 99000000   call    004190C3, 就是动态查找和填充 API 用的,包用 LoadLibraryA(),GetProcAddress(),GetSystemTime() 和 MessageBoxA() 等函数。如下图所示,已找到 LoadLibraryA()函数:

以及:


上面查找完 API 入口地址并返回后,几个 F8 执行到 00419063    jb      short 0041908B 时,必须通过修改 EIP 实现跳转,不然就是显示上面的“本软件试用期已过。。。。”的对话框,然后就会退出CrackMe了。
如下图,修改 EIP 进行跳转:

在 0x0041908B 这一行上按右键,弹出OD的右键菜单,选择”此处为新 EIP“,会弹出一个如上图所示的消息框,选”是“即可,修改好EIP后的情况如下图所示:

新的EIP已改为 0x0041908B,这里也是一段解码程序,定位到 0x004190AD 这一行代码,然后按 F4 跳过解码过程,几个 F8 来到 0x004190C1,这里是一个JMP eax 指令,eax = 0x00418000,通过前面的节信息可知,这是另一个代码节的开始地址。
后面几个节的执行过程与 0x00419000这一节的代码完全一样,操作过程也一样,包括 0x00418000, 0x00417000, 0x00416000 这三个节,功能和操作方法完全一样(都要同样修改EIP),当执行到 0x00416000这一节最后面时,如下:
0041608B    BE 61364000   mov   esi, 00403661                              ; 新的EIP,继续解码
00416090    03F5            add   esi, ebp
00416092    833E 00         cmp   dword ptr , 0
00416095    74 16         je      short 004160AD
00416097    8B9D 5D364000   mov   ebx, dword ptr
0041609D    031E            add   ebx, dword ptr
0041609F    8B4E 04         mov   ecx, dword ptr
004160A2    802B 01         sub   byte ptr , 1
004160A5    43            inc   ebx
004160A6^ E2 FA         loopd   short 004160A2
004160A8    83C6 08         add   esi, 8
004160AB^ EB E5         jmp   short 00416092
004160AD    8B85 59364000   mov   eax, dword ptr
004160B3    8B9D 5D364000   mov   ebx, dword ptr
004160B9    03C3            add   eax, ebx
004160BB    5B            pop   ebx
004160BC    59            pop   ecx
004160BD    5A            pop   edx
004160BE    5E            pop   esi
004160BF    5F            pop   edi
004160C0    5D            pop   ebp
004160C1    FFE0            jmp   eax                                       ; eax = 0x00414319
EAX = 0x00414319,这个就是壳代码的入口,OD载入Crackme后,就是停留在这个地址。
我们再次执行这里的三段代码,最后的跳转如下:
00414376    8B9A 09274000   mov   ebx, dword ptr
0041437C    898A 09274000   mov   dword ptr , ecx
00414382    FFE3            jmp   ebx                           ; ebx = 0x00414000
这次不是跳转到 0x00419000了,而是跳转到 0x00414000,然后到了 0x00414000一看,与前面0x00419000代码也是一样的,用同样的方法执行即可(包括修改EIP)。
0x00414000执行完后,同样跳转到另一个相同代码的过程0x00413000,按同样操作方式完成0x00413000代码节的执行,来到最后的跳转处:
004130AD    8B85 59364000   mov   eax, dword ptr
004130B3    8B9D 5D364000   mov   ebx, dword ptr
004130B9    03C3            add   eax, ebx
004130BB    5B            pop   ebx
004130BC    59            pop   ecx
004130BD    5A            pop   edx
004130BE    5E            pop   esi
004130BF    5F            pop   edi
004130C0    5D            pop   ebp
004130C1    FFE0            jmp   eax                                        ; eax= 0x004011CB
可以看到,这时 EAX = 0x004011CB,这个地址就是 CrackMe 的 OEP 了。
按F8来到 OEP,下一步就是通过 LordPE 进行 Dump操作了,如下图:

然后再进行导入表的修正,用 Import REC 完成:

先选择Crackme进程后,再修改 OEP 为 0x000011CB,然后点击”IAT AutoSearch“,再然后上点击”Get Imports“,看到有导入信息:


但只有一个crtdll.dll 的导入信息,没有 Kernal32.dll,User32.dll 等系统DLL的导入信息,这肯定不对。我们前面点击”IAT AutoSearch"时,会弹出一个消息框,説可以按提示修改 RVA 及其 Size 的参数(如果不记得了,可再次点击“IAT AutoSearch”),我们将 RAV 改成0x00005000, Size 修改成 0x0000047A,然后再次点击“Get Imports",这次出来一大片:

有一些是无效导入,需要删除,点击右边的”Show Invalid"按钮后,显示如下,无效的导入会选中(蓝色),如下图所示:

然后在这些选中的项目上按右键,选择“Delete thunk(s)"菜单项,删除这些无用的导入项,如下图所示:

删完无效的导入项后,有效的导入表如下图所示:

然后,我们就可以点击”Fix Dump"对 LordPE 程序 Dump 出来的 CrackMe 进行修正了,如下图:

选中“Dumped.exe"进行修复即可。
脱壳工作到此就完成了。
我们运行脱壳后的 Crackme,显示界面如下:

可以看到”Request"按钮是禁用状态,需要 patch 成可用状态才算完成 Crackme 的破解。
这是一个基于 Dialog 的应用程序,我们通过 OEP 向下执行,很快可以找到如下代码:
0040129D|.6A 00         push    0                                          ; /lParam = NULL
0040129F|.68 B8124000   push    004012B8                                 ; |DlgProc = DING_BOY.004012B8
004012A4|.6A 00         push    0                                          ; |hOwner = NULL
004012A6|.6A 64         push    64                                       ; |pTemplate = 64
004012A8|.57            push    edi                                        ; |hInst
004012A9|.E8 0A060000   call    <jmp.&USER32.DialogBoxParamA>            ; \DialogBoxParamA
其中 DlgProc 的地址为:0x004012B8,其代码如下:
004012B8/.55            push    ebp
004012B9|.89E5          mov   ebp, esp
004012BB|.51            push    ecx                                        ; 局部变量 isPatched, Win9x 下,ecx == eax == 0;
004012BC|.50            push    eax                                        ; 局部变量 hInstance, WinNt 下,ecx == eax == DlgProc,需要破解
004012BD|.C745 FC 00000>mov   dword ptr , 0                     ; win9x下破解: mov dword ptr , 0x01
004012C4|.8B45 0C       mov   eax, dword ptr
004012C7|.83F8 10       cmp   eax, 10                                    ;WM_CLOSE 消息, Switch (cases 10..111)
004012CA|.0F84 E1000000 je      004013B1
004012D0|.0F8C EA000000 jl      004013C0
004012D6|.3D 10010000   cmp   eax, 110                                 ;WM_INITDIALOG 消息
004012DB|.74 0C         je      short 004012E9
004012DD|.3D 11010000   cmp   eax, 111                                 ;WM_COMMAND 消息
004012E2|.74 39         je      short 0040131D
004012E4|.E9 D7000000   jmp   004013C0
; 以下是初始化主对话框界面(WM_INITDIALOG)
004012E9|>837D FC 00    cmp   dword ptr , 0                     ; 变量的初始值需要patch为非0值
004012ED|.75 15         jnz   short 00401304
004012EF|.68 2E040000   push    42E                                        ; /ControlID = 42E (1070.)
004012F4|.FF75 08       push    dword ptr                         ; |hWnd
004012F7|.E8 EC050000   call    <jmp.&USER32.GetDlgItem>                   ; \GetDlgItem
004012FC|.6A 00         push    0                                          ; /Enable = FALSE,破解这里,push 0 改成 push 1
004012FE|.50            push    eax                                        ; |hWnd
004012FF|.E8 C0050000   call    <jmp.&USER32.EnableWindow>               ; \EnableWindow
00401304|>FF75 14       push    dword ptr
00401307|.FF75 10       push    dword ptr
0040130A|.FF75 08       push    dword ptr
0040130D|.E8 A2FFFFFF   call    004012B4
00401312|.83C4 0C       add   esp, 0C
00401315|.31C0          xor   eax, eax
00401317|.40            inc   eax
00401318|.E9 A5000000   jmp   004013C2
; 以下是 WM_COMMAND 消息处理
0040131D|>0FB745 10   movzx   eax, word ptr                      ;Case 111 (WM_COMMAND) of switch 004012C7
00401321|.3D 24040000   cmp   eax, 424                                 ; About 按钮事件,Switch (cases 1..42E)
00401326|.74 38         je      short 00401360
00401328|.7F 0F         jg      short 00401339
0040132A|.83F8 01       cmp   eax, 1                                     ; ID_OK
0040132D|.74 13         je      short 00401342
0040132F|.83F8 02       cmp   eax, 2                                     ; ID_CANCEL
00401332|.74 1D         je      short 00401351
00401334|.E9 87000000   jmp   004013C0
00401339|>3D 2E040000   cmp   eax, 42E                                 ; Request 按钮事件
0040133E|.74 39         je      short 00401379
00401340|.EB 7E         jmp   short 004013C0                           ; 转去默认消息处理过程
00401342|>6A 01         push    1                                          ; /Result = 1; Case 1 of switch 00401321
00401344|.FF75 08       push    dword ptr                         ; |hWnd
00401347|.E8 84050000   call    <jmp.&USER32.EndDialog>                  ; \EndDialog
0040134C|.31C0          xor   eax, eax
0040134E|.40            inc   eax
0040134F|.EB 71         jmp   short 004013C2
00401351|>6A 00         push    0                                          ; /Result = 0; Case 2 of switch 00401321
00401353|.FF75 08       push    dword ptr                         ; |hWnd
00401356|.E8 75050000   call    <jmp.&USER32.EndDialog>                  ; \EndDialog
0040135B|.31C0          xor   eax, eax
0040135D|.40            inc   eax
0040135E|.EB 62         jmp   short 004013C2
; 下面是"About"按钮事件处理过程
00401360|>6A 00         push    0                                          ; /lParam = NULL; Case 424 of switch 00401321
00401362|.68 C6134000   push    004013C6                                 ; |DlgProc = DING_BOY.004013C6
00401367|.FF75 08       push    dword ptr                         ; |hOwner
0040136A|.6A 65         push    65                                       ; |pTemplate = 65
0040136C|.FF75 F8       push    dword ptr                         ; |hInst
0040136F|.E8 44050000   call    <jmp.&USER32.DialogBoxParamA>            ; \DialogBoxParamA
00401374|.31C0          xor   eax, eax
00401376|.40            inc   eax
00401377|.EB 49         jmp   short 004013C2
00401379|>837D FC 00    cmp   dword ptr , 0                     ;Case 42E of switch 00401321
0040137D|.75 1C         jnz   short 0040139B
; 下面是破解前request按钮事件处理过程
0040137F|.68 2E040000   push    42E                                        ; /ControlID = 42E (1070.)
00401384|.FF75 08       push    dword ptr                         ; |hWnd
00401387|.E8 5C050000   call    <jmp.&USER32.GetDlgItem>                   ; \GetDlgItem
0040138C|.6A 00         push    0                                          ; /Enable = FALSE
0040138E|.50            push    eax                                        ; |hWnd
0040138F|.E8 30050000   call    <jmp.&USER32.EnableWindow>               ; \EnableWindow
00401394|.B8 FFFFFFFF   mov   eax, -1
00401399|.EB 27         jmp   short 004013C2
; 下面是破解后request按钮事件处理过程
0040139B|>6A 00         push    0                                          ; /lParam = NULL
0040139D|.68 A1144000   push    004014A1                                 ; |DlgProc = DING_BOY.004014A1
004013A2|.FF75 08       push    dword ptr                         ; |hOwner
004013A5|.6A 66         push    66                                       ; |pTemplate = 66
004013A7|.FF75 F8       push    dword ptr                         ; |hInst
004013AA|.E8 09050000   call    <jmp.&USER32.DialogBoxParamA>            ; \DialogBoxParamA
004013AF|.EB 0F         jmp   short 004013C0
; 下面是 WM_CLOSE 消息处理过程
004013B1|>6A 00         push    0                                          ; /Result = 0; Case 10 (WM_CLOSE) of switch 004012C7
004013B3|.FF75 08       push    dword ptr                         ; |hWnd
004013B6|.E8 15050000   call    <jmp.&USER32.EndDialog>                  ; \EndDialog
004013BB|.31C0          xor   eax, eax
004013BD|.40            inc   eax
004013BE|.EB 02         jmp   short 004013C2
; 默认消息处理过程,只有下面一条指令
004013C0|>31C0          xor   eax, eax                                 ;Default case of switch 00401321
; 函数退出,回收资源并返回
004013C2|>C9            leave004013C3\.C2 1000       retn    10
其中处理 WM_INITDIALOG 消息的代码如下:
; 以下是初始化主对话框界面(WM_INITDIALOG)
004012E9|>837D FC 00    cmp   dword ptr , 0                     ; 变量的初始值需要patch为非0值
004012ED|.75 15         jnz   short 00401304
004012EF|.68 2E040000   push    42E                                        ; /ControlID = 42E (1070.)
004012F4|.FF75 08       push    dword ptr                         ; |hWnd
004012F7|.E8 EC050000   call    <jmp.&USER32.GetDlgItem>                   ; \GetDlgItem
004012FC|.6A 00         push    0                                          ; /Enable = FALSE,破解这里,push 0 改成 push 1
004012FE|.50            push    eax                                        ; |hWnd
004012FF|.E8 C0050000   call    <jmp.&USER32.EnableWindow>               ; \EnableWindow
00401304|>FF75 14       push    dword ptr
00401307|.FF75 10       push    dword ptr
0040130A|.FF75 08       push    dword ptr
0040130D|.E8 A2FFFFFF   call    004012B4
00401312|.83C4 0C       add   esp, 0C
00401315|.31C0          xor   eax, eax
00401317|.40            inc   eax
00401318|.E9 A5000000   jmp   004013C2
可见当局部变量为0时,就将“Request"按钮的状态设置为禁用状态了。
我们只要修改局部变量的值就可以了,来到 DlgProc的最前面,可以看到如下指令:
004012BD|.C745 FC 00000>mov   dword ptr , 0                      ; 初始化为 0

在这里给局部变量初始化为0了,我们将其改成1即可:
004012BD|.C745 FC 00000>mov   dword ptr , 1                     ; win9x下破解,初始值改成 0x01 即可。

这样修改后,再次运行Crackme,界面如下:

可见"Request"按钮已经是可用了。
按”About"可以看About信息如下:

按“Request"按钮,显示如下:

可以看到,破解成功了!
前面説过,脱壳后,该 Crackme 可以在 Windows 10 下运行,但会有一个 BUG 存在,需要修改,而这个 BUG 在 Windows 98 下不存在。
这个BUG就是 DlgProc中的局部变量的初始化问题,Crackme是通过两个寄存器来初始化其值的,指令如下所示:
004012BB|.51                  push    ecx                                        ; 局部变量 isPatched, Win9x 下,ecx == eax == 0;
004012BC|.50                  push    eax                                        ; 局部变量 hInstance, WinNt 下,ecx == eax == DlgProc,会引起BUG,需要修复
004012BD|.C745 FC 00000000      mov   dword ptr , 0

可以看到,CrackMe 通过 push ecx,push eax 两条 push 指令,生成并初始化两个局部变量,第一个就是 ,另一个是 ,其中就是前说的针对"Request"按钮的patch标志,为 0 表示没有 patch,非0则表示已经 patch 成功。
问题出的,通过两个按钮的事件处理过程可以看到,是当作 DialogBoxParamA() 调用的第1 个参数(hInstance)用的,在 Windows 9x 下,Windows 回调 DlgProc 时,会将 eax, ecx 清 0,但是,在 Windows NT(至少win10是这样)系统 下,当windows回调 DlgProc 时,eax, ecx 不为 0,都是保存的 DlgProc 本身函数指针,导致调用 DialogBoxParamA() 时,传入到该函数的 hInstance 参数不正确,因此会导致 DialogBoxParamA() 调用失败,不会弹出对话框,具体表现就是,不管是按"About" ,还是按 "Request" ,程序都没有反应,不会弹出对话框。改正此BUG的方法也很简单,就是修改上面三行指令,将和初始化成正确的值。修改如下,正好用完这三行代码所占用的字节数:
; 以下是"Request"事件处理的破解,并修正在 WinNT 系列下调用 DialogBoxParamA() 失败的问题。
004012BB      6A 01                  push    1                                    ; == 1, "Request"按钮事件分支判断条件改成1
004012BD      66:83E0 00             and   ax, 0                              ;ax 变为0, 在 NT 系列下将 eax 由 DlgProc 改造成 hInstance
004012C1      50                     push    eax                                  ;,并初始化为 hInstance(WinNT下) 或者 0(Win9x下)
004012C2      8BC1                   mov   eax, ecx                           ;恢复 eax,没什么作用,占用字节数


分析完成!!!
下面是脱壳后的文件信息:

Joduska 发表于 2019-6-17 19:00

{:1_921:} 论坛大牛
页: [1]
查看完整版本: 160 个 CrackMe 之 043 -- Ding Boy 之脱壳及patch过程