Saver 发表于 2005-3-14 22:00:08

中级新手(推荐)

【破文标题】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 来应付
一些特别困难的情况。

crazysky 发表于 2005-3-14 22:19:12

哗!很长哦!慢慢看!

云瑞 发表于 2005-3-15 22:03:56

太长了!^_^,要好好研究!
页: [1]
查看完整版本: 中级新手(推荐)