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,没什么作用,占用字节数
分析完成!!!
下面是脱壳后的文件信息:
{:1_921:} 论坛大牛
页:
[1]