【破文标题】riijj Crackme (4) 的详解
【对 象】中级新手
【下载地址】http://bbs.pediy.com/upload/file/2004/12/riijj_cm_20041217.zip_783.zip
【破解工具】OD, softice
【保护方式】序号
【任 务】找出序号
【破文作者】riijj
【组 织】没有
【破解声明】我的破解很菜,写这篇东西是给对这个 crackme 有兴趣的兄弟们,分享一下破解心得
【电 邮】je6543@yahoo.com
【调试环境】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:[EBP+2C]
77E73F95 FF75 28 PUSH DWORD PTR SS:[EBP+28]
77E73F98 FF75 24 PUSH DWORD PTR SS:[EBP+24]
77E73F9B FF75 20 PUSH DWORD PTR SS:[EBP+20]
按 Alt + F9 返回 crackme
00401012 |. 50 PUSH EAX ; /pStartupinfo
00401013 |. FF15 0C404000 CALL DWORD PTR DS:[<&KERNEL32.GetStartup>; \GetStartupInfoA
00401019 |. 8D4C24 00 LEA ECX,DWORD PTR SS:[ESP]
0040101D |. 8D5424 10 LEA EDX,DWORD PTR SS:[ESP+10]
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 08404000 CALL DWORD PTR DS:[<&KERNEL32.CreateProc>; \CreateProcessA
0040103C |. 85C0 TEST EAX,EAX <-------我们在这里
0040103E |. 75 09 JNZ SHORT Crackme4.00401049
00401040 |. 81C4 B4000000 ADD 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 A4404000 CALL 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 A0404000 CALL DWORD PTR DS:[<&USER32.SetWindowPos>; \SetWindowPos
0040106B |. 8B35 04404000 MOV ESI,DWORD PTR DS:[<&KERNEL32.WaitFor>; KERNEL32.WaitForDebugEvent
00401071 |. 8B3D 54404000 MOV EDI,DWORD PTR DS:[<&KERNEL32.Continu>; KERNEL32.ContinueDebugEvent
00401077 |> 8D4424 5C /LEA EAX,DWORD PTR SS:[ESP+5C]
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:[77FD0304],0
77F8EE19 0F85 3E430000 JNZ ntdll.77F9315D
77F8EE1F 834D FC FF OR DWORD PTR SS:[EBP-4],FFFFFFFF
77F8EE23 E8 0F000000 CALL ntdll.77F8EE37
77F8EE28 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10]
OD 的下方写上 Process terminated
我们估计,这个 crackme 在运行的时候产生了某种 exception,使程序运行的时候会把执行权交到父进程里,并在父进程运时做了一些必要工作,如果我们单独把 crackme 的子程序 (例如 sub3.ooo) 运行,便会导致运行不正常。
这时时候,我们把 sub3 重新加载,依旧设断点在 CreateProcess
bp CreateProcessA
F9 运行
果然,断下来了, alt+F9 返回 crackme ,看到
00401013 |. FF15 0C404000 CALL DWORD PTR DS:[<&KERNEL32.GetStartup>; \GetStartupInfoA
00401019 |. 8D4C24 00 LEA ECX,DWORD PTR SS:[ESP]
0040101D |. 8D5424 10 LEA EDX,DWORD PTR SS:[ESP+10]
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 08404000 CALL DWORD PTR DS:[<&KERNEL32.CreateProc>; \CreateProcessA
0040103C |. 85C0 TEST EAX,EAX
0040103E |. 75 09 JNZ SHORT sub3.00401049
00401040 |. 81C4 B4000000 ADD ESP,0B4
sub3 在调试 sub2,我们估计 sub2 会调试 sub1
现在我们用 OD 加载 sub2.ooo,设断点在 CreateProcessA
bp CreateProcessA
F9 运行
我们断下来了,按 Alt+F9 返回 crackme,我们身处
00401169 |. FF15 DC674000 CALL DWORD PTR DS:[4067DC]
0040116F |. 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+10]
00401173 |. 8D9424 8000000>LEA EDX,DWORD PTR SS:[ESP+80]
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 D4674000 CALL DWORD PTR DS:[4067D4]
00401195 |. 85C0 TEST EAX,EAX <---------- 我们在这里
00401197 |. 0F84 9F020000 JE sub2.0040143C
0040119D |. 8B7424 0C MOV ESI,DWORD PTR SS:[ESP+C]
004011A1 |. 8B7C24 0C MOV EDI,DWORD PTR SS:[ESP+C]
004011A5 |. BB 01000100 MOV EBX,10001
004011AA |> 8D4424 20 /LEA EAX,DWORD PTR SS:[ESP+20]
004011AE |. 6A FF |PUSH -1
004011B0 |. 50 |PUSH EAX
004011B1 |. FF15 D0674000 |CALL DWORD PTR DS:[4067D0]
004011B7 |. 8B4424 20 |MOV EAX,DWORD PTR SS:[ESP+20]
我们看见了 sub1 ,可是这里比较奇怪的是,我们看不见 CreateProcessA 的名字
我们知道 0040118F 呼叫的 call 是呼叫 CreateProcessA,这表示 [4067D4] 这里存放了 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:[EBP-4],sub1.004014A0
00401D70 |. E8 0D020000 CALL sub1.00401F82
00401D75 |. 83C4 04 ADD ESP,4
00401D78 |. A3 E0674000 MOV DWORD PTR DS:[4067E0],EAX
00401D7D |. 60 PUSHAD
00401D7E |. B8 FF000000 MOV EAX,0FF
00401D83 |. 8B15 E0674000 MOV EDX,DWORD PTR DS:[4067E0]
00401D89 |. 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
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:77DFFE62 F3A5 REPZ MOVSD
001B:77DFFE64 FF7508 PUSH DWORD PTR [EBP+08] <---这里
001B:77DFFE67 8BC8 MOV ECX,EAX
001B:77DFFE69 83E103 AND ECX,03
001B:77DFFE6C F3A4 REPZ MOVSB
001B:77DFFE6E E8110D0000 CALL 77E00B84
001B:77DFFE73 5E POP ESI
001B:77DFFE74 5F POP EDI
001B:77DFFE75 8BC3 MOV EAX,EBX
001B:77DFFE77 5B POP EBX
001B:77DFFE78 5D POP EBP
001B:77DFFE79 C21000 RET 0010
001B:77DFFE7C 880C1F MOV [EBX+EDI],CL
001B:77DFFE7F EBCD JMP 77DFFE4E
001B:77DFFE81 55 PUSH EBP
001B:77DFFE82 8BEC MOV EBP,ESP
001B:77DFFE84 83EC3C SUB ESP,3C
001B:77DFFE87 53 PUSH EBX
看看 code window 上方的提示,我们身处 User32!DrawStateA 里,我们看看当前的 call stack
:stack
FrameEBP RetEIP Symbol
0012F56C 77E006AC USER32!DrawStateA+1D08
0012F5A8 77E004EA USER32!EditWndProc+00D5
0012F5CC 77DF597E USER32!DrawStateA+238E
0012FDF8 77FA15EF USER32!PostMessageA+00CA
0012FE74 77DF70F3 ntdll!KiUserCallbackDispatcher+0013
0012FEB8 00401BA0 USER32!GetWindowTextA+0089
00000000 00000000 sub1!.text+0BA0
我们可以看见 sub1 呼叫 GetWindowTextA,位置在 00401BA0
到那里看看
:u 00401BA0
001B:00401B84 E827FAFFFF CALL 004015B0
001B:00401B89 8B1534684000 MOV EDX,[00406834]
001B:00401B8F 83C408 ADD ESP,08
001B:00401B92 6A14 PUSH 14
001B:00401B94 6860684000 PUSH 00406860
001B:00401B99 52 PUSH EDX
001B:00401B9A FF15A4684000 CALL [004068A4]
001B:00401BA0 A1A0674000 MOV EAX,[004067A0] <---- 这里
001B:00401BA5 6A14 PUSH 14
001B:00401BA7 6880684000 PUSH 00406880
001B:00401BAC 50 PUSH EAX
001B:00401BAD FF15A4684000 CALL [004068A4]
001B:00401BB3 60 PUSHAD
001B:00401BB4 B800000000 MOV EAX,00000000
001B:00401BB9 CC INT 3
001B:00401BBA 61 POPAD
001B:00401BBB 8B0DAC684000 MOV ECX,[004068AC]
001B:00401BC1 51 PUSH ECX
001B:00401BC2 E879F6FFFF CALL 00401240
001B:00401BC7 8B15AC684000 MOV EDX,[004068AC]
001B:00401BCD 52 PUSH EDX
001B:00401BCE E8DDF9FFFF CALL 004015B0
001B:00401BD3 83C408 ADD ESP,08
001B:00401BD6 6A01 PUSH 01
001B:00401BD8 6A00 PUSH 00
001B:00401BDA 6A00 PUSH 00
001B:00401BDC 6A00 PUSH 00
001B:00401BDE 68C0674000 PUSH 004067C0
往上看,没有找到 GetWindowTextA,只见 CALL [004068A4]。 很明显 [004068A4] 是 GetWindowTextA 的位置,我们尝试过在 GetWindowTextA 下断,可是断不下来,估计断点被程序自行清除了。
[ Part IV. 解开断点清除]
调试器设置普通断点时,在断点处放置 int 3 (0xcc) 来达到中断的效果,如果程序在执行前把 0xcc 恢复,断点便会消失
我们要找出 GetWindowTextA 被存取的一刻,在它的位置设置内存断点
:bpm getwindowtexta
离开 softice 后,我们立即断在
001B:004011AF 90 NOP
001B:004011B0 8B442404 MOV EAX,[ESP+04]
001B:004011B4 56 PUSH ESI
001B:004011B5 57 PUSH EDI
001B:004011B6 8B3D48684000 MOV EDI,[00406848]
001B:004011BC 3BC7 CMP EAX,EDI
001B:004011BE 7221 JB 004011E1
001B:004011C0 8B0D50684000 MOV ECX,[00406850]
001B:004011C6 8D1439 LEA EDX,[EDI+ECX]
001B:004011C9 3BC2 CMP EAX,EDX
001B:004011CB 7714 JA 004011E1
001B:004011CD 8B3540684000 MOV ESI,[00406840]
001B:004011D3 8BD1 MOV EDX,ECX
001B:004011D5 C1E902 SHR ECX,02
001B:004011D8 F3A5 REPZ MOVSD <-------这里
001B:004011DA 8BCA MOV ECX,EDX
001B:004011DC 83E103 AND ECX,03
001B:004011DF F3A4 REPZ MOVSB
001B:004011E1 8B3DE8684000 MOV EDI,[004068E8]
001B:004011E7 3BC7 CMP EAX,EDI
001B:004011E9 7221 JB 0040120C
这里是 sub1 的领空,奇怪的是,这里对 getwindowtexta 的内容进行读写 (REPZ MOVSD , 复制内存 )
你中断的地方可能跟我不相同,如果不相同的话,你可以略过这里的描述,往下找找有没有你中断的地方
我们按一下 F12 到达 call 的返回
001B:0040123F 90 NOP
001B:00401240 8B442404 MOV EAX,[ESP+04]
001B:00401244 8B4801 MOV ECX,[EAX+01]
001B:00401247 51 PUSH ECX
001B:00401248 E863FFFFFF CALL 004011B0
001B:0040124D 59 POP ECX <-------这里
001B:0040124E C3 RET
001B:0040124F 90 NOP
001B:00401250 83EC5C SUB ESP,5C
001B:00401253 B9DEFFFFFF MOV ECX,FFFFFFDE
001B:00401258 53 PUSH EBX
001B:00401259 BAE9FFFFFF MOV EDX,FFFFFFE9
001B:0040125E 56 PUSH ESI
001B:0040125F 894C241C MOV [ESP+1C],ECX
001B:00401263 894C2428 MOV [ESP+28],ECX
001B:00401267 BEEDFFFFFF MOV ESI,FFFFFFED
001B:0040126C 89542414 MOV [ESP+14],EDX
001B:00401270 B8DFFFFFFF MOV EAX,FFFFFFDF
我们尝试把 00401248 的一行 nop 掉
:a 00401248
nop 掉 5 个元位
现在离开 softice ,再中断在
001B:004014DF 33D2 XOR EDX,EDX
001B:004014E1 F3A6 REPZ CMPSB <------这里
001B:004014E3 7405 JZ 004014EA
001B:004014E5 1BD2 SBB EDX,EDX
001B:004014E7 83DAFF SBB EDX,-01
001B:004014EA 8BF2 MOV ESI,EDX
001B:004014EC 8B15E8684000 MOV EDX,[004068E8]
001B:004014F2 3BC2 CMP EAX,EDX
001B:004014F4 722A JB 00401520
001B:004014F6 8B0DF0684000 MOV ECX,[004068F0]
001B:004014FC 8D3C11 LEA EDI,[EDX+ECX]
001B:004014FF 3BC7 CMP EAX,EDI
001B:00401501 771D JA 00401520
001B:00401503 8B3DE0684000 MOV EDI,[004068E0]
001B:00401509 2BC8 SUB ECX,EAX
001B:0040150B 2BFA SUB EDI,EDX
001B:0040150D 03CA ADD ECX,EDX
这里是内存比较 (REPZ CMPSB ),程序把备份的内容与现在的内容比较,如果不相同,便可能不进行呼叫或干其它事。
按一下 F12 ,来到
001B:00401560 56 PUSH ESI
001B:00401561 8B742408 MOV ESI,[ESP+08]
001B:00401565 56 PUSH ESI
001B:00401566 E845FFFFFF CALL 004014B0
001B:0040156B 83C404 ADD ESP,04 <-----这里
001B:0040156E 85C0 TEST EAX,EAX <--看见这里有比较
001B:00401570 7413 JZ 00401585 <--- F8 单步,是跳的
001B:00401572 56 PUSH ESI
001B:00401573 E838FCFFFF CALL 004011B0
001B:00401578 56 PUSH ESI
001B:00401579 E832FFFFFF CALL 004014B0
001B:0040157E 83C408 ADD ESP,08
001B:00401581 85C0 TEST EAX,EAX
001B:00401583 75ED JNZ 00401572
001B:00401585 80BEE0000000CC CMP BYTE PTR [ESI+000000E0],CC
001B:0040158C 7513 JNZ 004015A1
001B:0040158E 56 PUSH ESI
001B:0040158F E81CFCFFFF CALL 004011B0
001B:00401594 8A86E0000000 MOV AL,[ESI+000000E0]
001B:0040159A 83C404 ADD ESP,04
001B:0040159D 3CCC CMP AL,CC
001B:0040159F 74ED JZ 0040158E
001B:004015A1 B801000000 MOV EAX,00000001
001B:004015A6 5E POP ESI
001B:004015A7 C3 RET
我们按 F8 单步,发现在没有断点的情况下,JZ 是跳的。我们把 00401566 的 call 修改
:a 00401566
改成 xor eax,eax 和 3 个 nop
现在离开 softice,再次中断在
001B:00401585 80BEE0000000CC CMP BYTE PTR [ESI+000000E0],CC
001B:0040158C 7513 JNZ 004015A1 <---这里
001B:0040158E 56 PUSH ESI
001B:0040158F E81CFCFFFF CALL 004011B0
001B:00401594 8A86E0000000 MOV AL,[ESI+000000E0]
001B:0040159A 83C404 ADD ESP,04
001B:0040159D 3CCC CMP AL,CC
001B:0040159F 74ED JZ 0040158E
001B:004015A1 B801000000 MOV EAX,00000001
001B:004015A6 5E POP ESI
001B:004015A7 C3 RET
这一句 CMP BYTE PTR [ESI+000000E0],CC 是把 getwindowtexta 的第一个位跟 0xcc ( int 3) 比较,检查是否被设为断点
我们把 00401585 (CMP) 和 0040158C (JNZ) 两句 nop 掉
:a 00401585
一直 nop 至 0040158C
再离开 softice,又断在
001B:004011AF 90 NOP
001B:004011B0 8B442404 MOV EAX,[ESP+04]
001B:004011B4 56 PUSH ESI
001B:004011B5 57 PUSH EDI
001B:004011B6 8B3D48684000 MOV EDI,[00406848]
001B:004011BC 3BC7 CMP EAX,EDI
001B:004011BE 7221 JB 004011E1
001B:004011C0 8B0D50684000 MOV ECX,[00406850]
001B:004011C6 8D1439 LEA EDX,[EDI+ECX]
001B:004011C9 3BC2 CMP EAX,EDX
001B:004011CB 7714 JA 004011E1
001B:004011CD 8B3540684000 MOV ESI,[00406840]
001B:004011D3 8BD1 MOV EDX,ECX
001B:004011D5 C1E902 SHR ECX,02
001B:004011D8 F3A5 REPZ MOVSD <-------这里
001B:004011DA 8BCA MOV ECX,EDX
001B:004011DC 83E103 AND ECX,03
001B:004011DF F3A4 REPZ MOVSB
001B:004011E1 8B3DE8684000 MOV EDI,[004068E8]
001B:004011E7 3BC7 CMP EAX,EDI
001B:004011E9 7221 JB 0040120C
我们刚才来过的地方,看来是程序某一处也 call 这里
按一下 F12 返回
001B:0040158D 90 NOP
001B:0040158E 56 PUSH ESI
001B:0040158F E81CFCFFFF CALL 004011B0
001B:00401594 8A86E0000000 MOV AL,[ESI+000000E0] <--这里
001B:0040159A 83C404 ADD ESP,04
001B:0040159D 3CCC CMP AL,CC
001B:0040159F 74ED JZ 0040158E
001B:004015A1 B801000000 MOV EAX,00000001
001B:004015A6 5E POP ESI
001B:004015A7 C3 RET
001B:004015A8 90 NOP
把 0040158F 的 call nop 掉
:a 0040158F
nop 5 个位
再离开 softice,又断在刚刚的那里
001B:00401594 8A86E0000000 MOV AL,[ESI+000000E0]
001B:0040159A 83C404 ADD ESP,04 <--我们在这里
001B:0040159D 3CCC CMP AL,CC <-- 把 getwindowtexta 与 0xcc 比较
001B:0040159F 74ED JZ 0040158E <-- 如果是 0xcc 便跳
001B:004015A1 B801000000 MOV EAX,00000001
001B:004015A6 5E POP ESI
001B:004015A7 C3 RET
001B:004015A8 90 NOP
今次我们把 00401594 的 mov ,0040159D 的 cmp,和 0040159F 的 jz 全部 nop 掉
现在我们离开 softice,没有再跳出来,成功把检查和恢复断点的地方清除。
尝试下断 getwindowtexta
:bc *
:bpx getwindowtexta
离开 softice,断在
001B:77DF7067 C21C00 RET 001C
USER32!GetWindowTextA
001B:77DF706A 55 PUSH EBP <--这里
001B:77DF706B 8BEC MOV EBP,ESP
001B:77DF706D 6AFF PUSH FF
001B:77DF706F 68F870DF77 PUSH 77DF70F8
001B:77DF7074 68B71FE477 PUSH 77E41FB7
001B:77DF7079 64A100000000 MOV EAX,FS:[00000000]
001B:77DF707F 50 PUSH EAX
001B:77DF7080 64892500000000 MOV FS:[00000000],ESP
程序成功断在 getwindowtexta,现在我们把程序关掉,打开 OD 加载 sub1.ooo,把刚才修改的地方写到 sub1.ooo 里
( sub1.ooo 的名字不可以改 )
[ Part V. 分析序号处理 ]
全部修改后,打开 crackme4.exe ,用 softice 下断
bpx getwindowtexta
离开 softice,断在 getwindowtexta 里,
F12 返回,开始跟踪序号
001B:00401B84 E827FAFFFF CALL 004015B0
001B:00401B89 8B1534684000 MOV EDX,[00406834]
001B:00401B8F 83C408 ADD ESP,08
001B:00401B92 6A14 PUSH 14
001B:00401B94 6860684000 PUSH 00406860
001B:00401B99 52 PUSH EDX
001B:00401B9A FF15A4684000 CALL [004068A4]
001B:00401BA0 A1A0674000 MOV EAX,[004067A0] <---- 我们在这里
001B:00401BA5 6A14 PUSH 14
001B:00401BA7 6880684000 PUSH 00406880
001B:00401BAC 50 PUSH EAX
001B:00401BAD FF15A4684000 CALL [004068A4]
001B:00401BB3 60 PUSHAD
001B:00401BB4 B800000000 MOV EAX,00000000
001B:00401BB9 CC INT 3
001B:00401BBA 61 POPAD
001B:00401BBB 8B0DAC684000 MOV ECX,[004068AC]
001B:00401BC1 51 PUSH ECX
001B:00401BC2 E879F6FFFF CALL 00401240
001B:00401BC7 8B15AC684000 MOV EDX,[004068AC]
001B:00401BCD 52 PUSH EDX
我们看一下代码,看不见熟悉的 API 名字,感觉不是很好。
我们知道 [004068A4] 是 getwindowtexta,看看下方,在 00401BAD 也呼叫了这里一次,很明显程序在读取注册名字和序号。
我们发现 00401BB9 这一行使用了 INT 3 ,当程序执行 int 3 的时候,异常发生, sub1 停止运行, sub2 将会继续。在 int 3 的前面还有一个 PUSHAD,后面有一个 POPAD,分别是用来保留和恢复 stack
sub1 得到序号字符串后,使用 int 3 来通知 sub2
我们 F8 单步跟踪,跟进了 00401BAD 的 [004068A4] (getwindowtexta)
001B:0098000B C3 RET
001B:0098000C B88A6FDF77 MOV EAX,77DF6F8A <--在这里
001B:00980011 05E0000000 ADD EAX,000000E0
001B:00980016 50 PUSH EAX
001B:00980017 C3 RET
001B:00980018 B81368DF77 MOV EAX,77DF6813
001B:0098001D 05E0000000 ADD EAX,000000E0
001B:00980022 50 PUSH EAX
001B:00980023 C3 RET
001B:00980024 B85F54DF77 MOV EAX,77DF545F
001B:00980029 05E0000000 ADD EAX,000000E0
001B:0098002E 50 PUSH EAX
001B:0098002F C3 RET
001B:00980030 B84362E077 MOV EAX,77E06243
001B:00980035 05E0000000 ADD EAX,000000E0
001B:0098003A 50 PUSH EAX
001B:0098003B C3 RET
001B:0098003C B8C52EDF77 MOV EAX,77DF2EC5
001B:00980041 05E0000000 ADD EAX,000000E0
001B:00980046 50 PUSH EAX
001B:00980047 C3 RET
看见这里的结构,我们便明白程序的 API 呼叫,都是通过这里的代码来计算出 API 的位置,再使用 PSUH 和 RET 来实现呼叫
001B:0098000C B88A6FDF77 MOV EAX,77DF6F8A
77DF6F8A 这个常数是 getwindowtexta 的位置 - 0xe0
001B:00980011 05E0000000 ADD EAX,000000E0
把位置 + 0xe0,变成 getwindowtexta 的位置
001B:00980016 50 PUSH EAX
001B:00980017 C3 RET
使 EIP 变成 getwindowtexta 的位置
我们按两次 F12 返回,来到
001B:00401BB3 60 PUSHAD <---这里
001B:00401BB4 B800000000 MOV EAX,00000000
001B:00401BB9 CC INT 3
001B:00401BBA 61 POPAD
这里程序把 eax 设为 0,再进行 int 3 中断。
我们要在 sub2 的 WaitForDebugEvent 后面下断,先找出它的位置。
用 OD 加载 sub2,在 WaitForDebguEvent 下断
bp WaitForDebugEvent
F9 运行,断在 KERNEL32 的领空里,按 ALt+F9 返回
004011B1 |. FF15 D0674000 |CALL DWORD PTR DS:[4067D0]
004011B7 |. 8B4424 20 |MOV EAX,DWORD PTR SS:[ESP+20] <--这里
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:004011B1 FF15D0674000 CALL [004067D0]
001B:004011B7 8B442420 MOV EAX,[ESP+20] SS:0012FBAC=00000001 <--这里
001B:004011BB 48 DEC EAX
001B:004011BC 740E JZ 004011CC
001B:004011BE 83E804 SUB EAX,04
开始 F8 单步跟踪,沿途留意 WaitForDebugEvent 的第一参数 DEBUG_EVENT 结构
(DEBUG_EVENT 结构的 C definition)
typedef struct _DEBUG_EVENT { // de
DWORD dwDebugEventCode; // [ESP+20]
DWORD dwProcessId; // [ESP+24]
DWORD dwThreadId; // [ESP+28]
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:004011A5 BB01000100 MOV EBX,00010001
001B:004011AA 8D442420 LEA EAX,[ESP+20] // [ESP+20] 是 DEBUG_EVENT
001B:004011AE 6AFF PUSH FF // ff 代表永远等待返回 (INFINITE)
001B:004011B0 50 PUSH EAX
001B:004011B1 FF15D0674000 CALL [004067D0] // call WaitForDebugEvent
001B:004011B7 8B442420 MOV EAX,[ESP+20] SS:0012FBAC=00000001
001B:004011BB 48 DEC EAX
001B:004011BC 740E JZ 004011CC <-这里跳
[ESP+20] 是 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; // [ESP+2C]
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO;
EXCEPTION_RECORD 的结构 :
typedef struct _EXCEPTION_RECORD { // exr
DWORD ExceptionCode; // [ESP+2C]
DWORD ExceptionFlags; // [ESP+30]
struct _EXCEPTION_RECORD *ExceptionRecord; // [ESP+34]
PVOID ExceptionAddress; // [ESP+38]
DWORD NumberParameters; // [ESP+4c]
DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; // [ESP+50]
} EXCEPTION_RECORD;
001B:004011BE 83E804 SUB EAX,04
001B:004011C1 0F8475020000 JZ 0040143C
001B:004011C7 E948020000 JMP 00401414
001B:004011CC 8B44242C MOV EAX,[ESP+2C] // 把 ExceptionCode 放到 EAX
001B:004011D0 3D03000080 CMP EAX,80000003 // 与 STATUS_BREAKPOINT (int 3) 比较
001B:004011D5 0F872B020000 JA 00401406 <--不跳
001B:004011DB 741B JZ 004011F8 <--这里跳
001B:004011DD 3D05000140 CMP EAX,40010005
001B:004011E2 0F8454020000 JZ 0040143C
001B:004011E8 3D02000080 CMP EAX,80000002 ; STATUS_DATATYPE_MISA
001B:004011ED 0F8449020000 JZ 0040143C
001B:004011F3 E91C020000 JMP 00401414
001B:004011F8 8B542414 MOV EDX,[ESP+14] <--这里继续
001B:004011FC 8D8C24C4000000 LEA ECX,[ESP+000000C4]
001B:00401203 51 PUSH ECX
001B:00401204 52 PUSH EDX
001B:00401205 C78424CC000000020001MOV DWORD PTR [ESP+000000CC],00010002
001B:00401210 FF15CC674000 CALL [004067CC]
这个 call 跟进入后,会到达一个类似 sub1 跳转表的地方,几下 F8 走过 retn 后,
发现这个 call 是 GetThreadContext ,用来取得被调试程序的 register 内容
由此我们知道, [ESP+000000C4] 是 CONTEXT 结构
x86 CONTEXT 结构如下 :
typedef struct _CONTEXT {
DWORD ContextFlags; // [ESP+000000C4]
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave; // 这个结构的大小是 0x70
DWORD SegGs; // [ESP+00000150]
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp; // [ESP+00000178]
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
001B:00401216 8B842474010000 MOV EAX,[ESP+00000174] // 把 context.eax 放入 eax
001B:0040121D 3DF0000000 CMP EAX,000000F0 // context.eax 与 f0 比较
001B:00401222 0F877D010000 JA 004013A5 // 不跳
001B:00401228 0F843A010000 JZ 00401368 // 不跳
001B:0040122E 83E800 SUB EAX,00 // context.eax 与 0 比较
001B:00401231 0F84B4000000 JZ 004012EB // <---跳
001B:00401237 48 DEC EAX
001B:00401238 0F85D6010000 JNZ 00401414
001B:0040123E 6880674000 PUSH 00406780
......
如果 sub1 的 eax 是 0 ,进行 int 3 便来到这里
001B:004012E6 E929010000 JMP 00401414
001B:004012EB 8B1518674000 MOV EDX,[00406718] // <--继续 edx = 406860
001B:004012F1 8B442410 MOV EAX,[ESP+10] // eax = 28
001B:004012F5 8D4C240C LEA ECX,[ESP+0C] // ecx = 12fb90
001B:004012F9 51 PUSH ECX
001B:004012FA 6A15 PUSH 15
001B:004012FC 6880674000 PUSH 00406780
001B:00401301 52 PUSH EDX
001B:00401302 50 PUSH EAX
001B:00401303 FF15C0674000 CALL [004067C0] // ReadProcessMemory
001B:00401309 8B1570674000 MOV EDX,[00406770] // edx = 406880
001B:0040130F 8D4C240C LEA ECX,[ESP+0C] // ecx = 12fb98
001B:00401313 8B442410 MOV EAX,[ESP+10] // eax = 28
001B:00401317 51 PUSH ECX
001B:00401318 6A15 PUSH 15
001B:0040131A 68A0674000 PUSH 004067A0
001B:0040131F 52 PUSH EDX
001B:00401320 50 PUSH EAX
001B:00401321 FF15C0674000 CALL [004067C0] // ReadProcessMemory
001B:00401327 8B1518674000 MOV EDX,[00406718] // edx = 406860
001B:0040132D 8D4C240C LEA ECX,[ESP+0C] // ecx = 12fb98
001B:00401331 8B442410 MOV EAX,[ESP+10] // eax = 28
001B:00401335 51 PUSH ECX
001B:00401336 6A15 PUSH 15
001B:00401338 6840654000 PUSH 00406540
001B:0040133D 52 PUSH EDX
001B:0040133E 50 PUSH EAX
001B:0040133F FF15C4674000 CALL [004067C4] // WriteProcessMemory
001B:00401345 8B1570674000 MOV EDX,[00406770] // edx = 406880
001B:0040134B 8D4C240C LEA ECX,[ESP+0C] // ecx = 12fb98
001B:0040134F 8B442410 MOV EAX,[ESP+10] // eax = 28
001B:00401353 51 PUSH ECX
001B:00401354 6A15 PUSH 15
001B:00401356 6858654000 PUSH 00406558
001B:0040135B 52 PUSH EDX
001B:0040135C 50 PUSH EAX
001B:0040135D FF15C4674000 CALL [004067C4] // WriteProcessMemory
001B:00401363 E9AC000000 JMP 00401414
sub2 使用了 2 次 ReadProcessMemory 和 2 次 WriteProcessMemory,对像都是 [ESP+10] (0x28),我们尝试找出 [ESP+10] 所代表的程序,在 softice 里往上寻找
一直到达了这段程序开始的地方 :
001B:0040114E 90 NOP
001B:0040114F 90 NOP
001B:00401150 81EC84030000 SUB ESP,00000384
001B:00401156 53 PUSH EBX
001B:00401157 56 PUSH ESI
001B:00401158 8D44247C LEA EAX,[ESP+7C]
001B:0040115C 57 PUSH EDI
001B:0040115D 50 PUSH EAX
001B:0040115E C7842484000000440000MOV DWORD PTR [ESP+00000084],00000044
001B:00401169 FF15DC674000 CALL [004067DC]
001B:0040116F 8D4C2410 LEA ECX,[ESP+10] <---这里第一次使用 [ESP+10]
001B:00401173 8D942480000000 LEA EDX,[ESP+00000080]
001B:0040117A 51 PUSH ECX
001B:0040117B 52 PUSH EDX
001B:0040117C 6A00 PUSH 00
001B:0040117E 6A00 PUSH 00
001B:00401180 6A03 PUSH 03
001B:00401182 6A00 PUSH 00
001B:00401184 6A00 PUSH 00
001B:00401186 6A00 PUSH 00
001B:00401188 6A00 PUSH 00
001B:0040118A 6830604000 PUSH 00406030
001B:0040118F FF15D4674000 CALL [004067D4] // 跟踪后发现这是 CreateProcess
001B:00401195 85C0 TEST EAX,EAX
001B:00401197 0F849F020000 JZ 0040143C
由此可见, [ESP+10] 是 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:00401050 83C9FF OR ECX,-01
001B:00401053 33D2 XOR EDX,EDX
001B:00401055 8844241F MOV [ESP+1F],AL
001B:00401059 F2AE REPNZ SCASB <----在这里
001B:0040105B F7D1 NOT ECX
001B:0040105D 49 DEC ECX
0
开始 F8 单步跟踪
001B:00401053 33D2 XOR EDX,EDX
001B:00401055 8844241F MOV [ESP+1F],AL
001B:00401059 F2AE REPNZ SCASB
001B:0040105B F7D1 NOT ECX
001B:0040105D 49 DEC ECX
001B:0040105E 88542410 MOV [ESP+10],DL
001B:00401062 85C9 TEST ECX,ECX
001B:00401064 7E0F JLE 00401075
001B:00401066 0FBE3428 MOVSX ESI,BYTE PTR [EBP+EAX]
001B:0040106A 03F2 ADD ESI,EDX
001B:0040106C D1E6 SHL ESI,1
001B:0040106E 40 INC EAX
001B:0040106F 8BD6 MOV EDX,ESI
001B:00401071 3BC1 CMP EAX,ECX
001B:00401073 7CF1 JL 00401066
001B:00401075 8BC2 MOV EAX,EDX
001B:00401077 C1E005 SHL EAX,05
001B:0040107A 2BC2 SUB EAX,EDX
001B:0040107C 8D0440 LEA EAX,[EAX*2+EAX]
001B:0040107F 8D0442 LEA EAX,[EAX*2+EDX]
001B:00401082 C1E004 SHL EAX,04
001B:00401085 03C2 ADD EAX,EDX
001B:00401087 33FF XOR EDI,EDI
001B:00401089 8D3442 LEA ESI,[EAX*2+EDX]
001B:0040108C 8BC7 MOV EAX,EDI
001B:0040108E 99 CDQ
001B:0040108F F7F9 IDIV ECX
001B:00401091 33C0 XOR EAX,EAX
001B:00401093 8A042A MOV AL,[EBP+EDX]
001B:00401096 85C0 TEST EAX,EAX
001B:00401098 7E1B JLE 004010B5
001B:0040109A 8BD8 MOV EBX,EAX
001B:0040109C 8BC6 MOV EAX,ESI
001B:0040109E 33D2 XOR EDX,EDX
001B:004010A0 69C0EF1E0000 IMUL EAX,EAX,00001EEF
001B:004010A6 83C00D ADD EAX,0D
001B:004010A9 BEDF180000 MOV ESI,000018DF
001B:004010AE F7F6 DIV ESI
001B:004010B0 4B DEC EBX
001B:004010B1 8BF2 MOV ESI,EDX
001B:004010B3 75E7 JNZ 0040109C
001B:004010B5 8BC6 MOV EAX,ESI
001B:004010B7 33D2 XOR EDX,EDX
001B:004010B9 BBFE000000 MOV EBX,000000FE
001B:004010BE F7F3 DIV EBX
001B:004010C0 FEC2 INC DL
001B:004010C2 88543C10 MOV [EDI+ESP+10],DL
001B:004010C6 47 INC EDI
001B:004010C7 83FF0F CMP EDI,0F
001B:004010CA 7CC0 JL 0040108C
001B:004010CC 8D7C2410 LEA EDI,[ESP+10]
001B:004010D0 83C9FF OR ECX,-01
001B:004010D3 33C0 XOR EAX,EAX
001B:004010D5 C6450000 MOV BYTE PTR [EBP+00],00
001B:004010D9 F2AE REPNZ SCASB
001B:004010DB F7D1 NOT ECX
001B:004010DD 2BF9 SUB EDI,ECX
001B:004010DF 8BD1 MOV EDX,ECX
001B:004010E1 8BF7 MOV ESI,EDI
001B:004010E3 8BFD MOV EDI,EBP
001B:004010E5 C1E902 SHR ECX,02
001B:004010E8 F3A5 REPZ MOVSD
001B:004010EA 8BCA MOV ECX,EDX
001B:004010EC 83E103 AND ECX,03
001B:004010EF F3A4 REPZ MOVSB
001B:004010F1 5F POP EDI
001B:004010F2 5E POP ESI
001B:004010F3 5D POP EBP
001B:004010F4 5B POP EBX
001B:004010F5 83C410 ADD ESP,10
001B:004010F8 C3 RET
算法很复杂,使用了很多 REPNZ SCASB 和 REPZ MOVSD,看来是一堆字符串处理,继续进 retn
001B:00401222 0F877D010000 JA 004013A5
001B:00401228 0F843A010000 JZ 00401368
001B:0040122E 83E800 SUB EAX,00
001B:00401231 0F84B4000000 JZ 004012EB
001B:00401237 48 DEC EAX
001B:00401238 0F85D6010000 JNZ 00401414
001B:0040123E 6880674000 PUSH 00406780
001B:00401243 E8E8FDFFFF CALL 00401030
001B:00401248 68A0674000 PUSH 004067A0 // <----来到这里
001B:0040124D E8AEFDFFFF CALL 00401000
进去看看
001B:00401000 8B542404 MOV EDX,[ESP+04]
001B:00401004 57 PUSH EDI
001B:00401005 8BFA MOV EDI,EDX
001B:00401007 83C9FF OR ECX,-01
001B:0040100A 33C0 XOR EAX,EAX
001B:0040100C F2AE REPNZ SCASB
001B:0040100E F7D1 NOT ECX
001B:00401010 49 DEC ECX
001B:00401011 5F POP EDI
001B:00401012 85C9 TEST ECX,ECX
001B:00401014 7E10 JLE 00401026
001B:00401016 53 PUSH EBX
001B:00401017 8A1C10 MOV BL,[EDX+EAX] <--循环
001B:0040101A 80F359 XOR BL,59 // 把字符组与 59 xor
001B:0040101D 881C10 MOV [EDX+EAX],BL
001B:00401020 40 INC EAX
001B:00401021 3BC1 CMP EAX,ECX
001B:00401023 7CF2 JL 00401017 <--往上跳
001B:00401025 5B POP EBX
001B:00401026 C3 RET
001B:00401027 90 NOP
这里循环里的 EDX 的值是 4067a0,用 softice 查看
:d 4067a0
发现这里依次序填满了序号 AAAABBBB
ret 返回,继续 F8
001B:00401252 8B0D1C674000 MOV ECX,[0040671C]
001B:00401258 8B542418 MOV EDX,[ESP+18]
001B:0040125C 83C408 ADD ESP,08
001B:0040125F 8D44240C LEA EAX,[ESP+0C]
001B:00401263 83C110 ADD ECX,10
001B:00401266 50 PUSH EAX
001B:00401267 6A15 PUSH 15
001B:00401269 6880674000 PUSH 00406780
001B:0040126E 51 PUSH ECX
001B:0040126F 52 PUSH EDX
001B:00401270 FF15C4674000 CALL [004067C4] // ReadProcessMemory
001B:00401276 8B0D1C674000 MOV ECX,[0040671C]
001B:0040127C 8D44240C LEA EAX,[ESP+0C]
001B:00401280 8B542410 MOV EDX,[ESP+10]
001B:00401284 50 PUSH EAX
001B:00401285 6A15 PUSH 15
001B:00401287 83C138 ADD ECX,38
001B:0040128A 68A0674000 PUSH 004067A0
001B:0040128F 51 PUSH ECX
001B:00401290 52 PUSH EDX
001B:00401291 FF15C4674000 CALL [004067C4] // ReadProcessMemory
001B:00401297 8D8424C4000000 LEA EAX,[ESP+000000C4]
001B:0040129E 899C24C4000000 MOV [ESP+000000C4],EBX
001B:004012A5 8B4C2414 MOV ECX,[ESP+14]
001B:004012A9 50 PUSH EAX
001B:004012AA 51 PUSH ECX
001B:004012AB FF15CC674000 CALL [004067CC] // GetThreadContext
我们可以推断 [ESP+000000C4] 是 context 结构
001B:004012B1 8D9424C4000000 LEA EDX,[ESP+000000C4] //把 context 的位置放入 edx
001B:004012B8 C78424C4000000030001MOV DWORD PTR [ESP+000000C4],00010003
// 把 10003 放进 context 的 ContextFlags 里
001B:004012C3 8B84247C010000 MOV EAX,[ESP+0000017C] // 把 context.Eip 放进 eax
001B:004012CA 52 PUSH EDX // *注意这里, esp 改变了
001B:004012CB 8BF0 MOV ESI,EAX // 把 context.Eip 放到 esi
001B:004012CD 89842478010000 MOV [ESP+00000178],EAX //把 context.Eip 放到 context.Eax
001B:004012D4 8B442418 MOV EAX,[ESP+18] // eax = 2c
001B:004012D8 89BC2480010000 MOV [ESP+00000180],EDI // 把 edi 放到 context.Eip,值是 4014a0
001B:004012DF 50 PUSH EAX
001B:004012E0 FF15C8674000 CALL [004067C8] // SetThreadContext
001B:004012E6 E929010000 JMP 00401414
001B:004012EB 8B1518674000 MOV EDX,[00406718]
001B:004012F1 8B442410 MOV EAX,[ESP+10]
001B:004012F5 8D4C240C LEA ECX,[ESP+0C]
001B:004012F9 90 NOP
001B:004012FA 90 NOP
001B:004012FB 90 NOP
这段程序把 sub1 的 eip 放到 eax 里,并把 eip 改变为 4014a0,这样会强行改变 sub1 流程到 4014a0 处继续
我们现在到 sub1 里,设断点在 4014a0
:addr sub1
:bpx 4014a0
在 sub2 中离开 softice,果然断在刚刚设在 sub1 的断点
估计如果序号是正确的话,这里便会进行判断,显示成功信息
001B:0040149F 90 NOP
001B:004014A0 50 PUSH EAX //<----这里
001B:004014A1 E87AFEFFFF CALL 00401320
001B:004014A6 C3 RET
001B:004014A7 C3 RET
001B:004014A8 90 NOP
001B:004014A9 90 NOP
这里把 eax (sub1 原来的 eip) push 到 stack 去,再呼叫 00401320,我们不知它的原因
F8 跟进去
001B:0040131F 90 NOP
001B:00401320 83EC7C SUB ESP,7C
001B:00401323 33C9 XOR ECX,ECX
001B:00401325 33C0 XOR EAX,EAX
001B:00401327 894C2410 MOV [ESP+10],ECX
001B:0040132B 53 PUSH EBX
001B:0040132C 894C2418 MOV [ESP+18],ECX
001B:00401330 89442404 MOV [ESP+04],EAX
001B:00401334 55 PUSH EBP
001B:00401335 894C2420 MOV [ESP+20],ECX
001B:00401339 56 PUSH ESI
001B:0040133A 89442410 MOV [ESP+10],EAX
001B:0040133E 57 PUSH EDI
001B:0040133F 894C242C MOV [ESP+2C],ECX
001B:00401343 89442418 MOV [ESP+18],EAX
001B:00401347 B90A000000 MOV ECX,0000000A
001B:0040134C 8D7C2464 LEA EDI,[ESP+64]
001B:00401350 8B1DE0674000 MOV EBX,[004067E0]
001B:00401356 F3AB REPZ STOSD // 把 [ESP+64] 清 0
001B:00401358 B907000000 MOV ECX,00000007
001B:0040135D 8D7C2444 LEA EDI,[ESP+44]
001B:00401361 8944241C MOV [ESP+1C],EAX
001B:00401365 33D2 XOR EDX,EDX
001B:00401367 F3AB REPZ STOSD // 把 [ESP+44] 清 0
001B:00401369 66AB STOSW
001B:0040136B 8D7B10 LEA EDI,[EBX+10]
001B:0040136E 83C9FF OR ECX,-01
001B:00401371 33C0 XOR EAX,EAX
001B:00401373 8D6C2410 LEA EBP,[ESP+10]
001B:00401377 F2AE REPNZ SCASB
001B:00401379 F7D1 NOT ECX // 估计是计算字符串长度
001B:0040137B 2BF9 SUB EDI,ECX
001B:0040137D 8BC1 MOV EAX,ECX
001B:0040137F 8BF7 MOV ESI,EDI
001B:00401381 8BFD MOV EDI,EBP
001B:00401383 C1E902 SHR ECX,02
001B:00401386 F3A5 REPZ MOVSD
001B:00401388 8BC8 MOV ECX,EAX
001B:0040138A 33C0 XOR EAX,EAX
001B:0040138C 83E103 AND ECX,03
001B:0040138F F3A4 REPZ MOVSB
001B:00401391 8D7B38 LEA EDI,[EBX+38]
001B:00401394 83C9FF OR ECX,-01
001B:00401397 F2AE REPNZ SCASB
001B:00401399 F7D1 NOT ECX
001B:0040139B 2BF9 SUB EDI,ECX
001B:0040139D 8D5C2420 LEA EBX,[ESP+20]
001B:004013A1 8BC1 MOV EAX,ECX
001B:004013A3 8BF7 MOV ESI,EDI
001B:004013A5 8BFB MOV EDI,EBX
001B:004013A7 C1E902 SHR ECX,02
001B:004013AA F3A5 REPZ MOVSD
001B:004013AC 8BC8 MOV ECX,EAX
001B:004013AE 33C0 XOR EAX,EAX
001B:004013B0 83E103 AND ECX,03
001B:004013B3 F3A4 REPZ MOVSB
001B:004013B5 8D7C2410 LEA EDI,[ESP+10]
001B:004013B9 83C9FF OR ECX,-01
001B:004013BC F2AE REPNZ SCASB
001B:004013BE F7D1 NOT ECX
001B:004013C0 49 DEC ECX
001B:004013C1 85C9 TEST ECX,ECX
001B:004013C3 7E10 JLE 004013D5
001B:004013C5 0FBE740410 MOVSX ESI,BYTE PTR [EAX+ESP+10]
001B:004013CA 03F2 ADD ESI,EDX
001B:004013CC D1E6 SHL ESI,1
001B:004013CE 40 INC EAX
001B:004013CF 8BD6 MOV EDX,ESI
001B:004013D1 3BC1 CMP EAX,ECX
001B:004013D3 7CF0 JL 004013C5
001B:004013D5 8BC2 MOV EAX,EDX
001B:004013D7 C1E004 SHL EAX,04
001B:004013DA 03C2 ADD EAX,EDX
001B:004013DC C1E004 SHL EAX,04
001B:004013DF 03C2 ADD EAX,EDX
001B:004013E1 33FF XOR EDI,EDI
001B:004013E3 8D0442 LEA EAX,[EAX*2+EDX]
001B:004013E6 8D0480 LEA EAX,[EAX*4+EAX]
001B:004013E9 8D3442 LEA ESI,[EAX*2+EDX]
001B:004013EC 8BC7 MOV EAX,EDI
001B:004013EE 33DB XOR EBX,EBX
001B:004013F0 99 CDQ
001B:004013F1 F7F9 IDIV ECX
001B:004013F3 8A5C1410 MOV BL,[EDX+ESP+10]
001B:004013F7 85DB TEST EBX,EBX
001B:004013F9 7E19 JLE 00401414
001B:004013FB 8BC6 MOV EAX,ESI
001B:004013FD 33D2 XOR EDX,EDX
001B:004013FF 69C02F1E0000 IMUL EAX,EAX,00001E2F
001B:00401405 83C00D ADD EAX,0D
001B:00401408 BEDB190000 MOV ESI,000019DB
001B:0040140D F7F6 DIV ESI
001B:0040140F 4B DEC EBX
001B:00401410 8BF2 MOV ESI,EDX
001B:00401412 75E7 JNZ 004013FB
001B:00401414 8BC6 MOV EAX,ESI
001B:00401416 33D2 XOR EDX,EDX
001B:00401418 BB1A000000 MOV EBX,0000001A
001B:0040141D F7F3 DIV EBX
001B:0040141F 80C241 ADD DL,41
001B:00401422 88543C30 MOV [EDI+ESP+30],DL
001B:00401426 47 INC EDI
001B:00401427 83FF0F CMP EDI,0F
001B:0040142A 7CC0 JL 004013EC
001B:0040142C 5F POP EDI
001B:0040142D 5E POP ESI
001B:0040142E 5D POP EBP
001B:0040142F C644243400 MOV BYTE PTR [ESP+34],00
001B:00401434 33C0 XOR EAX,EAX
001B:00401436 5B POP EBX
001B:00401437 8A4C0410 MOV CL,[EAX+ESP+10]
001B:0040143B 8A540420 MOV DL,[EAX+ESP+20]
001B:0040143F 80F159 XOR CL,59
001B:00401442 3ACA CMP CL,DL
001B:00401444 754F JNZ 00401495 <-----这里往下跳至 ret,
001B:00401446 40 INC EAX
上面部份算法比较复杂,在我们分析前,我们发现程序跟到这里后,便跳往 ret
我们估计这里是检查序号的正确性,我们尝试用 softice 的 u 指令,到达下面的 call ,人手把它们的 API 翻译出来
001B:00401447 83F80F CMP EAX,0F
001B:0040144A 7CEB JL 00401437
001B:0040144C 8D542454 LEA EDX,[ESP+54]
001B:00401450 52 PUSH EDX
001B:00401451 E8FAFDFFFF CALL 00401250
到 00401250
:u 00401250
001B:00401250 83EC5C SUB ESP,5C
001B:00401253 B9DEFFFFFF MOV ECX,FFFFFFDE
001B:00401258 53 PUSH EBX
001B:00401259 BAE9FFFFFF MOV EDX,FFFFFFE9
001B:0040125E 56 PUSH ESI
001B:0040125F 894C241C MOV [ESP+1C],ECX
001B:00401263 894C2428 MOV [ESP+28],ECX
001B:00401267 BEEDFFFFFF MOV ESI,FFFFFFED
001B:0040126C 89542414 MOV [ESP+14],EDX
001B:00401270 B8DFFFFFFF MOV EAX,FFFFFFDF
001B:00401275 8954242C MOV [ESP+2C],EDX
001B:00401279 B9EFFFFFFF MOV ECX,FFFFFFEF
001B:0040127E BADDFFFFFF MOV EDX,FFFFFFDD
001B:00401283 8974240C MOV [ESP+0C],ESI
001B:00401287 89442418 MOV [ESP+18],EAX
001B:0040128B 8944243C MOV [ESP+3C],EAX
001B:0040128F 894C2444 MOV [ESP+44],ECX
001B:00401293 894C2448 MOV [ESP+48],ECX
001B:00401297 8974244C MOV [ESP+4C],ESI
001B:0040129B 8B742468 MOV ESI,[ESP+68]
001B:0040129F 89442450 MOV [ESP+50],EAX
001B:004012A3 89442454 MOV [ESP+54],EAX
001B:004012A7 C744240800000000 MOV DWORD PTR [ESP+08],00000000
001B:004012AF C7442410EBFFFFFF MOV DWORD PTR [ESP+10],FFFFFFEB
001B:004012B7 C7442420E0FFFFFF MOV DWORD PTR [ESP+20],FFFFFFE0
001B:004012BF C7442424F1FFFFFF MOV DWORD PTR [ESP+24],FFFFFFF1
001B:004012C7 C7442430E3FFFFFF MOV DWORD PTR [ESP+30],FFFFFFE3
001B:004012CF C7442434E4FFFFFF MOV DWORD PTR [ESP+34],FFFFFFE4
001B:004012D7 C744243832000000 MOV DWORD PTR [ESP+38],00000032
001B:004012DF 89542440 MOV [ESP+40],EDX
001B:004012E3 C7442458ECFFFFFF MOV DWORD PTR [ESP+58],FFFFFFEC
001B:004012EB 8954245C MOV [ESP+5C],EDX
001B:004012EF C7442460E6FFFFFF MOV DWORD PTR [ESP+60],FFFFFFE6
001B:004012F7 33C0 XOR EAX,EAX
001B:004012F9 8D4C2408 LEA ECX,[ESP+08]
001B:004012FD 8A19 MOV BL,[ECX]
001B:004012FF B252 MOV DL,52
001B:00401301 2AD3 SUB DL,BL
001B:00401303 83C104 ADD ECX,04
001B:00401306 881430 MOV [ESI+EAX],DL
001B:00401309 40 INC EAX
001B:0040130A 83F817 CMP EAX,17
001B:0040130D 7CEE JL 004012FD
001B:0040130F 5E POP ESI
001B:00401310 5B POP EBX
001B:00401311 83C45C ADD ESP,5C
001B:00401314 C3 RET
001B:00401315 90 NOP
很明显,这里不是 API 跳转表的位置,是程序的其它 call
回到先前的部份
:u 00401456
001B:00401456 8D442438 LEA EAX,[ESP+38]
001B:0040145A 50 PUSH EAX
001B:0040145B E890FCFFFF CALL 004010F0
看看 004010F0
:u 004010F0
001B:004010F0 83EC4C SUB ESP,4C
001B:004010F3 B80A000000 MOV EAX,0000000A
001B:004010F8 B95A000000 MOV ECX,0000005A
001B:004010FD 89442408 MOV [ESP+08],EAX
001B:00401101 89442410 MOV [ESP+10],EAX
001B:00401105 B807000000 MOV EAX,00000007
001B:0040110A 53 PUSH EBX
001B:0040110B 56 PUSH ESI
001B:0040110C 8B742458 MOV ESI,[ESP+58]
001B:00401110 894C2414 MOV [ESP+14],ECX
001B:00401114 89442420 MOV [ESP+20],EAX
001B:00401118 89442424 MOV [ESP+24],EAX
001B:0040111C 894C243C MOV [ESP+3C],ECX
001B:00401120 C744240800000000 MOV DWORD PTR [ESP+08],00000000
001B:00401128 C744240C11000000 MOV DWORD PTR [ESP+0C],00000011
001B:00401130 C744241C19000000 MOV DWORD PTR [ESP+1C],00000019
001B:00401138 C744242803000000 MOV DWORD PTR [ESP+28],00000003
001B:00401140 C744242C0B000000 MOV DWORD PTR [ESP+2C],0000000B
001B:00401148 C744243008000000 MOV DWORD PTR [ESP+30],00000008
001B:00401150 C744243416000000 MOV DWORD PTR [ESP+34],00000016
001B:00401158 C744243840000000 MOV DWORD PTR [ESP+38],00000040
001B:00401160 C744244014000000 MOV DWORD PTR [ESP+40],00000014
001B:00401168 C744244406000000 MOV DWORD PTR [ESP+44],00000006
001B:00401170 C744244810000000 MOV DWORD PTR [ESP+48],00000010
001B:00401178 C744244C05000000 MOV DWORD PTR [ESP+4C],00000005
001B:00401180 C744245013000000 MOV DWORD PTR [ESP+50],00000013
001B:00401188 33C0 XOR EAX,EAX
001B:0040118A 8D4C2408 LEA ECX,[ESP+08]
001B:0040118E 8A19 MOV BL,[ECX]
001B:00401190 B27A MOV DL,7A
001B:00401192 2AD3 SUB DL,BL
001B:00401194 83C104 ADD ECX,04
001B:00401197 881430 MOV [ESI+EAX],DL
001B:0040119A 40 INC EAX
001B:0040119B 83F813 CMP EAX,13
001B:0040119E 7CEE JL 0040118E
001B:004011A0 5E POP ESI
001B:004011A1 5B POP EBX
001B:004011A2 83C44C ADD ESP,4C
001B:004011A5 C3 RET
001B:004011A6 90 NOP
看来也不是 API 跳转表,再回去看看
:u 00401460
001B:00401460 8B1530684000 MOV EDX,[00406830]
001B:00401466 83C408 ADD ESP,08
001B:00401469 8D4C2454 LEA ECX,[ESP+54]
001B:0040146D 6A00 PUSH 00
001B:0040146F 68C0654000 PUSH 004065C0
001B:00401474 51 PUSH ECX
001B:00401475 52 PUSH EDX
001B:00401476 FF15A0684000 CALL [004068A0]
看看 [004068A0]
:dd 004068A0
得到值 980000
:u 980000
001B:00980000 B86464E177 MOV EAX,77E16464
001B:00980005 05E0000000 ADD EAX,000000E0
001B:0098000A 50 PUSH EAX
001B:0098000B C3 RET
001B:0098000C B80341DF77 MOV EAX,77DF4103
001B:00980011 05E0000000 ADD EAX,000000E0
001B:00980016 50 PUSH EAX
001B:00980017 C3 RET
001B:00980018 B8AA57DF77 MOV EAX,77DF57AA
001B:0098001D 05E0000000 ADD EAX,000000E0
001B:00980022 50 PUSH EAX
001B:00980023 C3 RET
这里像 sub2 和 sub1 常用的 API 跳转表
我们人手把 77E16464 的值加上 E0,得到 77e16384
:u 77e16544
USER32!MessageBoxA // <--留意这里
001B:77E16544 55 PUSH EBP
001B:77E16545 8BEC MOV EBP,ESP
001B:77E16547 51 PUSH ECX
001B:77E16548 833D1893E47700 CMP DWORD PTR [77E49318],00
001B:77E1654F 0F85EA220100 JNZ 77E2883F
001B:77E16555 6A00 PUSH 00
001B:77E16557 FF7514 PUSH DWORD PTR [EBP+14]
001B:77E1655A FF7510 PUSH DWORD PTR [EBP+10]
001B:77E1655D FF750C PUSH DWORD PTR [EBP+0C]
001B:77E16560 FF7508 PUSH DWORD PTR [EBP+08]
001B:77E16563 E804000000 CALL USER32!MessageBoxExA
001B:77E16568 C9 LEAVE
001B:77E16569 C21000 RET 0010
USER32!MessageBoxExA
001B:77E1656C 55 PUSH EBP
001B:77E1656D 8BEC MOV EBP,ESP
001B:77E1656F 51 PUSH ECX
001B:77E16570 51 PUSH ECX
001B:77E16571 53 PUSH EBX
001B:77E16572 56 PUSH ESI
001B:77E16573 57 PUSH EDI
001B:77E16574 33FF XOR EDI,EDI
001B:77E16576 83CEFF OR ESI,-01
001B:77E16579 397D0C CMP [EBP+0C],EDI
001B:77E1657C 897DFC MOV [EBP-04],EDI
001B:77E1657F 897DF8 MOV [EBP-08],EDI
001B:77E16582 7419 JZ 77E1659D
001B:77E16584 6A01 PUSH 01
001B:77E16586 8D45FC LEA EAX,[EBP-04]
001B:77E16589 56 PUSH ESI
001B:77E1658A 50 PUSH EAX
我们看见这里是 KERNEL32.MessageBoxA 的领空,看来这个 call 的 MessageBoxA 的呼叫
这里有可能是跳出成功注册信息的地方,我们现在想办法改变程序的流程,让 sub1 成功到达这里
[ Part VI. 最后决战 ]
先把程序关掉,重新打开 crackme,确保上面两处 ReadProcessMemory 的位置可以正常通过,读取注册名字和序号
现在,回到 sub1 的那个跳转看看,打开 softice 到 sub1 下断点直接到达
:addr sub1
:bpx 00401437
按 register,断在
001B:00401437 8A4C0410 MOV CL,[EAX+ESP+10]
001B:0040143B 8A540420 MOV DL,[EAX+ESP+20]
001B:0040143F 80F159 XOR CL,59
001B:00401442 3ACA CMP CL,DL
001B:00401444 754F JNZ 00401495 <-----这里往下跳至 ret
这个 JNZ 的上方是 CMP,当两者不相等,这个 JNZ 便会跳往完结处
上面的 CL 和 DL 估计是用来储存单位字符组, CL 的值经过 xor 0x59 后,跟 DL 比较
我们在 00401444 设断点,按 register
断下时用 softice 看看 [EAX+ESP+10]
:db EAX+ESP+10
0023:0012FBAC 03 0C 09 0B 1B 1F 15 0A-1A 1A 14 14 13 11 0A 00 ................
再看 [EAX+ESP+20]
:db EAX+ESP+20
0023:0012FBBC 5A 55 50 52 42 46 4C 53-43 43 4D 4D 4A 48 53 00 ZUPRBFLSCCMMJHS.
这是一行特别的字符串 "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[16] = {0};
len = strlen(buf);
seed = 5987;
for( i=0; i<len; i++)
{
affect = (affect + buf[i]) * 2;
}
seed = seed * affect;
for( i=0; i<15; i++)
{
for( j=0; j< (unsigned char)buf[i % len]; j++)
{
seed = ((seed * 7919) + 13) % 6367;
}
bufout[i] = 1 + (unsigned char)(seed % 254);
}
buf[0] = 0;
strcpy(buf, bufout);
}
第二次转换:
void encode2(char *buf)
{
int len;
int i;
len = strlen(buf);
for( i=0; i<len; i++)
{
buf[i] = buf[i] ^ 0x59;
}
}
sub1 的算法与 sub2 的第一次算法是相同的,看懂后很容易明白。
这个 crackme 的制作用了两星期,读者可能会觉得一些地方不够完美,例如为甚么整个序号
处理只使用了 sub2 和 sub1,却没有使用 sub3 和 crackme4 的主体 ? 我在设计也觉得不够强硬,
但是它的复杂性已经很大,我在修正了多个 bug 后,已经没有气力了。
希望这篇文章对初入门的兄弟有用,这篇文章的后半部分使用了 softice ,对于不熟识 softice
操作的兄弟可能会觉得十分不方便,可是在现实中,我们很多时候也是被迫使用 softice 来应付
一些特别困难的情况。
[全文完]
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!