中级新手(推荐)
【破文标题】riijj Crackme (4) 的详解【对 象】中级新手
【下载地址】http://bbs.pediy.com/upload/file/2004/12/riijj_cm_20041217.zip_783.zip
【破解工具】OD, softice
【保护方式】序号
【任 务】找出序号
【破文作者】riijj
【组 织】没有
【破解声明】我的破解很菜,写这篇东西是给对这个 crackme 有兴趣的兄弟们,分享一下破解心得
【电 邮】[email protected]
【调试环境】w2k
【破解过程】
这个 crackme 是一个自调试程序的例子。当一个程序被调试时,其它程序不可以对它进行调试,这种做法是防止破解者用 ring3 调试器加载。
这篇文章会讨论这个 crackme 使用的方法与结构,希望让不熟悉自调试的兄弟们有一点初部印象。
[ Part I.观察 ]
刚下载了这个 crackme,看见它包含 5 个档案,其中一个是执行档 Crackme4.exe (外表像一只魔鬼),另外有 3 个 sub 档,还有一个 zip 档,它是一张图片的压缩文件,只要破解成功便可以取得解压的密码。
我们尝试用 OD 打开 Crackme4.exe ,只忽略 KERNEL32 内存异常, F9 运行,程序很顺利地打开,似乎这个 crackme 没有设置反调试 (的确是这样)
我们输入注册名字 "riijj" ,序号 "AAAABBBB"
现在开始动手,先试试简单的断点
bp GetWindowTextA
现在输入序号,按下 register,没有反应。
我们怀疑这个程序的真正核心不是在 Crackme4.exe 里,我们现在检查一下,在 OD 里按一下 F12 (暂停程序)
很奇怪,OD 显示程序是停止了,现在身处 ntdll 的 77F82870 ,可是 crackme 依然正常运作,窗口和 resgister 按钮也有反应。我们按 OD 上方的 View > Threads(查看线程),看见这个 crackme4.exe 只有一个线程,现在这个线程已经停止了。
我们打开工作管理员,可以看见 sub1.ooo sub2.ooo 和 sub3.ooo 的名字,很明显它们才是真正运行的东西。
[ Part II.结构分析 ]
我们把 crackme 再新加载,设定断点
bp CreateProcessA
现在 F9 运行
KERNEL32
77E73F8F > 55 PUSH EBP <------ 停在这里
77E73F90 8BEC MOV EBP,ESP
77E73F92 FF75 2C PUSH DWORD PTR SS:
77E73F95 FF75 28 PUSH DWORD PTR SS:
77E73F98 FF75 24 PUSH DWORD PTR SS:
77E73F9B FF75 20 PUSH DWORD PTR SS:
按 Alt + F9 返回 crackme
00401012|. 50 PUSH EAX ; /pStartupinfo
00401013|. FF15 0C404000CALL DWORD PTR DS:[<&KERNEL32.GetStartup>; \GetStartupInfoA
00401019|. 8D4C24 00 LEA ECX,DWORD PTR SS:
0040101D|. 8D5424 10 LEA EDX,DWORD PTR SS:
00401021|. 51 PUSH ECX ; /pProcessInfo
00401022|. 52 PUSH EDX ; |pStartupInfo
00401023|. 6A 00 PUSH 0 ; |CurrentDir = NULL
00401025|. 6A 00 PUSH 0 ; |pEnvironment = NULL
00401027|. 6A 03 PUSH 3 ; |CreationFlags = DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS
00401029|. 6A 00 PUSH 0 ; |InheritHandles = FALSE
0040102B|. 6A 00 PUSH 0 ; |pThreadSecurity = NULL
0040102D|. 6A 00 PUSH 0 ; |pProcessSecurity = NULL
0040102F|. 6A 00 PUSH 0 ; |CommandLine = NULL
00401031|. 68 4C504000 PUSH Crackme4.0040504C ; |ModuleFileName = "sub3.ooo"<---看见这个
00401036|. FF15 08404000CALL DWORD PTR DS:[<&KERNEL32.CreateProc>; \CreateProcessA
0040103C|. 85C0 TEST EAX,EAX <-------我们在这里
0040103E|. 75 09 JNZ SHORT Crackme4.00401049
00401040|. 81C4 B4000000ADD ESP,0B4
00401046|. C2 1000 RETN 10
00401049|> 56 PUSH ESI
0040104A|. 57 PUSH EDI
0040104B|. 68 30504000 PUSH Crackme4.00405030 ; /Title = "Riijj Crackme - 20041217"
00401050|. 6A 00 PUSH 0 ; |Class = 0
00401052|. FF15 A4404000CALL DWORD PTR DS:[<&USER32.FindWindowA>>; \FindWindowA
00401058|. 6A 03 PUSH 3 ; /Flags = SWP_NOSIZE|SWP_NOMOVE
0040105A|. 6A 00 PUSH 0 ; |Height = 0
0040105C|. 6A 00 PUSH 0 ; |Width = 0
0040105E|. 6A 00 PUSH 0 ; |Y = 0
00401060|. 6A 00 PUSH 0 ; |X = 0
00401062|. 6A 00 PUSH 0 ; |InsertAfter = HWND_TOP
00401064|. 50 PUSH EAX ; |hWnd
00401065|. FF15 A0404000CALL DWORD PTR DS:[<&USER32.SetWindowPos>; \SetWindowPos
0040106B|. 8B35 04404000MOV ESI,DWORD PTR DS:[<&KERNEL32.WaitFor>;KERNEL32.WaitForDebugEvent
00401071|. 8B3D 54404000MOV EDI,DWORD PTR DS:[<&KERNEL32.Continu>;KERNEL32.ContinueDebugEvent
00401077|> 8D4424 5C /LEA EAX,DWORD PTR SS:
0040107B|. 6A FF |PUSH -1
这个 crackme4.exe 创建新进程,档案是 sub3.ooo ,以 DEBUG_PROCESS 的方式产生,也就是说现在 crackme4.exe 是它的调试程序。
一般调试程序的方法有两种,分别是以 CreateProcess 产生,或以 DebugActiveProcess 连接到运行中的程序。一般来说调试程序在使用 CreateProcess 后,便会使用 WaitForDebugEvent 来等待被调试者的 debug 事件发生 (例如 exception,进程开始或进程结束等 ) ,在处理事件后,用 ContinueDebugEvent 使被调试的程序继续运行
我们在 0040106B 看见了 WaitForDebugEvent ,当程序执行到这里的时候,便会进入睡眠状态,即使我们停止它,也影响不到 sub3.ooo
我们用 OD 把 sub3.ooo 加载,看看可不可以单独运行。
加载后, F9 运行,果然我们看见了 crackme 的初始画面,可是运行了不久,程序便终止了。
77F8EE0F C2 0800 RETN 8
77F8EE12 803D 0403FD77 00 CMP BYTE PTR DS:,0
77F8EE19 0F85 3E430000 JNZ ntdll.77F9315D
77F8EE1F 834D FC FF OR DWORD PTR SS:,FFFFFFFF
77F8EE23 E8 0F000000 CALL ntdll.77F8EE37
77F8EE28 8B4D F0 MOV ECX,DWORD PTR SS:
OD 的下方写上 Process terminated
我们估计,这个 crackme 在运行的时候产生了某种 exception,使程序运行的时候会把执行权交到父进程里,并在父进程运时做了一些必要工作,如果我们单独把 crackme 的子程序 (例如 sub3.ooo) 运行,便会导致运行不正常。
这时时候,我们把 sub3 重新加载,依旧设断点在 CreateProcess
bp CreateProcessA
F9 运行
果然,断下来了, alt+F9 返回 crackme ,看到
00401013|. FF15 0C404000CALL DWORD PTR DS:[<&KERNEL32.GetStartup>; \GetStartupInfoA
00401019|. 8D4C24 00 LEA ECX,DWORD PTR SS:
0040101D|. 8D5424 10 LEA EDX,DWORD PTR SS:
00401021|. 51 PUSH ECX ; /pProcessInfo
00401022|. 52 PUSH EDX ; |pStartupInfo
00401023|. 6A 00 PUSH 0 ; |CurrentDir = NULL
00401025|. 6A 00 PUSH 0 ; |pEnvironment = NULL
00401027|. 6A 03 PUSH 3 ; |CreationFlags = DEBUG_PROCESS|DEBUG_ONLY_THIS_PROCESS
00401029|. 6A 00 PUSH 0 ; |InheritHandles = FALSE
0040102B|. 6A 00 PUSH 0 ; |pThreadSecurity = NULL
0040102D|. 6A 00 PUSH 0 ; |pProcessSecurity = NULL
0040102F|. 6A 00 PUSH 0 ; |CommandLine = NULL
00401031|. 68 30504000 PUSH sub3.00405030 ; |ModuleFileName = "sub2.ooo"
00401036|. FF15 08404000CALL DWORD PTR DS:[<&KERNEL32.CreateProc>; \CreateProcessA
0040103C|. 85C0 TEST EAX,EAX
0040103E|. 75 09 JNZ SHORT sub3.00401049
00401040|. 81C4 B4000000ADD ESP,0B4
sub3 在调试 sub2,我们估计 sub2 会调试 sub1
现在我们用 OD 加载 sub2.ooo,设断点在 CreateProcessA
bp CreateProcessA
F9 运行
我们断下来了,按 Alt+F9 返回 crackme,我们身处
00401169|. FF15 DC674000CALL DWORD PTR DS:
0040116F|. 8D4C24 10 LEA ECX,DWORD PTR SS:
00401173|. 8D9424 8000000>LEA EDX,DWORD PTR SS:
0040117A|. 51 PUSH ECX
0040117B|. 52 PUSH EDX
0040117C|. 6A 00 PUSH 0
0040117E|. 6A 00 PUSH 0
00401180|. 6A 03 PUSH 3
00401182|. 6A 00 PUSH 0
00401184|. 6A 00 PUSH 0
00401186|. 6A 00 PUSH 0
00401188|. 6A 00 PUSH 0
0040118A|. 68 30604000 PUSH sub2.00406030 ;ASCII "sub1.ooo"
0040118F|. FF15 D4674000CALL DWORD PTR DS:
00401195|. 85C0 TEST EAX,EAX <---------- 我们在这里
00401197|. 0F84 9F020000JE sub2.0040143C
0040119D|. 8B7424 0C MOV ESI,DWORD PTR SS:
004011A1|. 8B7C24 0C MOV EDI,DWORD PTR SS:
004011A5|. BB 01000100 MOV EBX,10001
004011AA|> 8D4424 20 /LEA EAX,DWORD PTR SS:
004011AE|. 6A FF |PUSH -1
004011B0|. 50 |PUSH EAX
004011B1|. FF15 D0674000|CALL DWORD PTR DS:
004011B7|. 8B4424 20 |MOV EAX,DWORD PTR SS:
我们看见了 sub1 ,可是这里比较奇怪的是,我们看不见 CreateProcessA 的名字
我们知道 0040118F 呼叫的 call 是呼叫 CreateProcessA,这表示 这里存放了 CreateProcessA 的 API 位置
API 的呼叫看来是经过处理的
现在我们知道crackme4.exe -> sub3.ooo -> sub2.ooo -> sub1.ooo ,我们估计程序的真正部分在 sub1.ooo 里,我们尝试单独用 OD 加载 sub1
这时候,程序运行了,可是发生了 int 3 异常
00401D60/$ 55 PUSH EBP
00401D61|. 8BEC MOV EBP,ESP
00401D63|. 51 PUSH ECX
00401D64|. 68 00010000 PUSH 100
00401D69|. C745 FC A01440>MOV DWORD PTR SS:,sub1.004014A0
00401D70|. E8 0D020000 CALL sub1.00401F82
00401D75|. 83C4 04 ADD ESP,4
00401D78|. A3 E0674000 MOV DWORD PTR DS:,EAX
00401D7D|. 60 PUSHAD
00401D7E|. B8 FF000000 MOV EAX,0FF
00401D83|. 8B15 E0674000MOV EDX,DWORD PTR DS:
00401D89|. 8B4D FC MOV ECX,DWORD PTR SS:
00401D8C CC INT3 <---------在这里
00401D8D|. 8BE5 MOV ESP,EBP
00401D8F|. 5D POP EBP
00401D90\. C3 RETN
我们想象,这个 int 3 是故意放进去的,目的是产生异常,当异常出现时, sub1 停止运行,sub2 的 WaitForDebugEvent 会返回, sub2 开始运行,对于 WaitForDebugEvent 接收到的异常进行处理,当处理完成后,便使用 ContinueDebugEvent 继续 sub1。
[ Part III.调试 ]
对于这种多进程的防护,使用 softice 是比较直接的。
从取得字符串的地方开始跟踪,Ctrl + D 打开 softice,设断点
:bpx getwindowtexta
离开后,按一下 "Register" 按钮,可是 softice 没有反应,可能这个程序没有使用 GetWindowTextA 来得到字符串。
一般 windows 程序 (除了 delphi 以外),文字方块的取得字符串方法有以下几种 :
1. GetWindowTextA(送出 WM_GETTEXT)
2. GetDlgItemTextA (内里使用 GetWindowTextA )
3. SendMessage 送出 WM_GETTEXT
4. SendMessage 送出 EM_GETLINE
5. 程序拦截 WM_KEYDOWN ,把使用者按键纪录
6. 直接取得文字内存 handle,Lock 后读取
我们尝试在内存搜索字符串, Ctrl+D 打开 softice,
选择 sub1 的领空
:addr sub1
搜索字符串从 0 至 ffffffff
:s 0 L ffffffff "AAAABBBB"
这时候, softice 提示
Pattern found at 0010:0012F5EC (0012F5EC)
在 0012F5EC 发现了序号字符串,我们设定内存断点在那里
:bpm 0012f5ec
离开 softice 后,立即断在
001B:77DFFE62F3A5 REPZ MOVSD
001B:77DFFE64FF7508 PUSH DWORD PTR <---这里
001B:77DFFE678BC8 MOV ECX,EAX
001B:77DFFE6983E103 AND ECX,03
001B:77DFFE6CF3A4 REPZ MOVSB
001B:77DFFE6EE8110D0000 CALL 77E00B84
001B:77DFFE735E POP ESI
001B:77DFFE745F POP EDI
001B:77DFFE758BC3 MOV EAX,EBX
001B:77DFFE775B POP EBX
001B:77DFFE785D POP EBP
001B:77DFFE79C21000 RET 0010
001B:77DFFE7C880C1F MOV ,CL
001B:77DFFE7FEBCD JMP 77DFFE4E
001B:77DFFE8155 PUSH EBP
001B:77DFFE828BEC MOV EBP,ESP
001B:77DFFE8483EC3C SUB ESP,3C
001B:77DFFE8753 PUSH EBX
看看 code window 上方的提示,我们身处 User32!DrawStateA 里,我们看看当前的 call stack
:stack
FrameEBPRetEIP Symbol
0012F56C77E006AC USER32!DrawStateA+1D08
0012F5A877E004EA USER32!EditWndProc+00D5
0012F5CC77DF597E USER32!DrawStateA+238E
0012FDF877FA15EF USER32!PostMessageA+00CA
0012FE7477DF70F3 ntdll!KiUserCallbackDispatcher+0013
0012FEB800401BA0 USER32!GetWindowTextA+0089
0000000000000000 sub1!.text+0BA0
我们可以看见 sub1 呼叫 GetWindowTextA,位置在 00401BA0
到那里看看
:u 00401BA0
001B:00401B84E827FAFFFF CALL 004015B0
001B:00401B898B1534684000 MOV EDX,
001B:00401B8F83C408 ADD ESP,08
001B:00401B926A14 PUSH 14
001B:00401B946860684000 PUSH 00406860
001B:00401B9952 PUSH EDX
001B:00401B9AFF15A4684000 CALL
001B:00401BA0A1A0674000 MOV EAX,<---- 这里
001B:00401BA56A14 PUSH 14
001B:00401BA76880684000 PUSH 00406880
001B:00401BAC50 PUSH EAX
001B:00401BADFF15A4684000 CALL
001B:00401BB360 PUSHAD
001B:00401BB4B800000000 MOV EAX,00000000
001B:00401BB9CC INT 3
001B:00401BBA61 POPAD
001B:00401BBB8B0DAC684000 MOV ECX,
001B:00401BC151 PUSH ECX
001B:00401BC2E879F6FFFF CALL 00401240
001B:00401BC78B15AC684000 MOV EDX,
001B:00401BCD52 PUSH EDX
001B:00401BCEE8DDF9FFFF CALL 004015B0
001B:00401BD383C408 ADD ESP,08
001B:00401BD66A01 PUSH 01
001B:00401BD86A00 PUSH 00
001B:00401BDA6A00 PUSH 00
001B:00401BDC6A00 PUSH 00
001B:00401BDE68C0674000 PUSH 004067C0
往上看,没有找到 GetWindowTextA,只见 CALL 。 很明显 是 GetWindowTextA 的位置,我们尝试过在 GetWindowTextA 下断,可是断不下来,估计断点被程序自行清除了。
[ Part IV. 解开断点清除]
调试器设置普通断点时,在断点处放置 int 3 (0xcc) 来达到中断的效果,如果程序在执行前把 0xcc 恢复,断点便会消失
我们要找出 GetWindowTextA 被存取的一刻,在它的位置设置内存断点
:bpm getwindowtexta
离开 softice 后,我们立即断在
001B:004011AF90 NOP
001B:004011B08B442404 MOV EAX,
001B:004011B456 PUSH ESI
001B:004011B557 PUSH EDI
001B:004011B68B3D48684000 MOV EDI,
001B:004011BC3BC7 CMP EAX,EDI
001B:004011BE7221 JB 004011E1
001B:004011C08B0D50684000 MOV ECX,
001B:004011C68D1439 LEA EDX,
001B:004011C93BC2 CMP EAX,EDX
001B:004011CB7714 JA 004011E1
001B:004011CD8B3540684000 MOV ESI,
001B:004011D38BD1 MOV EDX,ECX
001B:004011D5C1E902 SHR ECX,02
001B:004011D8F3A5 REPZ MOVSD <-------这里
001B:004011DA8BCA MOV ECX,EDX
001B:004011DC83E103 AND ECX,03
001B:004011DFF3A4 REPZ MOVSB
001B:004011E18B3DE8684000 MOV EDI,
001B:004011E73BC7 CMP EAX,EDI
001B:004011E97221 JB 0040120C
这里是 sub1 的领空,奇怪的是,这里对 getwindowtexta 的内容进行读写(REPZ MOVSD , 复制内存 )
你中断的地方可能跟我不相同,如果不相同的话,你可以略过这里的描述,往下找找有没有你中断的地方
我们按一下 F12 到达 call 的返回
001B:0040123F90 NOP
001B:004012408B442404 MOV EAX,
001B:004012448B4801 MOV ECX,
001B:0040124751 PUSH ECX
001B:00401248E863FFFFFF CALL 004011B0
001B:0040124D59 POP ECX <-------这里
001B:0040124EC3 RET
001B:0040124F90 NOP
001B:0040125083EC5C SUB ESP,5C
001B:00401253B9DEFFFFFF MOV ECX,FFFFFFDE
001B:0040125853 PUSH EBX
001B:00401259BAE9FFFFFF MOV EDX,FFFFFFE9
001B:0040125E56 PUSH ESI
001B:0040125F894C241C MOV ,ECX
001B:00401263894C2428 MOV ,ECX
001B:00401267BEEDFFFFFF MOV ESI,FFFFFFED
001B:0040126C89542414 MOV ,EDX
001B:00401270B8DFFFFFFF MOV EAX,FFFFFFDF
我们尝试把 00401248 的一行 nop 掉
:a 00401248
nop 掉 5 个元位
现在离开 softice ,再中断在
001B:004014DF33D2 XOR EDX,EDX
001B:004014E1F3A6 REPZ CMPSB <------这里
001B:004014E37405 JZ 004014EA
001B:004014E51BD2 SBB EDX,EDX
001B:004014E783DAFF SBB EDX,-01
001B:004014EA8BF2 MOV ESI,EDX
001B:004014EC8B15E8684000 MOV EDX,
001B:004014F23BC2 CMP EAX,EDX
001B:004014F4722A JB 00401520
001B:004014F68B0DF0684000 MOV ECX,
001B:004014FC8D3C11 LEA EDI,
001B:004014FF3BC7 CMP EAX,EDI
001B:00401501771D JA 00401520
001B:004015038B3DE0684000 MOV EDI,
001B:004015092BC8 SUB ECX,EAX
001B:0040150B2BFA SUB EDI,EDX
001B:0040150D03CA ADD ECX,EDX
这里是内存比较 (REPZ CMPSB ),程序把备份的内容与现在的内容比较,如果不相同,便可能不进行呼叫或干其它事。
按一下 F12 ,来到
001B:0040156056 PUSH ESI
001B:004015618B742408 MOV ESI,
001B:0040156556 PUSH ESI
001B:00401566E845FFFFFF CALL 004014B0
001B:0040156B83C404 ADD ESP,04<-----这里
001B:0040156E85C0 TEST EAX,EAX <--看见这里有比较
001B:004015707413 JZ 00401585 <--- F8 单步,是跳的
001B:0040157256 PUSH ESI
001B:00401573E838FCFFFF CALL 004011B0
001B:0040157856 PUSH ESI
001B:00401579E832FFFFFF CALL 004014B0
001B:0040157E83C408 ADD ESP,08
001B:0040158185C0 TEST EAX,EAX
001B:0040158375ED JNZ 00401572
001B:0040158580BEE0000000CC CMP BYTE PTR ,CC
001B:0040158C7513 JNZ 004015A1
001B:0040158E56 PUSH ESI
001B:0040158FE81CFCFFFF CALL 004011B0
001B:004015948A86E0000000 MOV AL,
001B:0040159A83C404 ADD ESP,04
001B:0040159D3CCC CMP AL,CC
001B:0040159F74ED JZ 0040158E
001B:004015A1B801000000 MOV EAX,00000001
001B:004015A65E POP ESI
001B:004015A7C3 RET
我们按 F8 单步,发现在没有断点的情况下,JZ 是跳的。我们把 00401566 的 call 修改
:a 00401566
改成 xor eax,eax 和 3 个 nop
现在离开 softice,再次中断在
001B:0040158580BEE0000000CC CMP BYTE PTR ,CC
001B:0040158C7513 JNZ 004015A1 <---这里
001B:0040158E56 PUSH ESI
001B:0040158FE81CFCFFFF CALL 004011B0
001B:004015948A86E0000000 MOV AL,
001B:0040159A83C404 ADD ESP,04
001B:0040159D3CCC CMP AL,CC
001B:0040159F74ED JZ 0040158E
001B:004015A1B801000000 MOV EAX,00000001
001B:004015A65E POP ESI
001B:004015A7C3 RET
这一句CMP BYTE PTR ,CC是把 getwindowtexta 的第一个位跟 0xcc ( int 3) 比较,检查是否被设为断点
我们把 00401585 (CMP) 和 0040158C (JNZ) 两句 nop 掉
:a 00401585
一直 nop 至 0040158C
再离开 softice,又断在
001B:004011AF90 NOP
001B:004011B08B442404 MOV EAX,
001B:004011B456 PUSH ESI
001B:004011B557 PUSH EDI
001B:004011B68B3D48684000 MOV EDI,
001B:004011BC3BC7 CMP EAX,EDI
001B:004011BE7221 JB 004011E1
001B:004011C08B0D50684000 MOV ECX,
001B:004011C68D1439 LEA EDX,
001B:004011C93BC2 CMP EAX,EDX
001B:004011CB7714 JA 004011E1
001B:004011CD8B3540684000 MOV ESI,
001B:004011D38BD1 MOV EDX,ECX
001B:004011D5C1E902 SHR ECX,02
001B:004011D8F3A5 REPZ MOVSD <-------这里
001B:004011DA8BCA MOV ECX,EDX
001B:004011DC83E103 AND ECX,03
001B:004011DFF3A4 REPZ MOVSB
001B:004011E18B3DE8684000 MOV EDI,
001B:004011E73BC7 CMP EAX,EDI
001B:004011E97221 JB 0040120C
我们刚才来过的地方,看来是程序某一处也 call 这里
按一下 F12 返回
001B:0040158D90 NOP
001B:0040158E56 PUSH ESI
001B:0040158FE81CFCFFFF CALL 004011B0
001B:004015948A86E0000000 MOV AL,<--这里
001B:0040159A83C404 ADD ESP,04
001B:0040159D3CCC CMP AL,CC
001B:0040159F74ED JZ 0040158E
001B:004015A1B801000000 MOV EAX,00000001
001B:004015A65E POP ESI
001B:004015A7C3 RET
001B:004015A890 NOP
把 0040158F 的 call nop 掉
:a 0040158F
nop 5 个位
再离开 softice,又断在刚刚的那里
001B:004015948A86E0000000 MOV AL,
001B:0040159A83C404 ADD ESP,04 <--我们在这里
001B:0040159D3CCC CMP AL,CC <-- 把 getwindowtexta 与 0xcc 比较
001B:0040159F74ED JZ 0040158E<-- 如果是 0xcc 便跳
001B:004015A1B801000000 MOV EAX,00000001
001B:004015A65E POP ESI
001B:004015A7C3 RET
001B:004015A890 NOP
今次我们把 00401594 的 mov ,0040159D 的 cmp,和 0040159F 的 jz 全部 nop 掉
现在我们离开 softice,没有再跳出来,成功把检查和恢复断点的地方清除。
尝试下断 getwindowtexta
:bc *
:bpx getwindowtexta
离开 softice,断在
001B:77DF7067C21C00 RET 001C
USER32!GetWindowTextA
001B:77DF706A55 PUSH EBP <--这里
001B:77DF706B8BEC MOV EBP,ESP
001B:77DF706D6AFF PUSH FF
001B:77DF706F68F870DF77 PUSH 77DF70F8
001B:77DF707468B71FE477 PUSH 77E41FB7
001B:77DF707964A100000000 MOV EAX,FS:
001B:77DF707F50 PUSH EAX
001B:77DF708064892500000000 MOV FS:,ESP
程序成功断在 getwindowtexta,现在我们把程序关掉,打开 OD 加载 sub1.ooo,把刚才修改的地方写到 sub1.ooo 里
( sub1.ooo 的名字不可以改 )
[ Part V.分析序号处理 ]
全部修改后,打开 crackme4.exe ,用 softice 下断
bpx getwindowtexta
离开 softice,断在 getwindowtexta 里,
F12 返回,开始跟踪序号
001B:00401B84E827FAFFFF CALL 004015B0
001B:00401B898B1534684000 MOV EDX,
001B:00401B8F83C408 ADD ESP,08
001B:00401B926A14 PUSH 14
001B:00401B946860684000 PUSH 00406860
001B:00401B9952 PUSH EDX
001B:00401B9AFF15A4684000 CALL
001B:00401BA0A1A0674000 MOV EAX,<---- 我们在这里
001B:00401BA56A14 PUSH 14
001B:00401BA76880684000 PUSH 00406880
001B:00401BAC50 PUSH EAX
001B:00401BADFF15A4684000 CALL
001B:00401BB360 PUSHAD
001B:00401BB4B800000000 MOV EAX,00000000
001B:00401BB9CC INT 3
001B:00401BBA61 POPAD
001B:00401BBB8B0DAC684000 MOV ECX,
001B:00401BC151 PUSH ECX
001B:00401BC2E879F6FFFF CALL 00401240
001B:00401BC78B15AC684000 MOV EDX,
001B:00401BCD52 PUSH EDX
我们看一下代码,看不见熟悉的 API 名字,感觉不是很好。
我们知道 是 getwindowtexta,看看下方,在 00401BAD 也呼叫了这里一次,很明显程序在读取注册名字和序号。
我们发现 00401BB9 这一行使用了 INT 3 ,当程序执行 int 3 的时候,异常发生, sub1 停止运行, sub2 将会继续。在 int 3 的前面还有一个 PUSHAD,后面有一个 POPAD,分别是用来保留和恢复 stack
sub1 得到序号字符串后,使用 int 3 来通知 sub2
我们 F8 单步跟踪,跟进了 00401BAD 的 (getwindowtexta)
001B:0098000BC3 RET
001B:0098000CB88A6FDF77 MOV EAX,77DF6F8A<--在这里
001B:0098001105E0000000 ADD EAX,000000E0
001B:0098001650 PUSH EAX
001B:00980017C3 RET
001B:00980018B81368DF77 MOV EAX,77DF6813
001B:0098001D05E0000000 ADD EAX,000000E0
001B:0098002250 PUSH EAX
001B:00980023C3 RET
001B:00980024B85F54DF77 MOV EAX,77DF545F
001B:0098002905E0000000 ADD EAX,000000E0
001B:0098002E50 PUSH EAX
001B:0098002FC3 RET
001B:00980030B84362E077 MOV EAX,77E06243
001B:0098003505E0000000 ADD EAX,000000E0
001B:0098003A50 PUSH EAX
001B:0098003BC3 RET
001B:0098003CB8C52EDF77 MOV EAX,77DF2EC5
001B:0098004105E0000000 ADD EAX,000000E0
001B:0098004650 PUSH EAX
001B:00980047C3 RET
看见这里的结构,我们便明白程序的 API 呼叫,都是通过这里的代码来计算出 API 的位置,再使用 PSUH 和 RET 来实现呼叫
001B:0098000CB88A6FDF77 MOV EAX,77DF6F8A
77DF6F8A 这个常数是 getwindowtexta 的位置 - 0xe0
001B:0098001105E0000000 ADD EAX,000000E0
把位置 + 0xe0,变成 getwindowtexta 的位置
001B:0098001650 PUSH EAX
001B:00980017C3 RET
使 EIP 变成 getwindowtexta 的位置
我们按两次 F12 返回,来到
001B:00401BB360 PUSHAD <---这里
001B:00401BB4B800000000 MOV EAX,00000000
001B:00401BB9CC INT 3
001B:00401BBA61 POPAD
这里程序把 eax 设为 0,再进行 int 3 中断。
我们要在 sub2 的 WaitForDebugEvent 后面下断,先找出它的位置。
用 OD 加载 sub2,在 WaitForDebguEvent 下断
bp WaitForDebugEvent
F9 运行,断在 KERNEL32 的领空里,按 ALt+F9 返回
004011B1|. FF15 D0674000|CALL DWORD PTR DS:
004011B7|. 8B4424 20 |MOV EAX,DWORD PTR SS:<--这里
004011BB|. 48 |DEC EAX ;Switch (cases 1..5)
004011BC|. 74 0E |JE SHORT sub2.004011CC
004011BE|. 83E8 04 |SUB EAX,4
004011C1|. 0F84 75020000|JE sub2.0040143C
004011C7|. E9 48020000 |JMP sub2.00401414
把 004011B7 记下,离开 OD,打开 softice
现在再回到 sub1,下断点 getwindowtexta
离开 softice 后,断在 sub1 里,现在对 sub2 下断点
选择 sub2 的领空
:addr sub2
下断点
:bpx 004011B7
我们让 sub1 继续运行,它将会执行 int3 ,使 sub2 的 WaitForDebugEvent 返回,停在我们的断点上
离开 softice,便断在
001B:004011B1FF15D0674000 CALL
001B:004011B78B442420 MOV EAX,SS:0012FBAC=00000001<--这里
001B:004011BB48 DEC EAX
001B:004011BC740E JZ 004011CC
001B:004011BE83E804 SUB EAX,04
开始 F8 单步跟踪,沿途留意 WaitForDebugEvent 的第一参数 DEBUG_EVENT 结构
(DEBUG_EVENT 结构的 C definition)
typedef struct _DEBUG_EVENT { // de
DWORD dwDebugEventCode; //
DWORD dwProcessId; //
DWORD dwThreadId; //
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT;
001B:004011A5BB01000100 MOV EBX,00010001
001B:004011AA8D442420 LEA EAX, // 是 DEBUG_EVENT
001B:004011AE6AFF PUSH FF // ff 代表永远等待返回 (INFINITE)
001B:004011B050 PUSH EAX
001B:004011B1FF15D0674000 CALL // call WaitForDebugEvent
001B:004011B78B442420 MOV EAX,SS:0012FBAC=00000001
001B:004011BB48 DEC EAX
001B:004011BC740E JZ 004011CC<-这里跳
是 dwDebugEventCode,现在它的值是 1
dwDebugEventCode 的 1 代表 EXCEPTION_DEBUG_EVENT
根据 DEBUG_EVENT 的结构,当 dwDebugEventCode 是 EXCEPTION_DEBUG_EVENT 的时候, union 是 EXCEPTION_DEBUG_INFO Exception
EXCEPTION_DEBUG_INFO 的结构如下 :
typedef struct _EXCEPTION_DEBUG_INFO { // exdi
EXCEPTION_RECORD ExceptionRecord; //
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO;
EXCEPTION_RECORD 的结构 :
typedef struct _EXCEPTION_RECORD { // exr
DWORD ExceptionCode; //
DWORD ExceptionFlags; //
struct _EXCEPTION_RECORD *ExceptionRecord; //
PVOID ExceptionAddress; //
DWORD NumberParameters; //
DWORD ExceptionInformation; //
} EXCEPTION_RECORD;
001B:004011BE83E804 SUB EAX,04
001B:004011C10F8475020000 JZ 0040143C
001B:004011C7E948020000 JMP 00401414
001B:004011CC8B44242C MOV EAX, // 把 ExceptionCode 放到 EAX
001B:004011D03D03000080 CMP EAX,80000003 // 与 STATUS_BREAKPOINT (int 3) 比较
001B:004011D50F872B020000 JA 00401406 <--不跳
001B:004011DB741B JZ 004011F8 <--这里跳
001B:004011DD3D05000140 CMP EAX,40010005
001B:004011E20F8454020000 JZ 0040143C
001B:004011E83D02000080 CMP EAX,80000002 ; STATUS_DATATYPE_MISA
001B:004011ED0F8449020000 JZ 0040143C
001B:004011F3E91C020000 JMP 00401414
001B:004011F88B542414 MOV EDX,<--这里继续
001B:004011FC8D8C24C4000000 LEA ECX,
001B:0040120351 PUSH ECX
001B:0040120452 PUSH EDX
001B:00401205C78424CC000000020001MOV DWORD PTR ,00010002
001B:00401210FF15CC674000 CALL
这个 call 跟进入后,会到达一个类似 sub1 跳转表的地方,几下 F8 走过 retn 后,
发现这个 call 是 GetThreadContext ,用来取得被调试程序的 register 内容
由此我们知道, 是 CONTEXT 结构
x86 CONTEXT 结构如下 :
typedef struct _CONTEXT {
DWORD ContextFlags; //
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;//这个结构的大小是 0x70
DWORD SegGs; //
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp; //
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegisters;
} CONTEXT;
001B:004012168B842474010000 MOV EAX, // 把 context.eax 放入 eax
001B:0040121D3DF0000000 CMP EAX,000000F0 // context.eax 与 f0 比较
001B:004012220F877D010000 JA 004013A5 // 不跳
001B:004012280F843A010000 JZ 00401368 // 不跳
001B:0040122E83E800 SUB EAX,00 // context.eax 与 0 比较
001B:004012310F84B4000000 JZ 004012EB // <---跳
001B:0040123748 DEC EAX
001B:004012380F85D6010000 JNZ 00401414
001B:0040123E6880674000 PUSH 00406780
......
如果 sub1 的 eax 是 0 ,进行 int 3 便来到这里
001B:004012E6E929010000 JMP 00401414
001B:004012EB8B1518674000 MOV EDX, // <--继续edx = 406860
001B:004012F18B442410 MOV EAX, // eax = 28
001B:004012F58D4C240C LEA ECX, // ecx = 12fb90
001B:004012F951 PUSH ECX
001B:004012FA6A15 PUSH 15
001B:004012FC6880674000 PUSH 00406780
001B:0040130152 PUSH EDX
001B:0040130250 PUSH EAX
001B:00401303FF15C0674000 CALL // ReadProcessMemory
001B:004013098B1570674000 MOV EDX, // edx = 406880
001B:0040130F8D4C240C LEA ECX, // ecx = 12fb98
001B:004013138B442410 MOV EAX, // eax = 28
001B:0040131751 PUSH ECX
001B:004013186A15 PUSH 15
001B:0040131A68A0674000 PUSH 004067A0
001B:0040131F52 PUSH EDX
001B:0040132050 PUSH EAX
001B:00401321FF15C0674000 CALL // ReadProcessMemory
001B:004013278B1518674000 MOV EDX,// edx = 406860
001B:0040132D8D4C240C LEA ECX, // ecx = 12fb98
001B:004013318B442410 MOV EAX, // eax = 28
001B:0040133551 PUSH ECX
001B:004013366A15 PUSH 15
001B:004013386840654000 PUSH 00406540
001B:0040133D52 PUSH EDX
001B:0040133E50 PUSH EAX
001B:0040133FFF15C4674000 CALL // WriteProcessMemory
001B:004013458B1570674000 MOV EDX,// edx = 406880
001B:0040134B8D4C240C LEA ECX, // ecx = 12fb98
001B:0040134F8B442410 MOV EAX, // eax = 28
001B:0040135351 PUSH ECX
001B:004013546A15 PUSH 15
001B:004013566858654000 PUSH 00406558
001B:0040135B52 PUSH EDX
001B:0040135C50 PUSH EAX
001B:0040135DFF15C4674000 CALL // WriteProcessMemory
001B:00401363E9AC000000 JMP 00401414
sub2 使用了 2 次 ReadProcessMemory 和 2 次 WriteProcessMemory,对像都是 (0x28),我们尝试找出 所代表的程序,在 softice 里往上寻找
一直到达了这段程序开始的地方 :
001B:0040114E90 NOP
001B:0040114F90 NOP
001B:0040115081EC84030000 SUB ESP,00000384
001B:0040115653 PUSH EBX
001B:0040115756 PUSH ESI
001B:004011588D44247C LEA EAX,
001B:0040115C57 PUSH EDI
001B:0040115D50 PUSH EAX
001B:0040115EC7842484000000440000MOV DWORD PTR ,00000044
001B:00401169FF15DC674000 CALL
001B:0040116F8D4C2410 LEA ECX,<---这里第一次使用
001B:004011738D942480000000 LEA EDX,
001B:0040117A51 PUSH ECX
001B:0040117B52 PUSH EDX
001B:0040117C6A00 PUSH 00
001B:0040117E6A00 PUSH 00
001B:004011806A03 PUSH 03
001B:004011826A00 PUSH 00
001B:004011846A00 PUSH 00
001B:004011866A00 PUSH 00
001B:004011886A00 PUSH 00
001B:0040118A6830604000 PUSH 00406030
001B:0040118FFF15D4674000 CALL //跟踪后发现这是 CreateProcess
001B:0040119585C0 TEST EAX,EAX
001B:004011970F849F020000 JZ 0040143C
由此可见, 是 sub1 的 HANDLE, sub2 在 int 3 发生时对 sub1 读写
我们在这里进行 ReadProcessMemory 后,用 softice 查看 00406780 和 004067A0
:d 00406780
:d 004067A0
发现 00406780 正是我们输入的注册名字 "riijj" ,而 004067A0 是序号 "AAAABBBB"
我们再查看 004067C4 和 00406558,发现它们是一个 DWORD 值,暂时不知道其意义
我们按下 "Register" 按钮的时候,估计 sub1 会通知 sub2 把序号进行检查
现在我们要知道 sub2 的甚么地方对序号进行处理,可以用硬件断点来实现
先暂时把 2 个 ReadProcessMemory 的 call 和相关的 push 记下,然后 nop 掉,使 sub2 不再对缓冲区进行抄写
:a 004012F9
把第一个 push 至 call 的一段 nop 掉
:a 00401317
把第一个 push 至 call 的一段 nop 掉
现在 sub2 的缓冲区内存有 "riijj" 和 "AAAABBBB",我们尝试下硬件断点,看看有没有其它地方对它进行读写
:bpm 00406780
:bpm 004067A0
没有反应,表示没有其它地方读写该区
现在我们按下 Register 按钮,立即断在
001B:0040105083C9FF OR ECX,-01
001B:0040105333D2 XOR EDX,EDX
001B:004010558844241F MOV ,AL
001B:00401059F2AE REPNZ SCASB <----在这里
001B:0040105BF7D1 NOT ECX
001B:0040105D49 DEC ECX
0
开始 F8 单步跟踪
001B:0040105333D2 XOR EDX,EDX
001B:004010558844241F MOV ,AL
001B:00401059F2AE REPNZ SCASB
001B:0040105BF7D1 NOT ECX
001B:0040105D49 DEC ECX
001B:0040105E88542410 MOV ,DL
001B:0040106285C9 TEST ECX,ECX
001B:004010647E0F JLE 00401075
001B:004010660FBE3428 MOVSX ESI,BYTE PTR
001B:0040106A03F2 ADD ESI,EDX
001B:0040106CD1E6 SHL ESI,1
001B:0040106E40 INC EAX
001B:0040106F8BD6 MOV EDX,ESI
001B:004010713BC1 CMP EAX,ECX
001B:004010737CF1 JL 00401066
001B:004010758BC2 MOV EAX,EDX
001B:00401077C1E005 SHL EAX,05
001B:0040107A2BC2 SUB EAX,EDX
001B:0040107C8D0440 LEA EAX,
001B:0040107F8D0442 LEA EAX,
001B:00401082C1E004 SHL EAX,04
001B:0040108503C2 ADD EAX,EDX
001B:0040108733FF XOR EDI,EDI
001B:004010898D3442 LEA ESI,
001B:0040108C8BC7 MOV EAX,EDI
001B:0040108E99 CDQ
001B:0040108FF7F9 IDIV ECX
001B:0040109133C0 XOR EAX,EAX
001B:004010938A042A MOV AL,
001B:0040109685C0 TEST EAX,EAX
001B:004010987E1B JLE 004010B5
001B:0040109A8BD8 MOV EBX,EAX
001B:0040109C8BC6 MOV EAX,ESI
001B:0040109E33D2 XOR EDX,EDX
001B:004010A069C0EF1E0000 IMUL EAX,EAX,00001EEF
001B:004010A683C00D ADD EAX,0D
001B:004010A9BEDF180000 MOV ESI,000018DF
001B:004010AEF7F6 DIV ESI
001B:004010B04B DEC EBX
001B:004010B18BF2 MOV ESI,EDX
001B:004010B375E7 JNZ 0040109C
001B:004010B58BC6 MOV EAX,ESI
001B:004010B733D2 XOR EDX,EDX
001B:004010B9BBFE000000 MOV EBX,000000FE
001B:004010BEF7F3 DIV EBX
001B:004010C0FEC2 INC DL
001B:004010C288543C10 MOV ,DL
001B:004010C647 INC EDI
001B:004010C783FF0F CMP EDI,0F
001B:004010CA7CC0 JL 0040108C
001B:004010CC8D7C2410 LEA EDI,
001B:004010D083C9FF OR ECX,-01
001B:004010D333C0 XOR EAX,EAX
001B:004010D5C6450000 MOV BYTE PTR ,00
001B:004010D9F2AE REPNZ SCASB
001B:004010DBF7D1 NOT ECX
001B:004010DD2BF9 SUB EDI,ECX
001B:004010DF8BD1 MOV EDX,ECX
001B:004010E18BF7 MOV ESI,EDI
001B:004010E38BFD MOV EDI,EBP
001B:004010E5C1E902 SHR ECX,02
001B:004010E8F3A5 REPZ MOVSD
001B:004010EA8BCA MOV ECX,EDX
001B:004010EC83E103 AND ECX,03
001B:004010EFF3A4 REPZ MOVSB
001B:004010F15F POP EDI
001B:004010F25E POP ESI
001B:004010F35D POP EBP
001B:004010F45B POP EBX
001B:004010F583C410 ADD ESP,10
001B:004010F8C3 RET
算法很复杂,使用了很多 REPNZ SCASB 和 REPZ MOVSD,看来是一堆字符串处理,继续进 retn
001B:004012220F877D010000 JA 004013A5
001B:004012280F843A010000 JZ 00401368
001B:0040122E83E800 SUB EAX,00
001B:004012310F84B4000000 JZ 004012EB
001B:0040123748 DEC EAX
001B:004012380F85D6010000 JNZ 00401414
001B:0040123E6880674000 PUSH 00406780
001B:00401243E8E8FDFFFF CALL 00401030
001B:0040124868A0674000 PUSH 004067A0 // <----来到这里
001B:0040124DE8AEFDFFFF CALL 00401000
进去看看
001B:004010008B542404 MOV EDX,
001B:0040100457 PUSH EDI
001B:004010058BFA MOV EDI,EDX
001B:0040100783C9FF OR ECX,-01
001B:0040100A33C0 XOR EAX,EAX
001B:0040100CF2AE REPNZ SCASB
001B:0040100EF7D1 NOT ECX
001B:0040101049 DEC ECX
001B:004010115F POP EDI
001B:0040101285C9 TEST ECX,ECX
001B:004010147E10 JLE 00401026
001B:0040101653 PUSH EBX
001B:004010178A1C10 MOV BL,<--循环
001B:0040101A80F359 XOR BL,59 // 把字符组与 59 xor
001B:0040101D881C10 MOV ,BL
001B:0040102040 INC EAX
001B:004010213BC1 CMP EAX,ECX
001B:004010237CF2 JL 00401017<--往上跳
001B:004010255B POP EBX
001B:00401026C3 RET
001B:0040102790 NOP
这里循环里的 EDX 的值是 4067a0,用 softice 查看
:d 4067a0
发现这里依次序填满了序号 AAAABBBB
ret 返回,继续 F8
001B:004012528B0D1C674000 MOV ECX,
001B:004012588B542418 MOV EDX,
001B:0040125C83C408 ADD ESP,08
001B:0040125F8D44240C LEA EAX,
001B:0040126383C110 ADD ECX,10
001B:0040126650 PUSH EAX
001B:004012676A15 PUSH 15
001B:004012696880674000 PUSH 00406780
001B:0040126E51 PUSH ECX
001B:0040126F52 PUSH EDX
001B:00401270FF15C4674000 CALL // ReadProcessMemory
001B:004012768B0D1C674000 MOV ECX,
001B:0040127C8D44240C LEA EAX,
001B:004012808B542410 MOV EDX,
001B:0040128450 PUSH EAX
001B:004012856A15 PUSH 15
001B:0040128783C138 ADD ECX,38
001B:0040128A68A0674000 PUSH 004067A0
001B:0040128F51 PUSH ECX
001B:0040129052 PUSH EDX
001B:00401291FF15C4674000 CALL // ReadProcessMemory
001B:004012978D8424C4000000 LEA EAX,
001B:0040129E899C24C4000000 MOV ,EBX
001B:004012A58B4C2414 MOV ECX,
001B:004012A950 PUSH EAX
001B:004012AA51 PUSH ECX
001B:004012ABFF15CC674000 CALL // GetThreadContext
我们可以推断 是 context 结构
001B:004012B18D9424C4000000 LEA EDX,//把 context 的位置放入 edx
001B:004012B8C78424C4000000030001MOV DWORD PTR ,00010003
// 把 10003 放进 context 的 ContextFlags 里
001B:004012C38B84247C010000 MOV EAX, // 把 context.Eip 放进 eax
001B:004012CA52 PUSH EDX // *注意这里, esp 改变了
001B:004012CB8BF0 MOV ESI,EAX // 把 context.Eip 放到 esi
001B:004012CD89842478010000 MOV ,EAX //把 context.Eip 放到 context.Eax
001B:004012D48B442418 MOV EAX,// eax = 2c
001B:004012D889BC2480010000 MOV ,EDI // 把 edi 放到 context.Eip,值是 4014a0
001B:004012DF50 PUSH EAX
001B:004012E0FF15C8674000 CALL // SetThreadContext
001B:004012E6E929010000 JMP 00401414
001B:004012EB8B1518674000 MOV EDX,
001B:004012F18B442410 MOV EAX,
001B:004012F58D4C240C LEA ECX,
001B:004012F990 NOP
001B:004012FA90 NOP
001B:004012FB90 NOP
这段程序把 sub1 的 eip 放到 eax 里,并把 eip 改变为 4014a0,这样会强行改变 sub1 流程到 4014a0 处继续
我们现在到 sub1 里,设断点在 4014a0
:addr sub1
:bpx 4014a0
在 sub2 中离开 softice,果然断在刚刚设在 sub1 的断点
估计如果序号是正确的话,这里便会进行判断,显示成功信息
001B:0040149F90 NOP
001B:004014A050 PUSH EAX //<----这里
001B:004014A1E87AFEFFFF CALL 00401320
001B:004014A6C3 RET
001B:004014A7C3 RET
001B:004014A890 NOP
001B:004014A990 NOP
这里把 eax (sub1 原来的 eip) push 到 stack 去,再呼叫 00401320,我们不知它的原因
F8 跟进去
001B:0040131F90 NOP
001B:0040132083EC7C SUB ESP,7C
001B:0040132333C9 XOR ECX,ECX
001B:0040132533C0 XOR EAX,EAX
001B:00401327894C2410 MOV ,ECX
001B:0040132B53 PUSH EBX
001B:0040132C894C2418 MOV ,ECX
001B:0040133089442404 MOV ,EAX
001B:0040133455 PUSH EBP
001B:00401335894C2420 MOV ,ECX
001B:0040133956 PUSH ESI
001B:0040133A89442410 MOV ,EAX
001B:0040133E57 PUSH EDI
001B:0040133F894C242C MOV ,ECX
001B:0040134389442418 MOV ,EAX
001B:00401347B90A000000 MOV ECX,0000000A
001B:0040134C8D7C2464 LEA EDI,
001B:004013508B1DE0674000 MOV EBX,
001B:00401356F3AB REPZ STOSD // 把 清 0
001B:00401358B907000000 MOV ECX,00000007
001B:0040135D8D7C2444 LEA EDI,
001B:004013618944241C MOV ,EAX
001B:0040136533D2 XOR EDX,EDX
001B:00401367F3AB REPZ STOSD // 把 清 0
001B:0040136966AB STOSW
001B:0040136B8D7B10 LEA EDI,
001B:0040136E83C9FF OR ECX,-01
001B:0040137133C0 XOR EAX,EAX
001B:004013738D6C2410 LEA EBP,
001B:00401377F2AE REPNZ SCASB
001B:00401379F7D1 NOT ECX // 估计是计算字符串长度
001B:0040137B2BF9 SUB EDI,ECX
001B:0040137D8BC1 MOV EAX,ECX
001B:0040137F8BF7 MOV ESI,EDI
001B:004013818BFD MOV EDI,EBP
001B:00401383C1E902 SHR ECX,02
001B:00401386F3A5 REPZ MOVSD
001B:004013888BC8 MOV ECX,EAX
001B:0040138A33C0 XOR EAX,EAX
001B:0040138C83E103 AND ECX,03
001B:0040138FF3A4 REPZ MOVSB
001B:004013918D7B38 LEA EDI,
001B:0040139483C9FF OR ECX,-01
001B:00401397F2AE REPNZ SCASB
001B:00401399F7D1 NOT ECX
001B:0040139B2BF9 SUB EDI,ECX
001B:0040139D8D5C2420 LEA EBX,
001B:004013A18BC1 MOV EAX,ECX
001B:004013A38BF7 MOV ESI,EDI
001B:004013A58BFB MOV EDI,EBX
001B:004013A7C1E902 SHR ECX,02
001B:004013AAF3A5 REPZ MOVSD
001B:004013AC8BC8 MOV ECX,EAX
001B:004013AE33C0 XOR EAX,EAX
001B:004013B083E103 AND ECX,03
001B:004013B3F3A4 REPZ MOVSB
001B:004013B58D7C2410 LEA EDI,
001B:004013B983C9FF OR ECX,-01
001B:004013BCF2AE REPNZ SCASB
001B:004013BEF7D1 NOT ECX
001B:004013C049 DEC ECX
001B:004013C185C9 TEST ECX,ECX
001B:004013C37E10 JLE 004013D5
001B:004013C50FBE740410 MOVSX ESI,BYTE PTR
001B:004013CA03F2 ADD ESI,EDX
001B:004013CCD1E6 SHL ESI,1
001B:004013CE40 INC EAX
001B:004013CF8BD6 MOV EDX,ESI
001B:004013D13BC1 CMP EAX,ECX
001B:004013D37CF0 JL 004013C5
001B:004013D58BC2 MOV EAX,EDX
001B:004013D7C1E004 SHL EAX,04
001B:004013DA03C2 ADD EAX,EDX
001B:004013DCC1E004 SHL EAX,04
001B:004013DF03C2 ADD EAX,EDX
001B:004013E133FF XOR EDI,EDI
001B:004013E38D0442 LEA EAX,
001B:004013E68D0480 LEA EAX,
001B:004013E98D3442 LEA ESI,
001B:004013EC8BC7 MOV EAX,EDI
001B:004013EE33DB XOR EBX,EBX
001B:004013F099 CDQ
001B:004013F1F7F9 IDIV ECX
001B:004013F38A5C1410 MOV BL,
001B:004013F785DB TEST EBX,EBX
001B:004013F97E19 JLE 00401414
001B:004013FB8BC6 MOV EAX,ESI
001B:004013FD33D2 XOR EDX,EDX
001B:004013FF69C02F1E0000 IMUL EAX,EAX,00001E2F
001B:0040140583C00D ADD EAX,0D
001B:00401408BEDB190000 MOV ESI,000019DB
001B:0040140DF7F6 DIV ESI
001B:0040140F4B DEC EBX
001B:004014108BF2 MOV ESI,EDX
001B:0040141275E7 JNZ 004013FB
001B:004014148BC6 MOV EAX,ESI
001B:0040141633D2 XOR EDX,EDX
001B:00401418BB1A000000 MOV EBX,0000001A
001B:0040141DF7F3 DIV EBX
001B:0040141F80C241 ADD DL,41
001B:0040142288543C30 MOV ,DL
001B:0040142647 INC EDI
001B:0040142783FF0F CMP EDI,0F
001B:0040142A7CC0 JL 004013EC
001B:0040142C5F POP EDI
001B:0040142D5E POP ESI
001B:0040142E5D POP EBP
001B:0040142FC644243400 MOV BYTE PTR ,00
001B:0040143433C0 XOR EAX,EAX
001B:004014365B POP EBX
001B:004014378A4C0410 MOV CL,
001B:0040143B8A540420 MOV DL,
001B:0040143F80F159 XOR CL,59
001B:004014423ACA CMP CL,DL
001B:00401444754F JNZ 00401495 <-----这里往下跳至 ret,
001B:0040144640 INC EAX
上面部份算法比较复杂,在我们分析前,我们发现程序跟到这里后,便跳往 ret
我们估计这里是检查序号的正确性,我们尝试用 softice 的 u 指令,到达下面的 call ,人手把它们的 API 翻译出来
001B:0040144783F80F CMP EAX,0F
001B:0040144A7CEB JL 00401437
001B:0040144C8D542454 LEA EDX,
001B:0040145052 PUSH EDX
001B:00401451E8FAFDFFFF CALL 00401250
到 00401250
:u 00401250
001B:0040125083EC5C SUB ESP,5C
001B:00401253B9DEFFFFFF MOV ECX,FFFFFFDE
001B:0040125853 PUSH EBX
001B:00401259BAE9FFFFFF MOV EDX,FFFFFFE9
001B:0040125E56 PUSH ESI
001B:0040125F894C241C MOV ,ECX
001B:00401263894C2428 MOV ,ECX
001B:00401267BEEDFFFFFF MOV ESI,FFFFFFED
001B:0040126C89542414 MOV ,EDX
001B:00401270B8DFFFFFFF MOV EAX,FFFFFFDF
001B:004012758954242C MOV ,EDX
001B:00401279B9EFFFFFFF MOV ECX,FFFFFFEF
001B:0040127EBADDFFFFFF MOV EDX,FFFFFFDD
001B:004012838974240C MOV ,ESI
001B:0040128789442418 MOV ,EAX
001B:0040128B8944243C MOV ,EAX
001B:0040128F894C2444 MOV ,ECX
001B:00401293894C2448 MOV ,ECX
001B:004012978974244C MOV ,ESI
001B:0040129B8B742468 MOV ESI,
001B:0040129F89442450 MOV ,EAX
001B:004012A389442454 MOV ,EAX
001B:004012A7C744240800000000 MOV DWORD PTR ,00000000
001B:004012AFC7442410EBFFFFFF MOV DWORD PTR ,FFFFFFEB
001B:004012B7C7442420E0FFFFFF MOV DWORD PTR ,FFFFFFE0
001B:004012BFC7442424F1FFFFFF MOV DWORD PTR ,FFFFFFF1
001B:004012C7C7442430E3FFFFFF MOV DWORD PTR ,FFFFFFE3
001B:004012CFC7442434E4FFFFFF MOV DWORD PTR ,FFFFFFE4
001B:004012D7C744243832000000 MOV DWORD PTR ,00000032
001B:004012DF89542440 MOV ,EDX
001B:004012E3C7442458ECFFFFFF MOV DWORD PTR ,FFFFFFEC
001B:004012EB8954245C MOV ,EDX
001B:004012EFC7442460E6FFFFFF MOV DWORD PTR ,FFFFFFE6
001B:004012F733C0 XOR EAX,EAX
001B:004012F98D4C2408 LEA ECX,
001B:004012FD8A19 MOV BL,
001B:004012FFB252 MOV DL,52
001B:004013012AD3 SUB DL,BL
001B:0040130383C104 ADD ECX,04
001B:00401306881430 MOV ,DL
001B:0040130940 INC EAX
001B:0040130A83F817 CMP EAX,17
001B:0040130D7CEE JL 004012FD
001B:0040130F5E POP ESI
001B:004013105B POP EBX
001B:0040131183C45C ADD ESP,5C
001B:00401314C3 RET
001B:0040131590 NOP
很明显,这里不是 API 跳转表的位置,是程序的其它 call
回到先前的部份
:u 00401456
001B:004014568D442438 LEA EAX,
001B:0040145A50 PUSH EAX
001B:0040145BE890FCFFFF CALL 004010F0
看看 004010F0
:u 004010F0
001B:004010F083EC4C SUB ESP,4C
001B:004010F3B80A000000 MOV EAX,0000000A
001B:004010F8B95A000000 MOV ECX,0000005A
001B:004010FD89442408 MOV ,EAX
001B:0040110189442410 MOV ,EAX
001B:00401105B807000000 MOV EAX,00000007
001B:0040110A53 PUSH EBX
001B:0040110B56 PUSH ESI
001B:0040110C8B742458 MOV ESI,
001B:00401110894C2414 MOV ,ECX
001B:0040111489442420 MOV ,EAX
001B:0040111889442424 MOV ,EAX
001B:0040111C894C243C MOV ,ECX
001B:00401120C744240800000000 MOV DWORD PTR ,00000000
001B:00401128C744240C11000000 MOV DWORD PTR ,00000011
001B:00401130C744241C19000000 MOV DWORD PTR ,00000019
001B:00401138C744242803000000 MOV DWORD PTR ,00000003
001B:00401140C744242C0B000000 MOV DWORD PTR ,0000000B
001B:00401148C744243008000000 MOV DWORD PTR ,00000008
001B:00401150C744243416000000 MOV DWORD PTR ,00000016
001B:00401158C744243840000000 MOV DWORD PTR ,00000040
001B:00401160C744244014000000 MOV DWORD PTR ,00000014
001B:00401168C744244406000000 MOV DWORD PTR ,00000006
001B:00401170C744244810000000 MOV DWORD PTR ,00000010
001B:00401178C744244C05000000 MOV DWORD PTR ,00000005
001B:00401180C744245013000000 MOV DWORD PTR ,00000013
001B:0040118833C0 XOR EAX,EAX
001B:0040118A8D4C2408 LEA ECX,
001B:0040118E8A19 MOV BL,
001B:00401190B27A MOV DL,7A
001B:004011922AD3 SUB DL,BL
001B:0040119483C104 ADD ECX,04
001B:00401197881430 MOV ,DL
001B:0040119A40 INC EAX
001B:0040119B83F813 CMP EAX,13
001B:0040119E7CEE JL 0040118E
001B:004011A05E POP ESI
001B:004011A15B POP EBX
001B:004011A283C44C ADD ESP,4C
001B:004011A5C3 RET
001B:004011A690 NOP
看来也不是 API 跳转表,再回去看看
:u 00401460
001B:004014608B1530684000 MOV EDX,
001B:0040146683C408 ADD ESP,08
001B:004014698D4C2454 LEA ECX,
001B:0040146D6A00 PUSH 00
001B:0040146F68C0654000 PUSH 004065C0
001B:0040147451 PUSH ECX
001B:0040147552 PUSH EDX
001B:00401476FF15A0684000 CALL
看看
:dd 004068A0
得到值 980000
:u 980000
001B:00980000B86464E177 MOV EAX,77E16464
001B:0098000505E0000000 ADD EAX,000000E0
001B:0098000A50 PUSH EAX
001B:0098000BC3 RET
001B:0098000CB80341DF77 MOV EAX,77DF4103
001B:0098001105E0000000 ADD EAX,000000E0
001B:0098001650 PUSH EAX
001B:00980017C3 RET
001B:00980018B8AA57DF77 MOV EAX,77DF57AA
001B:0098001D05E0000000 ADD EAX,000000E0
001B:0098002250 PUSH EAX
001B:00980023C3 RET
这里像 sub2 和 sub1 常用的 API 跳转表
我们人手把 77E16464 的值加上 E0,得到 77e16384
:u 77e16544
USER32!MessageBoxA // <--留意这里
001B:77E1654455 PUSH EBP
001B:77E165458BEC MOV EBP,ESP
001B:77E1654751 PUSH ECX
001B:77E16548833D1893E47700 CMP DWORD PTR ,00
001B:77E1654F0F85EA220100 JNZ 77E2883F
001B:77E165556A00 PUSH 00
001B:77E16557FF7514 PUSH DWORD PTR
001B:77E1655AFF7510 PUSH DWORD PTR
001B:77E1655DFF750C PUSH DWORD PTR
001B:77E16560FF7508 PUSH DWORD PTR
001B:77E16563E804000000 CALL USER32!MessageBoxExA
001B:77E16568C9 LEAVE
001B:77E16569C21000 RET 0010
USER32!MessageBoxExA
001B:77E1656C55 PUSH EBP
001B:77E1656D8BEC MOV EBP,ESP
001B:77E1656F51 PUSH ECX
001B:77E1657051 PUSH ECX
001B:77E1657153 PUSH EBX
001B:77E1657256 PUSH ESI
001B:77E1657357 PUSH EDI
001B:77E1657433FF XOR EDI,EDI
001B:77E1657683CEFF OR ESI,-01
001B:77E16579397D0C CMP ,EDI
001B:77E1657C897DFC MOV ,EDI
001B:77E1657F897DF8 MOV ,EDI
001B:77E165827419 JZ 77E1659D
001B:77E165846A01 PUSH 01
001B:77E165868D45FC LEA EAX,
001B:77E1658956 PUSH ESI
001B:77E1658A50 PUSH EAX
我们看见这里是 KERNEL32.MessageBoxA 的领空,看来这个 call 的 MessageBoxA 的呼叫
这里有可能是跳出成功注册信息的地方,我们现在想办法改变程序的流程,让 sub1 成功到达这里
[ Part VI.最后决战 ]
先把程序关掉,重新打开 crackme,确保上面两处 ReadProcessMemory 的位置可以正常通过,读取注册名字和序号
现在,回到 sub1 的那个跳转看看,打开 softice 到 sub1 下断点直接到达
:addr sub1
:bpx 00401437
按 register,断在
001B:004014378A4C0410 MOV CL,
001B:0040143B8A540420 MOV DL,
001B:0040143F80F159 XOR CL,59
001B:004014423ACA CMP CL,DL
001B:00401444754F JNZ 00401495 <-----这里往下跳至 ret
这个 JNZ 的上方是 CMP,当两者不相等,这个 JNZ 便会跳往完结处
上面的 CL 和 DL 估计是用来储存单位字符组, CL 的值经过 xor 0x59 后,跟 DL 比较
我们在 00401444 设断点,按 register
断下时用 softice 看看
:db EAX+ESP+10
0023:0012FBAC 03 0C 09 0B 1B 1F 15 0A-1A 1A 14 14 13 11 0A 00................
再看
:db EAX+ESP+20
0023:0012FBBC 5A 55 50 52 42 46 4C 53-43 43 4D 4D 4A 48 53 00ZUPRBFLSCCMMJHS.
这是一行特别的字符串"ZUPRBFLSCCMMJHS"
这样看来,假如所有 CMP 都是相等的话,便会注册成功
我们试把 ZUPRBFLSCCMMJHS 用作序号来注册,使用未经修改的 crackme (包括 sub3 sub2 sub1)
果然,当我们按下 register 的时候,跳出了成功信息
Registration successful
并而出现 zip 文件的密码
ftjug
终于检到正确的序号了。
[ Part VII.总结 ]
这个 crackme 4 是我的一个自调试实试品,它的结构以 int 3 的手法作为阻碍破解者的主要手段,
由于我写这篇破文时没有看源码,尽量以破解者的角度出发,所以破解起来是有很多不足之处,手法
不够高明,我知道一些大侠们把 sub1 修改成独自运行来使用 OD 加载,这种无疑是较高明的手法。
这个程序的一些算法部份没有很详细地描述,如果读者有兴趣的话,相信跟踪后发觉并不艰辛
sub2 把注册名字的第一次转换 :
void encode(char *buf)
{
int len;
DWORD seed;
int i, j;
DWORD affect = 0;
char bufout = {0};
len = strlen(buf);
seed = 5987;
for( i=0; i<len; i++)
{
affect = (affect + buf) * 2;
}
seed = seed * affect;
for( i=0; i<15; i++)
{
for( j=0; j< (unsigned char)buf; j++)
{
seed = ((seed * 7919) + 13) % 6367;
}
bufout = 1 + (unsigned char)(seed % 254);
}
buf = 0;
strcpy(buf, bufout);
}
第二次转换:
void encode2(char *buf)
{
int len;
int i;
len = strlen(buf);
for( i=0; i<len; i++)
{
buf = buf ^ 0x59;
}
}
sub1 的算法与 sub2 的第一次算法是相同的,看懂后很容易明白。
这个 crackme 的制作用了两星期,读者可能会觉得一些地方不够完美,例如为甚么整个序号
处理只使用了 sub2 和 sub1,却没有使用 sub3 和 crackme4 的主体 ?我在设计也觉得不够强硬,
但是它的复杂性已经很大,我在修正了多个 bug 后,已经没有气力了。
希望这篇文章对初入门的兄弟有用,这篇文章的后半部分使用了 softice ,对于不熟识 softice
操作的兄弟可能会觉得十分不方便,可是在现实中,我们很多时候也是**使用 softice 来应付
一些特别困难的情况。 哗!很长哦!慢慢看! 太长了!^_^,要好好研究!
页:
[1]