【破文标题】riijj Crackme (3)的详解
【对 象】初级至中级的新手
【下载地址】http://bbs.pediy.com/upload/file/2004/11/riijj_cm_20041121.zip_081.zip
【破解工具】OD
【保护方式】序号
【任 务】分析算法,找出序号
【破文作者】riijj
【破解声明】我的破解很菜,写这篇东西是给对这个 crackme 有兴趣的兄弟看,分享破解它的方法
【备 注】老手勿看
【破解过程】
对于刚接触破解的新手来说,这是一只充满敌意的小猫。
这个 crackme(3) 的重点是反调试练习。以往我们遇到的软件都可以用调试器 (eg: OD) 进行跟踪,找出注册序号的算法,但是市面
上大多数软件已经使用了壳的保护,而且对于破者者采取主动防范的态度,希望用尽所有方法使破解者不能顺利调试。
[1]. 查壳
用 peid 打开,显示 Microsoft Visual C++ 6.0 ,没有加壳
[2]. 尝试运行
我们把 crackme 运行,在注册名和序号输入了 riijj 和 AAAABBBB ,按一下注册,没有任何反应。 对于这种没有失败信息的程序,
很明显作者是不想检查序号的位置暴露在断点之下,为了防止 bp MessageBoxA 这类简单的断点
[3]. 使用 OD 加载 (设置是没有使用插件,没有隐藏 OD)
好吧,我们拿起刀斧,一起干掉这只小猫。
004025A1 . 83C4 08 ADD ESP,8
004025A4 . 5D POP EBP
004025A5 . C2 0400 RETN 4
004025A8 >/$ 55 PUSH EBP <--------- 停在这里
004025A9 |. 8BEC MOV EBP,ESP
004025AB |. 6A FF PUSH -1
004025AD |. 68 C0514000 PUSH riijj_cr.004051C0
004025B2 |. 68 D0244000 PUSH riijj_cr.004024D0 ; SE handler installation
004025B7 |. 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
004025BD |. 50 PUSH EAX
004025BE |. 64:8925 000000>MOV DWORD PTR FS:[0],ESP
004025C5 |. 83EC 58 SUB ESP,58
004025C8 |. 53 PUSH EBX
004025C9 |. 56 PUSH ESI
004025CA |. 57 PUSH EDI
004025CB |. 8965 E8 MOV DWORD PTR SS:[EBP-18],ESP
004025CE |. FF15 C8504000 CALL DWORD PTR DS:[<&KERNEL32.GetVersion>; KERNEL32.GetVersion
004025D4 |. 33D2 XOR EDX,EDX
[4]. 设断点
我们尝试用最简单的断点, bp GetWindowTextA,拦截程序得到字符串的地方。
[5]. 运行
按一下 F9 运行
程序突然停下,看看 OD 的下方,写住
Access violation when executing [00000000] - use Shift+F7/F8/F9 to pass exception to program
这真是奇怪了,为甚么 crackme 在正常情况下可以运行呢 ? 因为这种 crackme 使用了反调试机制,当程序独立
运行时, 程序发生预定的出错 (exception error, 也就是 "异常错误",例如写入错误,读取错误),这时候程序
便会交由 exception handler 处理, 如果这个程序安装了 SEH (structured exception handler) ,这时候系统
便会把控制权交给 SEH,让程序自行处理错误。这种程序在 OD 下运行,会导致 OD 停止,因为调试器会在程序
发生 exception 的时候停止。
我们在 OD 中遇到 exception,惯常的做法是按 shift+F9 (或是 F7 或 F8),使 OD 把 exception 交回程序
自行处理,这样便可以继续调试。 (详情请参阅看雪的脱壳区,那里都是 SEH 的专家)
我们按一下 Shift+F9,让 OD 通过它
这时候, OD 右边的 CPU 数据改变了一下,可是 OD 像没有反应一样,依家写着 access violation 那一句。
看来,这个程序设置了多次 SEH 错误,我们刚刚通过了一次 SEH 错误,现在又遇到第二次了。
我们继续按 Shift+F9,按了几次后,看见 OD 的下方变成了
Process terminated
甚么 ?! 程序已经结束了
我们发现,这个 crackme 设置了多次 SEH ,而且当我们不断使用 Shift+F9 来通过错误,程序便会自动终止。
我们必须要在程序终止的前一刻,用单步进入去看清楚原因。
[6]. 对付 SEH 保护
一些程序当要进行有可能导致异常错误的代码时,都会使用 SEH 来处理异常。SEH 像一个安全网,当程序要死掉的时候,
便会掉进这个网里。程序必须在错误发生前把程序的 SEH 设定好。
step 1 : 设定 SEH
step 2 : 执行危险代码
step 3 : 程序不幸发生错误
step 4 : 系统发现程序有设定好的 SEH,把程序的执行位置跳到 SEH 的起点
step 5 : 程序从 SEH 的位置继续执行,进行修补错误工作,或是离开前的必要动作
step 6 : 如果 SEH 不能处理异常,便把异常交由系统处理
step 7 : 系统跳出错误信息,程序终止
为了让我们看清楚程序如何设定 SEH,我们利用这个 crackme 来揭开它的神秘面纱
[7]. 观察 SEH 设定
我们用 OD 把程序重新打开, F9 运行,遇到第一次 SEH ,尝试按一下 Shift+F7 单步进入
程序突然跳出了一个方块,上面写着
Don't know how to step command at address 00000000....
看来 OD 遇到了程序的错误运行位置,导致 OD 不懂得停在那里,我们再尝试 Shift+F8,结果也是一样
我们没有办法,只好再次重新加载程序
在程序的入口,用 F8 单步运行
004025A8 >/$ 55 PUSH EBP <-------- 从起点开始
004025A9 |. 8BEC MOV EBP,ESP
004025AB |. 6A FF PUSH -1
004025AD |. 68 C0514000 PUSH riijj_cr.004051C0
004025B2 |. 68 D0244000 PUSH riijj_cr.004024D0 ; SE handler installation
004025B7 |. 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
004025BD |. 50 PUSH EAX
我们不停按 F8,突然 OD 跳出了刚才的错误信息,我们当时的位置在
00402683 |. E8 08F3FFFF CALL riijj_cr.00401990
这是一个 call,显示错误发生在 call 的里面,我们尝试按 F7 跟进去,可是 OD 依然跳出错误,没法跟下去
我们只好再把程序重新加载,并设点在 00402683 ,F9 运行到断点, F7 单步进入
今次我们来到了
00401990 $ 55 PUSH EBP <---- 我们在这里
00401991 . 8BEC MOV EBP,ESP
00401993 . 6A FF PUSH -1
00401995 . 68 58514000 PUSH riijj_cr.00405158
0040199A . 68 D0244000 PUSH riijj_cr.004024D0 ; SE handler installation
0040199F . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
004019A5 . 50 PUSH EAX
004019A6 . 64:8925 000000>MOV DWORD PTR FS:[0],ESP
004019AD . 83EC 08 SUB ESP,8
004019B0 . 53 PUSH EBX
004019B1 . 56 PUSH ESI
004019B2 . 57 PUSH EDI
004019B3 . 8965 E8 MOV DWORD PTR SS:[EBP-18],ESP
004019B6 . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
004019B9 . A3 786B4000 MOV DWORD PTR DS:[406B78],EAX
004019BE . 8B4D 14 MOV ECX,DWORD PTR SS:[EBP+14]
004019C1 . 890D 44694000 MOV DWORD PTR DS:[406944],ECX
004019C7 . E8 A4FFFFFF CALL riijj_cr.00401970
004019CC . C745 FC 000000>MOV DWORD PTR SS:[EBP-4],0
004019D3 . FF25 54694000 JMP DWORD PTR DS:[406954]
依旧一直按 F8 ,直至最后一行
004019D3 . FF25 54694000 JMP DWORD PTR DS:[406954]
按下 F8 的时候, OD 跳出错误。我们知道这一行是导致 OD 错误的原因,我们假如要继续跟踪下去,便要
知道 SEH 处理程序的位置,让我们直接去那里
要知道 SEH 的位置,我们要看看这里
0040199A . 68 D0244000 PUSH riijj_cr.004024D0 ; SE handler installation
0040199F . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
004019A5 . 50 PUSH EAX
004019A6 . 64:8925 000000>MOV DWORD PTR FS:[0],ESP
程序在执行有可能导致错误的程序之前,会把 SEH 位置设定在 FS 寄存器 (详情请参阅看雪的 SEH 文章),
我们看见 OD 右边的注释,在 0040199A 的那一行 push 旁边写上了 SE handler installation。这个 push 的
位置将会被放入 FS 里,我们要到那里继续跟
到 004024D0
004024D0 /$ 55 PUSH EBP ; Structured exception handler
004024D1 |. 8BEC MOV EBP,ESP
004024D3 |. 83EC 08 SUB ESP,8
004024D6 |. 53 PUSH EBX
004024D7 |. 56 PUSH ESI
这里便是 SEH 的真身了。当程序发生异常错误,系统会把执行位置跳到这里继续,如果我们用 OD 运行程序,
这时候 OD 把 exception 接收了,我们当然不可以顺利来到这里。
[8]. 解除 SEH
大部分使用 SEH 保护的程序,都可以透过按下 Shift+F9 通过 SEH 异常,直至程序顺利运行,可是为甚么这个 crackme 会走进
程序终止呢 ? 这是因为这个 crackme 的作者 (我在说自己) 使用了另一个方法,导致破解者在 SEH 的森林里走进死亡
我们除了可以设定一部份程序的 SEH 外,也可以设定一个较高层次的 SEH,当所有其它 SEH 都失败的时候,便来到这个去。
要设定这一种 SEH,程序需要透过一个 API 叫做 SetUnhandledExceptionFilter
SetUnhandledExceptionFilter 需要一个参数 "LPTOP_LEVEL_EXCEPTION_FILTER" ,它是一个 SEH 处理程序的位置。当程序
呼叫 SetUnhandledExceptionFilter ,系统会把程序自定的 SEH 位置取代系统预设给一般程序的异常处理位置,也是说当
程序发生不能处理的错误时 (unhandled exception) ,系统便会把程序跳到这里,让程序进行最后的修补工作
基于这种高屠 SEH 的特性,如果用于保护程序上,那么当程序发生异常错误时,调试器是不能像处理一般 SEH 的情况一样,把
异常错误交还程序自行处理。这种使用 SetUnhandledExceptionFilter 来对付 OD 调试的手法,通常出现在壳的保护里。
为了让我们清楚看见 OD 死亡的一刻,我们把程序重新加载,今次我们每按一下 Shift+F9 的时候,留意 OD 左下方的信息
F9 运行
5 次 Shift+F9 后,
看见 OD 的下方写着 Exception C0123333,这是甚么呢 ? 这其实是一般的 exception,跟我们早前遇到的一样,
只是作者使用了 API 来制造有特别代号的 exception,我们依旧按 Shift+F9 通过
3 次 Shift+F9 后,
我们看见 OD 写了 Debugged program was unable to process exception
这代表了被调试的程序发生了异常错误,而且程序没有安装 SEH 处理程序,所以程序没有能力处理它,在这种情况下,
当我们再按一下 Shift+F9 ,程序便自然结束了。
这里暗示了,程序在这一刻之前,已经布置好 Unhandled Exception Filter,并且制造人工错误,使程序不能处理异常而死亡。
如果在没有调试器的环境下,程序死亡时会触发 Unhandled Exception Filter 运行,并在这里继续工作。
难道这种方法真的那么可怕吗 ? 我们要对付这种变态异常 (对于刚入门的兄弟来说,这是恶梦) ,可以在程序使用
SetUnhandledExceptionFilter 的时候,把它拦截
把程序重新加载,设断点在 SetUnhandledExceptionFilter
bp SetUnhandledExceptionFilter
运行程序,一步一步按下 Shift + F9
果然,在大约第 7 次异常的时候,我们断在 77E6BC57 ,这是 SetUnhandledExceptionFilter 的入口
77E6BC57 > 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+4] ; riijj_cr.00401940
77E6BC5B A1 4C14EC77 MOV EAX,DWORD PTR DS:[77EC144C]
77E6BC60 890D 4C14EC77 MOV DWORD PTR DS:[77EC144C],ECX
77E6BC66 C2 0400 RETN 4
我们按了几下 F8,跟随 retn 返回
00401CDB . C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401CE2 . EB 25 JMP SHORT riijj_cr.00401D09
00401CE4 . B8 01000000 MOV EAX,1
00401CE9 . C3 RETN
00401CEA . 8B65 E8 MOV ESP,DWORD PTR SS:[EBP-18]
00401CED . 68 40194000 PUSH riijj_cr.00401940 ; /pTopLevelFilter = riijj_cr.00401940
00401CF2 FF15 48504000 CALL DWORD PTR DS:[<&KERNEL32.SetUnhandl>; KERNEL32.SetUnhandledExceptionFilter
00401CF8 . 6A 0F PUSH 0F <---------我们在这里
00401CFA . E8 18050000 CALL riijj_cr.00402217
00401CFF . 83C4 04 ADD ESP,4
00401D02 . C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D09 > C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D10 > C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D17 > C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D1E > C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D25 > C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D2C > C745 FC FFFFFFF>MOV DWORD PTR SS:[EBP-4],-1
00401D33 > 33C0 XOR EAX,EAX
00401D35 . 8B4D F0 MOV ECX,DWORD PTR SS:[EBP-10]
00401D38 . 64:890D 0000000>MOV DWORD PTR FS:[0],ECX
00401D3F . 5F POP EDI
00401D40 . 5E POP ESI
00401D41 . 5B POP EBX
00401D42 . 8BE5 MOV ESP,EBP
00401D44 . 5D POP EBP
00401D45 . C2 1000 RETN 10
这里便是 crackme 中设定 SetUnhandledExceptionFilter 的地方,当设定完成后,便执行异常错误程序,使程序走进死亡。我们
看见 OD 右边的注释写上 /pTopLevelFilter = riijj_cr.00401940,这说明了放入 SetUnhandledExceptionFilter 的参数,
也就是最后的 SEH 位置,是 00401940
我们只要把这里的代码修改成直接跳进 00401940,便可以避过这个 SEH 陷阱
点选 00401CF2 这一行,右按鼠标,在 OD 选单里找 Assemble
OD 跳出了一个方块,可以让我们输入代码,我们输入 jmp 00401940
完成后按一下 Assemble,发现程序被修改了。我们再把变成红名的这两行 (表示修改了) 选取,右按鼠标,在选单里找
Copy to Executable (复制到程序文件) ,再选 Selection (复制已选取部分)
我们看见 OD 显示修改后的 exe,我们右按鼠标,选 Save file 储存,叫做 crack2.exe
我们把 SetUnhandledExceptionFilter 的地方修改了,当我们用 OD 运行时,程序便会顺利地到达下一个执行位置,不会发生
异常。我们把现在的程序关闭,用 OD 把刚才修改了的 crack2.exe 打开
我们再设定断点在 SetUnhandledExceptionFilter,小心地把所有 SetUnhandledExceptionFilter 的陷阱清除。
bp SetUnhandledExceptionFilter
F9 运行,每按一下 Shift+F9,便留意 OD
果然,我们再断在 SetUnhandledExceptionFilter,按几下 F8 返回,来到了
00401830 . 56 PUSH ESI
00401831 . 6A 00 PUSH 0 ; /pTopLevelFilter = NULL
00401833 . FF15 48504000 CALL DWORD PTR DS:[<&KERNEL32.SetUnhandl>; \SetUnhandledExceptionFilter
00401839 . E8 92FFFFFF CALL riijj_cr.004017D0 <-------我们在这里
0040183E . 68 80694000 PUSH riijj_cr.00406980 ; /Buffer = riijj_cr.00406980
00401843 . 68 F4010000 PUSH 1F4 ; |BufSize = 1F4 (500.)
00401848 . FF15 44504000 CALL DWORD PTR DS:[<&KERNEL32.GetTempPat>; \GetTempPathA
0040184E . 68 74604000 PUSH riijj_cr.00406074 ; /pModule = "kernel32.dll"
00401853 . FF15 40504000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleH>; \GetModuleHandleA
00401859 . 68 C06B4000 PUSH riijj_cr.00406BC0 ; /ProcNameOrOrdinal = ""
0040185E . 50 PUSH EAX ; |hModule
0040185F . FF15 20504000 CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; \GetProcAddress
00401865 . 8BF0 MOV ESI,EAX
00401867 > FFD6 CALL ESI
00401869 . 85C0 TEST EAX,EAX
0040186B . 74 05 JE SHORT riijj_cr.00401872
0040186D . E8 BEF9FFFF CALL riijj_cr.00401230
00401872 > E8 89FEFFFF CALL riijj_cr.00401700
00401877 . E8 34F9FFFF CALL riijj_cr.004011B0
0040187C . C705 58694000 0>MOV DWORD PTR DS:[406958],1
00401886 .^EB DF JMP SHORT riijj_cr.00401867
我们停下来,细心留意一下这段程序,我们看见这里有很多熟悉的名字,例如 GetTempPathA (取得暂存盘路径), GetModuleHandleA
(取得 module 的程序位置), 等等。可见我们已经走近了 crackme 的主要部分。
我们像上一次那样,把 SetUnhandledExceptionFilter 去除掉。可是今次有些不同,它的参数是 0,甚么 ?
SEH 当然不可以是 0,我们翻阅 API 文件,查看资料,发现当 SetUnhandledExceptionFilter 的参数是 0 时,表示把程序现行的
最终 SEH 回复成系统预设的 SEH。很明显,这是用于清楚上一次设定的 SEH
我们要细心思考清楚,为甚么它要把最终 SEH 清除掉呢 ? 这暗示程序没有 SEH 保护,那么我们刚才按 Shift+F9 通过这里
的时候又遇到了那句 "Debugged program was unable to process exception" ,这表示程序的确发生了异常错误
程序发生异常错误,可是没有 SEH 处理,也没有最终 SEH (Unhandled Exception Filter)处理,为甚么呢 ? 这样程序怎样
正常运行呢 ?
我们实在不知道原因,可能程序有别的方法继续运行,当然也可能是程序不想活了,想自行了结
我们为了继续跟踪,只好把这行 00401831 (push) 和 00401833(SetUnhandledExceptionFilter) 这两行以 nop 填充,使它们不干
任何事
点选这两行,右按鼠标,选 Binary -> Fill with NOPs
同样地,选 Copy to Executable,把它储存成 crack3.exe
我们再把 OD 重新加载 crack3.exe,并设定一个断点在刚才的 00401831 ,希望在那里单步跟踪
F9 运行, Shift+F9,一直到达了 00401831,断下了
00401831 . 90 NOP <---我们在这里
00401832 . 90 NOP
00401833 . 90 NOP
00401834 . 90 NOP
00401835 . 90 NOP
00401836 . 90 NOP
00401837 . 90 NOP
00401838 . 90 NOP
00401839 . E8 92FFFFFF CALL riijj_cr.004017D0
0040183E . 68 80694000 PUSH riijj_cr.00406980 ; /Buffer = riijj_cr.00406980
00401843 . 68 F4010000 PUSH 1F4 ; |BufSize = 1F4 (500.)
00401848 . FF15 44504000 CALL DWORD PTR DS:[<&KERNEL32.GetTempPat>; \GetTempPathA
0040184E . 68 74604000 PUSH riijj_cr.00406074 ; /pModule = "kernel32.dll"
00401853 . FF15 40504000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleH>; \GetModuleHandleA
00401859 . 68 C06B4000 PUSH riijj_cr.00406BC0 ; /ProcNameOrOrdinal = ""
0040185E . 50 PUSH EAX ; |hModule
0040185F . FF15 20504000 CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; \GetProcAddress
00401865 . 8BF0 MOV ESI,EAX
00401867 > FFD6 CALL ESI
00401869 . 85C0 TEST EAX,EAX
0040186B . 74 05 JE SHORT riijj_cr.00401872
0040186D . E8 BEF9FFFF CALL riijj_cr.00401230
00401872 > E8 89FEFFFF CALL riijj_cr.00401700
00401877 . E8 34F9FFFF CALL riijj_cr.004011B0
0040187C . C705 58694000 0>MOV DWORD PTR DS:[406958],1
00401886 .^EB DF JMP SHORT riijj_cr.00401867
我们按 F8 单步跟踪,观察程序的流程
我们看见过程调用 004017D0 后,叫一个 buffer 放入 GetTempPath 作参数,GetTempPath 会把系统用来放置暂存盘的路径
写在 buffer 里。当我们跟踪的时候,突然我们在 OD 发现了以下的字句
刚通过 00401839
0040183E . 68 80694000 PUSH riijj_cr.00406980 ; /Buffer = riijj_cr.00406980
00401843 . 68 F4010000 PUSH 1F4 ; |BufSize = 1F4 (500.)
00401848 . FF15 44504000 CALL DWORD PTR DS:[<&KERNEL32.GetTempPat>; \GetTempPathA
0040184E . 68 74604000 PUSH riijj_cr.00406074 ; /pModule = "kernel32.dll"
00401853 . FF15 40504000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleH>; \GetModuleHandleA
00401859 . 68 C06B4000 PUSH riijj_cr.00406BC0 ; /ProcNameOrOrdinal = "IsDebuggerPresent"
0040185E . 50 PUSH EAX ; |hModule
0040185F . FF15 20504000 CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; \GetProcAddress
看见这行的右边
0401859 . 68 C06B4000 PUSH riijj_cr.00406BC0 ; /ProcNameOrOrdinal = "IsDebuggerPresent"
这是一行字符串,内容是一个 API 的名字 "IsDebuggerPresent" 它的功用是检查程序是否正在被调试,我们知道,程序使用这个
API 的目的只有一个,就在防止被调试。如果 IsDebuggerPresent 返回 true,程序便使用一切方法来终止,甚至破坏你的系统。
这几行的设置,是把 IsDebuggerPresent 这个字符串在运行时动态解密出来,再使用 GetModuleHandleA,在 kernel32.dll 中得到
它的位置,然后呼叫。它这样做显然是为了避开破解者的查看,可是最终我们依然是发现了。
我们看看这里
0040186B . 74 05 JE SHORT riijj_cr.00401872
如果 IsDebuggerPresent 的结果为 false (0),便跳到 00401872,我们就是要这样。其实 OD 有一个插件是可以把 OD 隐藏,避过
这种检查,我们今次使用人手修改
在 jmp 的下一行
0040186D . E8 BEF9FFFF CALL riijj_cr.00401230
把这行 NOP 掉,程序即使执行到这里,也不会做成伤害了
点选 0040186D 这行,右按鼠标,选 Binary -> Fill with NOPs
储存到 crack3.exe
我们把这里的检查修好了,重新加载 crack3.exe,再尝试运行
F9 运行, Shift+ F9 几次
果然,crackme 的窗口出现在 window 的下方工具列,程序加载了一些 dll,真正起动了
可是,很快我们便遇到了一个异常错误,身处 kernel32 的领空,我们按一下 Shift+F9 ,便变成了
Debugged program was unable to process exception,真是太过份了。
这个程序的某一处也使用了异常陷阱,我们不要放弃,继续跟下去
我们要在刚才的 0040186D 设断回,从那里开始单步分析
[9]. 与反跟踪决战
我们喝一杯水,头脑清醒过来,把 crack3.exe 重新加载,设断点在 0040186D
F9 运行,几次 Shift+F9 ,来到了断点
0040186D . 90 NOP <---我们在这里
0040186E . 90 NOP
0040186F . 90 NOP
00401870 . 90 NOP
00401871 . 90 NOP
00401872 > E8 89FEFFFF CALL riijj_cr.00401700
00401877 . E8 34F9FFFF CALL riijj_cr.004011B0
0040187C . C705 58694000 0>MOV DWORD PTR DS:[406958],1
00401886 .^EB DF JMP SHORT riijj_cr.00401867
我们看见这里有两个 call,不知道是做甚么的,我们用 F7 跟进去看看
进入 0401700
00401700 /$ 81EC 4C030000 SUB ESP,34C <--------我们在这里
00401706 |. 57 PUSH EDI
00401707 |. 6A 00 PUSH 0 ; /ProcessID = 0
00401709 |. 6A 02 PUSH 2 ; |Flags = TH32CS_SNAPPROCESS
0040170B |. C74424 0C 28010>MOV DWORD PTR SS:[ESP+C],128 ; |
00401713 |. C78424 34010000>MOV DWORD PTR SS:[ESP+134],224 ; |
0040171E |. C705 886B4000 C>MOV DWORD PTR DS:[406B88],0C3 ; |
00401728 |. E8 A7060000 CALL <JMP.&KERNEL32.CreateToolhelp32Snap>; \CreateToolhelp32Snapshot
0040172D |. 8BF8 MOV EDI,EAX
0040172F |. 85FF TEST EDI,EDI
00401731 |. 0F84 8D000000 JE riijj_cr.004017C4
00401737 |. 8D4424 04 LEA EAX,DWORD PTR SS:[ESP+4]
0040173B |. 50 PUSH EAX ; /pProcessentry
0040173C |. 57 PUSH EDI ; |hSnapshot
0040173D |. E8 8C060000 CALL <JMP.&KERNEL32.Process32First> ; \Process32First
00401742 |. A1 806B4000 MOV EAX,DWORD PTR DS:[406B80]
00401747 |. 85C0 TEST EAX,EAX
00401749 |. 74 26 JE SHORT riijj_cr.00401771
0040174B |. 8B0D 9C6B4000 MOV ECX,DWORD PTR DS:[406B9C]
00401751 |. 85C9 TEST ECX,ECX
00401753 |. 75 1C JNZ SHORT riijj_cr.00401771
00401755 |. 68 E0114000 PUSH riijj_cr.004011E0 ; /Timerproc = riijj_cr.004011E0
0040175A |. 6A 64 PUSH 64 ; |Timeout = 100. ms
0040175C |. 6A 02 PUSH 2 ; |TimerID = 2
0040175E |. 50 PUSH EAX ; |hWnd => NULL
0040175F |. FF15 4C514000 CALL DWORD PTR DS:[<&USER32.SetTimer>] ; \SetTimer
00401765 |. A1 9C6B4000 MOV EAX,DWORD PTR DS:[406B9C]
0040176A |. 0C 01 OR AL,1
0040176C |. A3 9C6B4000 MOV DWORD PTR DS:[406B9C],EAX
00401771 |> 56 PUSH ESI
00401772 |> 8B4C24 10 /MOV ECX,DWORD PTR SS:[ESP+10]
00401776 |. 51 |PUSH ECX ; /ProcessID
00401777 |. 6A 08 |PUSH 8 ; |Flags = TH32CS_SNAPMODULE
00401779 |. E8 56060000 |CALL <JMP.&KERNEL32.CreateToolhelp32Sna>; \CreateToolhelp32Snapshot
0040177E |. 8D9424 30010000 |LEA EDX,DWORD PTR SS:[ESP+130]
00401785 |. 8BF0 |MOV ESI,EAX
00401787 |. 52 |PUSH EDX ; /pModuleentry
00401788 |. 56 |PUSH ESI ; |hSnapshot
00401789 |. E8 3A060000 |CALL <JMP.&KERNEL32.Module32First> ; \Module32First
0040178E |. 85C0 |TEST EAX,EAX
00401790 |. 74 10 |JE SHORT riijj_cr.004017A2
00401792 |> 8D8424 30010000 |LEA EAX,DWORD PTR SS:[ESP+130]
00401799 |. 50 |PUSH EAX
0040179A |. E8 A1FEFFFF |CALL riijj_cr.00401640
0040179F |. 83C4 04 |ADD ESP,4
004017A2 |> 8D8C24 30010000 |LEA ECX,DWORD PTR SS:[ESP+130]
004017A9 |. 51 |PUSH ECX ; /pModuleentry
004017AA |. 56 |PUSH ESI ; |hSnapshot
004017AB |. E8 12060000 |CALL <JMP.&KERNEL32.Module32Next> ; \Module32Next
004017B0 |. 85C0 |TEST EAX,EAX
004017B2 |.^75 DE |JNZ SHORT riijj_cr.00401792
004017B4 |. 8D5424 08 |LEA EDX,DWORD PTR SS:[ESP+8]
004017B8 |. 52 |PUSH EDX ; /pProcessentry
004017B9 |. 57 |PUSH EDI ; |hSnapshot
004017BA |. E8 FD050000 |CALL <JMP.&KERNEL32.Process32Next> ; \Process32Next
004017BF |. 85C0 |TEST EAX,EAX
004017C1 |.^75 AF \JNZ SHORT riijj_cr.00401772
004017C3 |. 5E POP ESI
004017C4 |> 5F POP EDI
004017C5 |. 81C4 4C030000 ADD ESP,34C
004017CB \. C3 RETN
我们一看之下,发现这个 call 的里面十分丰富,使用各种 API 进行一系列处理,这的确让我们感到有点不安,因为翻阅 API 文件后,
我们发现 CreateToolhelp32Snapshot 这个 API 的功用,它是可以得到目前系统正在运行的程序名单
甚么 ? 这个 crackme 为甚么取得程序名单 ? 我们想,如果它看见一个程序叫做 olly dbg 的话,估计它会自行终止
我们可不可以把这个 call 用 NOP 删去 ? 我们不可以这样,因为我们不了解程序的运行,有些软件会把重要的运算部份故意放在反调试
的程序里,让我们错误地 NOP 掉,使程序不能运作。我们应该尽量把修改减少,只修改导致死亡的那些地方。
我们按 F8 单步,看看在那一行会死掉
我们跟到 004017B2 这行,发现它在 loop。这是典型的 CreateToolhelp32Snapshot 使用方式,是把名单中每一个名字作检查,
我们留意到 0040179A 有一个 call,它可能就是检查名字的地方,我们用 F7 跟进去看看
进入 0040179A
00401640 /$ 81EC F4010000 SUB ESP,1F4 <---我们在这里
00401646 |. 53 PUSH EBX
00401647 |. 56 PUSH ESI
00401648 |. 57 PUSH EDI
00401649 |. 32DB XOR BL,BL
0040164B |. B9 7C000000 MOV ECX,7C
00401650 |. 33C0 XOR EAX,EAX
00401652 |. 8D7C24 0D LEA EDI,DWORD PTR SS:[ESP+D]
00401656 |. 885C24 0C MOV BYTE PTR SS:[ESP+C],BL
0040165A |. F3:AB REP STOS DWORD PTR ES:[EDI]
0040165C |. 8B9424 04020000 MOV EDX,DWORD PTR SS:[ESP+204]
00401663 |. 83C9 FF OR ECX,FFFFFFFF
00401666 |. 66:AB STOS WORD PTR ES:[EDI]
00401668 |. AA STOS BYTE PTR ES:[EDI]
00401669 |. 8D7A 20 LEA EDI,DWORD PTR DS:[EDX+20]
0040166C |. 33C0 XOR EAX,EAX
0040166E |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00401670 |. F7D1 NOT ECX
00401672 |. 49 DEC ECX
00401673 |. 8DB2 20010000 LEA ESI,DWORD PTR DS:[EDX+120]
00401679 |. 2BD1 SUB EDX,ECX
0040167B |. 8BFE MOV EDI,ESI
0040167D |. 83C9 FF OR ECX,FFFFFFFF
00401680 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00401682 |. F7D1 NOT ECX
00401684 |. 49 DEC ECX
00401685 |. 8BFE MOV EDI,ESI
00401687 |. 889C0A 20010000 MOV BYTE PTR DS:[EDX+ECX+120],BL
0040168E |. 83C9 FF OR ECX,FFFFFFFF
00401691 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00401693 |. F7D1 NOT ECX
00401695 |. 2BF9 SUB EDI,ECX
00401697 |. 8D5424 0C LEA EDX,DWORD PTR SS:[ESP+C]
0040169B |. 8BC1 MOV EAX,ECX
0040169D |. 8BF7 MOV ESI,EDI
0040169F |. 8BFA MOV EDI,EDX
004016A1 |. 8D5424 0C LEA EDX,DWORD PTR SS:[ESP+C]
004016A5 |. C1E9 02 SHR ECX,2
004016A8 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
004016AA |. 8BC8 MOV ECX,EAX
004016AC |. 33C0 XOR EAX,EAX
004016AE |. 83E1 03 AND ECX,3
004016B1 |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
004016B3 |. BF A06B4000 MOV EDI,riijj_cr.00406BA0 ; ASCII "ollydbg.ini"
004016B8 |. 83C9 FF OR ECX,FFFFFFFF
004016BB |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
004016BD |. F7D1 NOT ECX
004016BF |. 2BF9 SUB EDI,ECX
004016C1 |. 8BF7 MOV ESI,EDI
004016C3 |. 8BD9 MOV EBX,ECX
004016C5 |. 8BFA MOV EDI,EDX
004016C7 |. 83C9 FF OR ECX,FFFFFFFF
004016CA |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
004016CC |. 8BCB MOV ECX,EBX
004016CE |. 4F DEC EDI
004016CF |. C1E9 02 SHR ECX,2
004016D2 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
004016D4 |. 8BCB MOV ECX,EBX
004016D6 |. 8D4424 0C LEA EAX,DWORD PTR SS:[ESP+C]
004016DA |. 83E1 03 AND ECX,3
004016DD |. 50 PUSH EAX ; /FileName
004016DE |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>; |
004016E0 |. FF15 28504000 CALL DWORD PTR DS:[<&KERNEL32.GetFileAtt>; \GetFileAttributesA
004016E6 |. 5F POP EDI
004016E7 |. 5E POP ESI
004016E8 |. 83F8 FF CMP EAX,-1
004016EB |. 5B POP EBX
004016EC |. 74 05 JE SHORT riijj_cr.004016F3
004016EE |. E8 3DFBFFFF CALL riijj_cr.00401230
004016F3 |> 81C4 F4010000 ADD ESP,1F4
004016F9 \. C3 RETN
今次我们的心里也是一阵寒意,因为我们细看一下代码,在 004016B3 这行看见
004016B3 |. BF A06B4000 MOV EDI,riijj_cr.00406BA0 ; ASCII "ollydbg.ini"
OD 右边的注释写了 ollydbg.ini,这是 OD 的设定档,为甚么会在这个 crackme 里提及 ? 我们相信这个 crackme 会检查这个档,
我们继续按 F8 单步,继续跟下去
一直来到了 004016EE,这里有一个 jmp,我们跳了,到达返回
我们把它的结构想象一下,它使用 CreateToolhelp32Snapshot 取得程序名单,再在每一个程序检查的时候进入这个 call 里,这跟
OD 的 ollydbg.ini 有甚么关系呢 ? 我们估计它得到了每一个程序的路径,如果在路径里发现了 ollydbg.ini 这个设定档,很明显它
便知道了我们正在使用 OD。
我们又注意到 GetFileAttributesA 这个 API,它的用途是查看档案的属性,假如档案不存在,会返回 -1。我们看看 004016E8
004016E8 |. 83F8 FF CMP EAX,-1
它正是检查这个值。
那么,004016EE 这一行
004016EE |. E8 3DFBFFFF CALL riijj_cr.00401230
估计就是走进死亡的道路了。我们尝试在这一行设断点,看看甚么时候会停在这里。
设断点在 004016EE,然后 F9 运行
crackme 顺利加载 dll,工作列出现了窗口,这时候程序停在断点。估计这是正准备进去死的,我们按 F8 单步通过,看看会不会死
F8 一下
果然,OD 出现错误,程序死了。
我们尝试把这一行 NOP 掉,储存到 crack3.exe ,再重新加载程序
F9 运行, SHift+F9 几下
很神奇 ! 我们的 crackme 终于出现了,可以看见 crackme 窗口,程序顺利运行。
[10]. 破解
我们经历了反调试的战役,现在开始尝试破解算法,检出序号
设下标准断点 bp GetWindowTextA
输入注册名 riijj,序号 AAAABBBB
按一下注册,程序没有反应,断点没有断下
这种情况暗示了 crackme 并没有使用 GetWindowTextA 这种方式来得到字符串,其实得到字符串的方法很多,我们要尝试用其它方法拦截
我们尝试找出程序处理 WM_COMMAND 的程序,进行分析。 WM_COMMAND 代表当按钮被按下的时候,系统向程序发出的信息
我们在 OD 的选单,找 View-> window
OD 出现一个列表,里面是这个程序的窗口
Windows
Handle Title Parent WinProc ID Style ExtStyle Thread ClsProc
Class
002002CA Riijj Crackme - 20041121 Topmost 14CA0000 00000100 Main 004013E0
Class123
K00280368 002002CA 5000004C Main 77E0627B
#32770
IK001C02C4 (name must be 4-15 characters lo 00280368 0000FFFF 50020000 00000004 Main 77E0629F
Static
IK001D02C8 code name: PurpleFlame 00280368 0000FFFF 50020000 00000004 Main 77E0629F
Static
IK002002BC 00280368 000000CA 50000080 Main 77E06022
Edit
IK002002C0 00280368 000000C9 50000080 Main 77E06022
Edit
IK002B0374 Register 00280368 000003EE 50010000 00000004 Main 77E062C3
Button
IK003C036E Serial: 00280368 0000FFFF 50020000 00000004 Main 77E0629F
Static
IE0040035E Name: 00280368 0000FFFF 50020000 00000004 Main 77E0629F
Static
E003603C2 Default IME 002002CA 8C000000 Main 77E3E2D5
IME
你看见的可能跟我不一样。我们可以看见, "Riijj Crackme - 20041121" 这个 window 排在第一位,它是这个程序的主要窗口,其次
是 Dialog 的窗口 00280368
我们的注册按钮位于 00280368 之下,这表示当按钮被按下的时候,父窗口 00280368 会接收到 WM_COMMAND
我们点选 00280368 这一行,按鼠标右键,选 Toggle breakpoint on classproc (设定断点)
这时候 ClsProc 的位置变成红色,表示断点已经设定在处理信息的程序段
我们尝试按一下 crackme ,这时候 crackme 接收到信息 (图像信息),断在刚才设定的断点
77E0627B > 8B4C24 04 MOV ECX,DWORD PTR SS:[ESP+4] <-----我们在这里
77E0627F E8 4CCDFEFF CALL USER32.77DF2FD0
77E06284 85C0 TEST EAX,EAX
77E06286 74 14 JE SHORT USER32.77E0629C
77E06288 6A 01 PUSH 1
77E0628A FF7424 14 PUSH DWORD PTR SS:[ESP+14]
77E0628E FF7424 14 PUSH DWORD PTR SS:[ESP+14]
77E06292 FF7424 14 PUSH DWORD PTR SS:[ESP+14]
77E06296 50 PUSH EAX
现在,我们身处 USER32 的领空中,我们怎知道系统何时呼叫 crackme 的按钮处理程序呢 ?
简单来说,我们可以不断以 F7 单步一步一步地跟踪,直至走进 crackme 的领空里
可是,这种方法实在是太辛苦了,我们采用另一个方法
我们先按一下 F2 清除断点
再在 OD 的选单找 View -> Memory
OD 出现了程序的内存列表,我们尝试找出 crackme 的程序代码区段,找到了这行
00401000 00004000 riijj_cr .text code Imag R RWE
这是 crackme 的 .text 区段 (程序代码段),我们只要在这个区段设下断点,便可以在系统呼叫按钮处理程序的时候停下
我们按一下 F2,位置变成了红色,断点设定好
现在我们把内存窗口关掉,用 F9 运行程序,程序从 77E0627B 开始运行
突然,程序停下,我身处 crackme 的领空
00401160 . 817C24 08 1101>CMP DWORD PTR SS:[ESP+8],111 <----我们在这里
00401168 . 75 14 JNZ SHORT riijj_cr.0040117E
0040116A . 817C24 0C EE03>CMP DWORD PTR SS:[ESP+C],3EE
00401172 . 75 0A JNZ SHORT riijj_cr.0040117E
00401174 . C705 846B4000 >MOV DWORD PTR DS:[406B84],1
0040117E > 33C0 XOR EAX,EAX
00401180 . C2 1000 RETN 10
这里便是程序处理注册按钮的地方了,这里只有短短几行,究竟它干了甚么呢 ?
00401160 . 817C24 08 1101>CMP DWORD PTR SS:[ESP+8],111 // 把 msg 与 111 比较, 111 就是 WM_COMMAND 的值
00401168 . 75 14 JNZ SHORT riijj_cr.0040117E // 如果不是 WM_COMMAND ,便跳到完结
0040116A . 817C24 0C EE03>CMP DWORD PTR SS:[ESP+C],3EE // 把 WM_COMMAND 中的参数与 3ee 比较 (这是按钮的 ID)
00401172 . 75 0A JNZ SHORT riijj_cr.0040117E // 如果不是按钮,便跳
00401174 . C705 846B4000 >MOV DWORD PTR DS:[406B84],1 // 如果是按钮,便把 DS:[406B84] 这个值设定成 1
0040117E > 33C0 XOR EAX,EAX // 设定返回值为 0,准备离开
00401180 . C2 1000 RETN 10 // 离开
甚么 ? 这段程序只是检查信息是否 WM_COMMAND,如果是的话便检查是不是按钮的 ID,如果也是的话,便做了一件事,
就是把 DS:[406B84] 这个变量设定成 1
这些就是这个 crackme 的注册按钮所做的事,实在是有点奇怪,只是把一个值设定成 1,便能够检查序号吗 ?
我们想一想,便估计到这个 crackme 的某一处,必定在检查这个值是否为 1 ,如果是的话,便进行序号检查。
我们要知道这个秘密的程序,必须要用硬件断点
我们按一下 F9,让程序正常运行
在 OD 下方的内存窗口,输入 406B84 ,来到了 406B84 的位置
按一下鼠标右键,选 Long -> Hex ,使显示模式变成 Long (4 字节)
我们发现,406B84 的值是 0,现在点选在 406B84 的资料上,
按鼠标右键,选 breakpoint -> hardware, on access -> DWORD ,这样便设定了硬件断点在这 4 个字位上
这时候,程序突然断下,看来它被我们的断点拦截了
00401030 /$ A1 846B4000 MOV EAX,DWORD PTR DS:[406B84]
00401035 |. 85C0 TEST EAX,EAX <---------我们在这里
00401037 |. 0F84 90000000 JE riijj_cr.004010CD
0040103D |. 56 PUSH ESI
0040103E |. 57 PUSH EDI
0040103F |. E8 BCFFFFFF CALL riijj_cr.00401000
00401044 |. BF F06B4000 MOV EDI,riijj_cr.00406BF0 ; ASCII "riijj"
00401049 |. 83C9 FF OR ECX,FFFFFFFF
0040104C |. 33C0 XOR EAX,EAX
0040104E |. C705 846B4000 >MOV DWORD PTR DS:[406B84],0
00401058 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
0040105A |. F7D1 NOT ECX
0040105C |. 2BF9 SUB EDI,ECX
0040105E |. C705 506C4000 >MOV DWORD PTR DS:[406C50],1
00401068 |. 8BC1 MOV EAX,ECX
0040106A |. 8BF7 MOV ESI,EDI
0040106C |. BF 406C4000 MOV EDI,riijj_cr.00406C40 ; ASCII "riijj"
00401071 |. C1E9 02 SHR ECX,2
00401074 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
00401076 |. 8BC8 MOV ECX,EAX
00401078 |. 33C0 XOR EAX,EAX
0040107A |. 83E1 03 AND ECX,3
0040107D |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
0040107F |. BF 006C4000 MOV EDI,riijj_cr.00406C00 ; ASCII "AAAABBBB"
00401084 |. 83C9 FF OR ECX,FFFFFFFF
00401087 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00401089 |. F7D1 NOT ECX
0040108B |. 2BF9 SUB EDI,ECX
0040108D |. A1 7C6B4000 MOV EAX,DWORD PTR DS:[406B7C]
00401092 |. 8BD1 MOV EDX,ECX
00401094 |. 8BF7 MOV ESI,EDI
00401096 |. BF 206C4000 MOV EDI,riijj_cr.00406C20 ; ASCII "AAAABBBB"
0040109B |. C1E9 02 SHR ECX,2
0040109E |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
004010A0 |. 8BCA MOV ECX,EDX
004010A2 |. 83E1 03 AND ECX,3
004010A5 |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[>
004010A7 |. 5F POP EDI
004010A8 |. 5E POP ESI
004010A9 |. 85C0 TEST EAX,EAX
004010AB |. 74 16 JE SHORT riijj_cr.004010C3
004010AD |. 8B0D 806B4000 MOV ECX,DWORD PTR DS:[406B80]
004010B3 |. 51 PUSH ECX
004010B4 |. 68 206C4000 PUSH riijj_cr.00406C20 ; ASCII "AAAABBBB"
004010B9 |. 68 406C4000 PUSH riijj_cr.00406C40 ; ASCII "riijj"
004010BE |. FFD0 CALL EAX
004010C0 |. 83C4 0C ADD ESP,0C
004010C3 |> C705 506C4000 >MOV DWORD PTR DS:[406C50],0
004010CD \> C3 RETN
有一种很强烈的感觉吗 ? 这里满是我们的注册名字和序号,可能这里是我们最后的目的地,也是最后的战场。
00401030 /$ A1 846B4000 MOV EAX,DWORD PTR DS:[406B84] // 把那个按钮的 "神奇值 1" 放到 EAX
00401035 |. 85C0 TEST EAX,EAX // 检查 EAX 是否 0
00401037 |. 0F84 90000000 JE riijj_cr.004010CD // 如果是 0,便离开
0040103D |. 56 PUSH ESI // 保存 esi
0040103E |. 57 PUSH EDI // 保存 edi
0040103F |. E8 BCFFFFFF CALL riijj_cr.00401000 // 呼叫
我们现在把硬件断点清除,在 OD 的选单,找 Debug -> Hardware breakpoints
把第一个 breakpoint 按一下 Delete1
现在,我们在 0040103D 设下断点,当我们按注册的时候,便会停在这里
设断点后,按 F9 让程序运行
现在,我们按一下注册
程序断在 0040103D,开始分析
0040103D |. 56 PUSH ESI // 保存 esi
0040103E |. 57 PUSH EDI // 保存 edi
0040103F |. E8 BCFFFFFF CALL riijj_cr.00401000 // 呼叫
按一下 F7 进入
00401000 /$ C705 64694000 >MOV DWORD PTR DS:[406964],1 // 把 [406964] 设成 1
0040100A |> 813D 746B4000 >/CMP DWORD PTR DS:[406B74],0FF // 把 [406B74] 与 ff 比较
00401014 |. 74 09 |JE SHORT riijj_cr.0040101F // 如果相等,便跳
00401016 |. A1 64694000 |MOV EAX,DWORD PTR DS:[406964] // 把刚才的 [406964] 放到 eax, eax 现在是 1
0040101B |. 85C0 |TEST EAX,EAX // 检查 eax 是否 0
0040101D |. 75 07 |JNZ SHORT riijj_cr.00401026 // 如果不是 0,便跳
0040101F |> E8 0C040000 |CALL riijj_cr.00401430 // 再呼叫
00401024 |.^EB E4 \JMP SHORT riijj_cr.0040100A
00401026 \> C3 RETN
我们没有进去 00401430,去到 retn 返回
00401044 |. BF F06B4000 MOV EDI,riijj_cr.00406BF0 ; ASCII "riijj" // edi 指向注册名字位置
00401049 |. 83C9 FF OR ECX,FFFFFFFF // ecx = ffffffff
0040104C |. 33C0 XOR EAX,EAX // eax = 0
0040104E |. C705 846B4000 >MOV DWORD PTR DS:[406B84],0 // [406B84] = 1
00401058 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI] // 检查注册名字的长度,典型的结构
0040105A |. F7D1 NOT ECX // ecx 现在是注册名字长度 + 1 ,值是 6
0040105C |. 2BF9 SUB EDI,ECX // edi - 6,edi 现在指向注册名字
0040105E |. C705 506C4000 >MOV DWORD PTR DS:[406C50],1 // [406C50] = 1
00401068 |. 8BC1 MOV EAX,ECX // eax = 注册名字长度 +1
0040106A |. 8BF7 MOV ESI,EDI // esi = 注册名字位置
0040106C |. BF 406C4000 MOV EDI,riijj_cr.00406C40 ; ASCII "riijj" // 把 00406C40 放入 edi
00401071 |. C1E9 02 SHR ECX,2 // 把 ecx 向右 bit shift 2,现在值是 1
00401074 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> // 把 esi 指向的字符串复制到 edi, ecx 个位
00401076 |. 8BC8 MOV ECX,EAX // ecx = 注册名字长度 +1
00401078 |. 33C0 XOR EAX,EAX // eax = 0
0040107A |. 83E1 03 AND ECX,3 // ecx AND 3
0040107D |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[> // 把 esi 指向的字符串复制到 edi, ecx 个位
0040107F |. BF 006C4000 MOV EDI,riijj_cr.00406C00 ; ASCII "AAAABBBB" // edi 指向序号位置
00401084 |. 83C9 FF OR ECX,FFFFFFFF // ecx = ffffffff
00401087 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI] // 检查注册名字的长度,典型的结构
00401089 |. F7D1 NOT ECX // ecx 现在是注册名字长度 + 1 ,值是 9
0040108B |. 2BF9 SUB EDI,ECX // edi - 9, edi 现在指向序号
0040108D |. A1 7C6B4000 MOV EAX,DWORD PTR DS:[406B7C]
0040108D 这一行,把 [406B7C] 的值放入 eax
我们在 OD 中看见,OD 提示 EAX 的值是 pf1.happytime,这是甚么 ?
我们的 crackme 附带一个 dll ?,它的名字正是 pf1.dll ,看来序号检查是使用了这个 dll 中的 happytime 这个程序
00401092 |. 8BD1 MOV EDX,ECX // edx = 序号长度 +1
00401094 |. 8BF7 MOV ESI,EDI // esi 现在指向序号
00401096 |. BF 206C4000 MOV EDI,riijj_cr.00406C20 ; ASCII "AAAABBBB" // edi 指向 00406C20
0040109B |. C1E9 02 SHR ECX,2 // 把 ecx 向右 bit shift 2,现在值是 2
0040109E |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS> // 把 esi 指向的字符串复制到 edi, ecx 个位
004010A0 |. 8BCA MOV ECX,EDX // ecx = 序号长度 +1
004010A2 |. 83E1 03 AND ECX,3 // ecx AND 3
004010A5 |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[> // 把 esi 指向的字符串复制到 edi, ecx 个位
004010A7 |. 5F POP EDI // 回复 edi
004010A8 |. 5E POP ESI // 回复 esi
004010A9 |. 85C0 TEST EAX,EAX // 检查 eax 是否 0,现在 eax 是 happytime 的位置
004010AB |. 74 16 JE SHORT riijj_cr.004010C3 // 如果是 0,便跳到 004010C3 离开
004010AD |. 8B0D 806B4000 MOV ECX,DWORD PTR DS:[406B80] // 把 [406B80] 放入 ecx,值是 2402ca
004010B3 |. 51 PUSH ECX // push 参数
004010B4 |. 68 206C4000 PUSH riijj_cr.00406C20 ; ASCII "AAAABBBB" // push 参数, 序号
004010B9 |. 68 406C4000 PUSH riijj_cr.00406C40 ; ASCII "riijj" // push 参数,注册名字
004010BE |. FFD0 CALL EAX // 呼叫 happytime
004010C0 |. 83C4 0C ADD ESP,0C // 清理 stack 空间
004010C3 |> C705 506C4000 >MOV DWORD PTR DS:[406C50],0 // 把 [406C50] 设定为 0
004010CD \> C3 RETN // 离开
现在,我们知道这段程序进行了一些检查,并拿了 happytime 的位置,把注册名字和序号都掉进去运行了。
我们知道,这个 crackme 的序号检查,就是在 pf1.dll 的 happytime 里
我们在 004010BE 的那一行,用 F7 单步进入,开始战斗
00F31010 > 81EC 84000000 SUB ESP,84
00F31016 B9 19000000 MOV ECX,19
00F3101B 33C0 XOR EAX,EAX
00F3101D 53 PUSH EBX
00F3101E 55 PUSH EBP
00F3101F 56 PUSH ESI
00F31020 57 PUSH EDI
00F31021 BE 5450F300 MOV ESI,pf1.00F35054 ; ASCII
"fytugjhkuijonlbpvqmcnxbvzdaeqrwtryetdgfkgphonuivmdbxfanqydexzwztqnkcfkvcpvlbmhotyiufdkdnjxuzyqhfstae"
00F31026 8D7C24 30 LEA EDI,DWORD PTR SS:[ESP+30]
00F3102A F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
00F3102C 8B9C24 98000000 MOV EBX,DWORD PTR SS:[ESP+98]
00F31033 83C9 FF OR ECX,FFFFFFFF
00F31036 8BFB MOV EDI,EBX
00F31038 F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00F3103A 8BBC24 9C000000 MOV EDI,DWORD PTR SS:[ESP+9C]
00F31041 F7D1 NOT ECX
00F31043 49 DEC ECX
00F31044 8BE9 MOV EBP,ECX
00F31046 83C9 FF OR ECX,FFFFFFFF
00F31049 F2:AE REPNE SCAS BYTE PTR ES:[EDI]
00F3104B F7D1 NOT ECX
00F3104D 49 DEC ECX
00F3104E 8D7C24 10 LEA EDI,DWORD PTR SS:[ESP+10]
00F31052 8BD1 MOV EDX,ECX
00F31054 B9 07000000 MOV ECX,7
00F31059 F3:AB REP STOS DWORD PTR ES:[EDI]
00F3105B 66:AB STOS WORD PTR ES:[EDI]
00F3105D 83FD 0F CMP EBP,0F
00F31060 AA STOS BYTE PTR ES:[EDI]
00F31061 0F8F A1000000 JG pf1.00F31108
00F31067 83FD 04 CMP EBP,4
00F3106A 0F8C 98000000 JL pf1.00F31108
00F31070 83FA 1E CMP EDX,1E
00F31073 0F8F 8F000000 JG pf1.00F31108
00F31079 83FA 04 CMP EDX,4
00F3107C 0F8C 86000000 JL pf1.00F31108
00F31082 33C9 XOR ECX,ECX
00F31084 85ED TEST EBP,EBP
00F31086 76 26 JBE SHORT pf1.00F310AE
00F31088 8D7424 11 LEA ESI,DWORD PTR SS:[ESP+11]
00F3108C 0FBE0419 MOVSX EAX,BYTE PTR DS:[ECX+EBX]
00F31090 99 CDQ
00F31091 BF 62000000 MOV EDI,62
00F31096 83C6 02 ADD ESI,2
00F31099 F7FF IDIV EDI
00F3109B 41 INC ECX
00F3109C 3BCD CMP ECX,EBP
00F3109E 8A4414 30 MOV AL,BYTE PTR SS:[ESP+EDX+30]
00F310A2 8A5414 31 MOV DL,BYTE PTR SS:[ESP+EDX+31]
00F310A6 8846 FD MOV BYTE PTR DS:[ESI-3],AL
00F310A9 8856 FE MOV BYTE PTR DS:[ESI-2],DL
00F310AC ^72 DE JB SHORT pf1.00F3108C
00F310AE 8BB424 9C000000 MOV ESI,DWORD PTR SS:[ESP+9C]
00F310B5 C64424 2E 00 MOV BYTE PTR SS:[ESP+2E],0
00F310BA 8D4424 10 LEA EAX,DWORD PTR SS:[ESP+10]
00F310BE 8A10 MOV DL,BYTE PTR DS:[EAX]
00F310C0 8A1E MOV BL,BYTE PTR DS:[ESI]
00F310C2 8ACA MOV CL,DL
00F310C4 3AD3 CMP DL,BL
00F310C6 75 1E JNZ SHORT pf1.00F310E6
00F310C8 84C9 TEST CL,CL
00F310CA 74 16 JE SHORT pf1.00F310E2
00F310CC 8A50 01 MOV DL,BYTE PTR DS:[EAX+1]
00F310CF 8A5E 01 MOV BL,BYTE PTR DS:[ESI+1]
00F310D2 8ACA MOV CL,DL
00F310D4 3AD3 CMP DL,BL
00F310D6 75 0E JNZ SHORT pf1.00F310E6
00F310D8 83C0 02 ADD EAX,2
00F310DB 83C6 02 ADD ESI,2
00F310DE 84C9 TEST CL,CL
00F310E0 ^75 DC JNZ SHORT pf1.00F310BE
00F310E2 33C0 XOR EAX,EAX
00F310E4 EB 05 JMP SHORT pf1.00F310EB
00F310E6 1BC0 SBB EAX,EAX
00F310E8 83D8 FF SBB EAX,-1
00F310EB 85C0 TEST EAX,EAX
00F310ED 75 19 JNZ SHORT pf1.00F31108
00F310EF 50 PUSH EAX
00F310F0 8B8424 A4000000 MOV EAX,DWORD PTR SS:[ESP+A4]
00F310F7 68 4C50F300 PUSH pf1.00F3504C ; ASCII "Crackme"
00F310FC 68 3050F300 PUSH pf1.00F35030 ; ASCII "Registration successful !"
00F31101 50 PUSH EAX
00F31102 FF15 B040F300 CALL DWORD PTR DS:[<&USER32.MessageBoxA>] ; USER32.MessageBoxA
00F31108 5F POP EDI
00F31109 5E POP ESI
00F3110A 5D POP EBP
00F3110B 5B POP EBX
00F3110C 81C4 84000000 ADD ESP,84
00F31112 C3 RETN
我们看见光明之路了 ! 在程序的右方看见一句简单的成功注册信息,我们的任务是检出序号,而对于这个 crackme 来说,我们
学习的重点是如果处理程序反跟踪。
我们现在可以分析算法,可是,我决定把这个任务留给你去完成了。 (作为这个 crackme 的作者,我知道它的算法
并不艰辛。)
简单来说,程序的开始部份检查名字和序号的长度,接着便使用对照表的方法,计算出正确的序号,再跟我们输入的序号进行比较。
现在我们只想检出序号,完成今次的任务。我们一直按 F8 单步,直到 00F310BE 的时候,便可以在 OD 的 EAX 显示中看见正确的序号
vqkukuuiui
我们关掉 OD ,打开原本没有修改的 crackme (必须要关掉 OD,因为 crackme 对 OD 有检测),
输入 riijj 和 vqkukuuiui 注册
成功注册了。
(注 : 这个 crackme 附带的 dll 档,是不可以直接用调试器分析的,因为它使用了密轮加密,所以只有在运行的时候,才可以分析
它。)
[11]. 总结
在看雪,我把这个 crackme 3 的级别定为入门级至中级,这是因为当初入门的读者真正接触市面上的软件时,总会深深体会到
现今软件保护技术的可怕。一些软件使用强劲的壳,再加上自身的算法复杂,往往令人失去战意,根本不想花时间去研究。
这个 crackme 的算法我是很简单地完成了,没有使用甚么技巧,只用最直接的序号对照。我觉得读者完成这个 crackme 的价值,
是在于怎样应付反调试的应付技巧和更熟练地应用 OD,如果破解者有能力来到检查序号的 dll 里,我想在算法那里应该也应付
自如。
希望这篇文章对大家有用,如果你是老手的话,可能觉得我很多地方都不够直接,或者是有些地方可以用更灵巧的方法跳过,如果你
愿意分享的话,希望你可以贴到看雪去,让大家学习一下。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课