首页
社区
课程
招聘
[原创]riijj Crackme (3)的详解
2004-12-8 17:13 41049

[原创]riijj Crackme (3)的详解

2004-12-8 17:13
41049
【破文标题】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 里,我想在算法那里应该也应付
   自如。

   希望这篇文章对大家有用,如果你是老手的话,可能觉得我很多地方都不够直接,或者是有些地方可以用更灵巧的方法跳过,如果你
   愿意分享的话,希望你可以贴到看雪去,让大家学习一下。

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞7
打赏
分享
最新回复 (54)
雪    币: 890
活跃值: (4039)
能力值: ( LV9,RANK:3410 )
在线值:
发帖
回帖
粉丝
fly 85 2004-12-8 17:17
2
0
学习
雪    币: 83295
活跃值: (198380)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
linhanshi 2004-12-8 17:46
3
0
支持!!!
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
riijj 7 2004-12-8 19:47
4
0
希望大侠们多多指点一下

有些地方可能处理得不够爽快,我的功力很有限 :D
雪    币: 662
活跃值: (1637)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
daxia200N 6 2004-12-8 20:05
5
0
分析的透彻,学习。
雪    币: 213
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
d1y2j3 2004-12-8 20:22
6
0
可以看看UnhandledExceptionFilter插件的做法
雪    币: 212
活跃值: (14)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cgdxxx 2004-12-8 21:15
7
0
我也尝试过,可是我失败了
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lwqqq 2004-12-8 23:50
8
0
先copy下来再说,以楼主123为突破口,希望一年内有小成,哈哈.
雪    币: 214
活跃值: (15)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
ljy3282393 1 2004-12-9 01:58
9
0
学习,学习,再学习!研究,研究,再研究!
雪    币: 222
活跃值: (145)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
七夜 1 2004-12-10 20:06
10
0
收藏,学习
雪    币: 209
活跃值: (55)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tane 2004-12-10 22:53
11
0
兄弟们试试我的方法,可以省去你很多麻烦一定行.我的目的,在于了解它的过程,最终找到结果,还要可行,方便看看吧.
http://bbs.pediy.com/showthread.php?s=&threadid=7300&perpage=15&pagenumber=2
雪    币: 207
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
k99992002 2004-12-12 15:22
12
0
在你的第八步里:
把程序重新加载,设断点在 SetUnhandledExceptionFilter

bp SetUnhandledExceptionFilter
运行程序,一步一步按下 Shift + F9
这里具体怎么断啊!??
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
riijj 7 2004-12-12 16:12
13
0
最初由 k99992002 发布
在你的第八步里:
把程序重新加载,设断点在 SetUnhandledExceptionFilter

bp SetUnhandledExceptionFilter
运行程序,一步一步按下 Shift + F9
........


设定断点bp SetUnhandledExceptionFilter 后,每按一下 Shift+F9,便看看 OD 左下方是否遇到断点,还是一般的异常

如果 OD 的左下方说程序不能处理异常的话,这代表程序已经踏到了 SetUnhandledExceptionFilter 的陷阱,太迟了
雪    币: 207
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
k99992002 2004-12-13 12:03
14
0
设定断点bp SetUnhandledExceptionFilter
设在什么地方呢?
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
riijj 7 2004-12-13 13:26
15
0
最初由 k99992002 发布
设定断点bp SetUnhandledExceptionFilter
设在什么地方呢?


找 OD 的使用说明看看,有介绍断点的使用

bp SetUnhandledExceptionFilter 是设定断点的指令行
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wenlonggao 2004-12-15 15:30
16
0
老大,我在按照你的做法,在这一步"果然,在大约第 7 次异常的时候,我们断在 77E6BC57 ,这是 SetUnhandledExceptionFilter 的入口",可是我这里却跳到了
“77E8BBF3  |. 5F             POP EDI                                  ;  riijj_cr.00405158
77E8BBF4  |. 5E             POP ESI
77E8BBF5  |. C9             LEAVE
77E8BBF6  \. C2 1000        RETN 10
77E8BBF9 >/$ 8B4C24 04      MOV ECX,DWORD PTR SS:[ESP+4]
77E8BBFD  |. A1 F0A1EB77    MOV EAX,DWORD PTR DS:[77EBA1F0]
77E8BC02  |. 890D F0A1EB77  MOV DWORD PTR DS:[77EBA1F0],ECX
77E8BC08  \. C2 0400        RETN 4
77E8BC0B   $ 55             PUSH EBP
77E8BC0C   . 8BEC           MOV EBP,ESP
77E8BC0E   . 6A FF          PUSH -1
77E8BC10   . 68 802EE677    PUSH KERNEL32.77E62E80
77E8BC15   . 68 441FEB77    PUSH KERNEL32.77EB1F44                   ;  SE handler installation
77E8BC1A   . 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
77E8BC20   . 50             PUSH EAX
77E8BC21   . 64:8925 000000>MOV DWORD PTR FS:[0],ESP”
能告诉我怎么回事吗
雪    币: 16
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
great1234 2004-12-15 19:23
17
0
hen bucuo!!!

很不错,受益非浅!!!!
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
riijj 7 2004-12-15 21:51
18
0
最初由 wenlonggao 发布
老大,我在按照你的做法,在这一步"果然,在大约第 7 次异常的时候,我们断在 77E6BC57 ,这是 SetUnhandledExceptionFilter 的入口",可是我这里却跳到了
“77E8BBF3 |. 5F POP EDI ; riijj_cr.00405158
77E8BBF4 |. 5E POP ESI
77E8BBF5 |. C9 LEAVE
77E8BBF6 \. C2 1000 RETN 10
........


试试在这时候按 Alt+F9 返回,看看是不是身处 crackme 领空,

如果身处 crackme 领空,找找 SetUnhandledExceptionFilter ,依破文的步骤把 SetUnhandledExceptionFilter 陷阱解除

我写文章的时候是写自己看到的内存位置,因为每个人的系统不同,跟我看见的位置会有分别
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
panther666 2004-12-18 16:46
19
0
谢谢啊。
又学到新的知识了。
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
vikingcn 2005-1-13 14:51
20
0
谢谢老大啊,我试着操作了一遍,还比较顺利。结果:name:viking password:nxkuijkunljh  注册成功!不过还是比较难啊,看来破解之路还很长啊。
雪    币: 241
活跃值: (160)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiluoyou 2005-1-14 00:32
21
0
好东西,收藏了慢慢看
雪    币: 2657
活跃值: (464)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
greentea 2005-1-27 10:36
22
0
riijj版主:
http://bbs.pediy.com/upload/file/2004/11/riijj_cm_20041121.zip_081.zip中的riijj Crackme (3)不能下载能否麻烦你发到我的E-mai
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
riijj 7 2005-1-27 11:12
23
0
http://www.systemp.net/riijj_cm_20041217.zip_783.zip

试试这里
雪    币: 2657
活跃值: (464)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
greentea 2005-1-27 13:10
24
0
http://www.systemp.net/riijj_cm_20041217.zip_783.zip

试试这里
这个是Crackme (4)
我所需要的是Crackme (3)
雪    币: 2319
活跃值: (565)
能力值: (RANK:300 )
在线值:
发帖
回帖
粉丝
riijj 7 2005-1-27 13:26
25
0
这个才是

http://www.systemp.net/riijj_cm_20041121.zip_081.zip
游客
登录 | 注册 方可回帖
返回