飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 3728|回复: 5

[转贴] Crackme (1) 的详解

  [复制链接]

该用户从未签到

发表于 2010-5-23 17:29:58 | 显示全部楼层 |阅读模式
本帖最后由 meishenm 于 2010-5-23 19:27 编辑

标 题: 【转载】riijj Crackme (1) 的详解
作 者: riijj
时 间: 2004-11-26,11:57:17
链 接: http://bbs.pediy.com/showthread.php?t=7505

【破文标题】riijj Crackme (1) 的详解
【对  象】初入门的新手
【下载地址】http://bbs.pediy.com/upload/files/1084801702.zip
破解工具】OD
【保护方式】序号
【任  务】找出序号
【破文作者】riijj
【组  织】没有
【破解声明】我的破解很菜,写这篇东西是给刚入门的兄弟可以顺利完成这个 crackme,找到一点信心
【备  注】老手勿看,别浪费你的时间
【电  邮】je6543@yahoo.com
【破解过程】


[PART. 1 第一部分]


对于刚接触破解的新手来说,这个 crackme 是一只没有杀伤力的小兔。

一般破文都是注重处理 antidebug 和序号检查这些精要的部份,可是这个 crackme 实在是太简单,所以
会详尽一点,把新手不懂的地方也说一下


1.  检查壳 (check for protector)

在开始之前,我们都会用 peid 或 Fi 这些工具检查一下壳,如果程序是有加壳的,当然是先脱壳
(关于脱壳,这里不多说了)

打开 peid ,把 exe 拉进去

peid 显示 Microsoft Visual C++ 6.0

这表示没有加壳,这是以 vc6 编写的原程序。  (注: 有些程序加壳后,会故意修改,装成没有加壳的样子,所以我们不可以
尽信查壳工具)


2. 用 OD 加载

我们可以用的调试工具很多,静态调试的有 IDA , wdasm893 (现在比较少人用),动态调试我们可以用 OD (olly debug)
,或是用 softice 对系统设下断点来破。 我们这里介绍 OD ,如果你喜欢其它工具也是可以的,原理是差不多。

加载后,看到

00401368 >/$ 55             PUSH EBP        <------- 我们在这里
00401369  |. 8BEC           MOV EBP,ESP
0040136B  |. 6A FF          PUSH -1
0040136D  |. 68 E8404000    PUSH ncrackme.004040E8
00401372  |. 68 9C1E4000    PUSH ncrackme.00401E9C                   ;  SE handler installation
00401377  |. 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
0040137D  |. 50             PUSH EAX
0040137E  |. 64:8925 000000>MOV DWORD PTR FS:[0],ESP
00401385  |. 83EC 58        SUB ESP,58
00401388  |. 53             PUSH EBX



这样的一大堆,便是程序开始的地方。一般程序的入口点都是 40xxxx 这样的,如果程序加了壳,入口点会不同。
程序的入口点,是可以由作者自行设定的,没有特别规定

在程序开始段常见的 API :  KERNEL32.GetVersion, GetCommandLineA, GetStartupInfoA

我们在这片密麻麻的字海里,是很难找到我们要破解的地方,我们第一步是要找出检查序号的部份。程序多数会用 GetWindowTextA,
GetDlgItemTextA 这类 API 来得到文字方块里的字符串。此外,当我们输入错误序号的时候,程序会弹出一个失败信息,这个信息由
MessageBoxA 所提供。假若我们要分析算法,便需要在 GetWindowTextA 下断,一步一步跟踪。现在我们想从内存中找出序号,比较
快速的方法是在 MessageBoxA 下断。


在 OD 上方的 Plugins ,选 Commandline ,这里可以像 softice 一样输入命令行

设下一个断点 :  

bp MessageBoxA


输入这行后,当程序使用 MessageBoxA ,程序便会断下

现在,我们把程序正常运行 (按 F9),输入一个名字,我在里输入了 riijj ,在序号那行输入了  AAAABBBBCCCC

按下 "Register" ,这时候程序遇到断点,停在以下地方

77E16544 > 55               PUSH EBP   <---停在这里
77E16545   8BEC             MOV EBP,ESP
77E16547   51               PUSH ECX
77E16548   833D 1893E477 00 CMP DWORD PTR DS:[77E49318],0
77E1654F   0F85 EA220100    JNZ USER32.77E2883F
77E16555   6A 00            PUSH 0
77E16557   FF75 14          PUSH DWORD PTR SS:[EBP+14]
77E1655A   FF75 10          PUSH DWORD PTR SS:[EBP+10]
77E1655D   FF75 0C          PUSH DWORD PTR SS:[EBP+C]
77E16560   FF75 08          PUSH DWORD PTR SS:[EBP+8]
77E16563   E8 04000000      CALL USER32.MessageBoxExA
77E16568   C9               LEAVE
77E16569   C2 1000          RETN 10
77E1656C > 55               PUSH EBP




看看 OD 的上方,写着 "CPU - main thread, module USER32" ,这说明了我们身处 user32.dll 的领空里,这是系统的程序
部份,我们是不会修改这里的。

(注 : 如果你把文字窗口向上卷的时候,发现 OD 的编码出现不正常现象 (例如刚刚那行 77E16544 的上方变成了 ??? ) ,
这可能是 OD 的对位出错)

我们要返回 crackme 的领空里,有几个方法

1. 不停接 F8 ,一步一步地执行直至程序遇上 retn ,这是返回指令,它会带我们回去
2. 按 Ctrl + F9 ,这样 OD 会不停执行,直至遇到 retn 停下
3. 按一下返回的 retn ,再按 F4 ,程序会执行到光标所在的地方
4. 打开 OD 的 call stack window,看看我们从那里飞来,便设一个断点在那地方,之后 F9 运行

我们按一下 F2 清除断点,再按一下 77E16569 (retn) 那行,按 F4 执行到那里,再按一下 F7 进入 retn。



00401050   . 817C24 08 1101>CMP DWORD PTR SS:[ESP+8],111
00401058   . 75 74          JNZ SHORT ncrackme.004010CE
0040105A   . 8B4424 0C      MOV EAX,DWORD PTR SS:[ESP+C]
0040105E   . 66:3D EA03     CMP AX,3EA
00401062   . 75 42          JNZ SHORT ncrackme.004010A6
00401064   . E8 C7010000    CALL ncrackme.00401230
00401069   . 85C0           TEST EAX,EAX
0040106B   . 6A 00          PUSH 0                                   ; /Style = MB_OK|MB_APPLMODAL
0040106D   . 68 80504000    PUSH ncrackme.00405080                   ; |Title = "ncrackme"
00401072   . 75 1B          JNZ SHORT ncrackme.0040108F              ; |
00401074   . A1 B8564000    MOV EAX,DWORD PTR DS:[4056B8]            ; |
00401079   . 68 64504000    PUSH ncrackme.00405064                   ; |Text = "Registration successful."
0040107E   . 50             PUSH EAX                                 ; |hOwner => 001C0218 ('Newbie smallsize crackme -

v1',class='myWindowClass')
0040107F   . FF15 C0404000  CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
00401085   . E8 A6020000    CALL ncrackme.00401330
0040108A   . 33C0           XOR EAX,EAX
0040108C   . C2 1000        RETN 10
0040108F   > 8B0D B8564000  MOV ECX,DWORD PTR DS:[4056B8]            ; |
00401095   . 68 50504000    PUSH ncrackme.00405050                   ; |Text = "Registration fail."
0040109A   . 51             PUSH ECX                                 ; |hOwner => 001C0218 ('Newbie smallsize crackme -

v1',class='myWindowClass')
0040109B   . FF15 C0404000  CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
004010A1   . 33C0           XOR EAX,EAX                        <--------------- 我们停在这里
004010A3   . C2 1000        RETN 10
004010A6   > 66:3D EB03     CMP AX,3EB
004010AA   . 75 22          JNZ SHORT ncrackme.004010CE
004010AC   . A1 C0564000    MOV EAX,DWORD PTR DS:[4056C0]
004010B1   . 85C0           TEST EAX,EAX
004010B3   . 74 19          JE SHORT ncrackme.004010CE




我们看见了这里是 MessageBox 跳出的程序,这里是成功信息和失败信息的地方。 (读者可能已经大叫,只要把跳往失败信息的跳转修改
一下,便可以成功爆破) 。这是对的,假如我们要爆破它,只要把 00401072 的 JNZ (jump if not zero),修改成 JZ 便可以。


那么,假若我们不想修改软件,而是找出正确的序号,那又怎办呢 ? 我们需要往那个 jump 的上面看看。在 00401069 这里有一个 TEST
,这就是进行比较的指令,它的结果用在其后的 jnz 上。

这句 TEST EAX,EAX 是检查 EAX 是否等于 0 的典型语句,假如 EAX 不等于 0,JNZ 便会跳。我们就是不想它跳,所以我们要找出让 EAX
等于 0 的条件。在 TEST 的上方是 CALL ncrackme.00401230,这个 CALL 的返回值就是 EAX 的值。

我们按右键选 Go to,在 expression 输入 00401230 这个位置

我们来到了检查序号的地方,一看之下便感到这里是一堆很麻烦的运算。今次我们的目的是检出一个给
自己用的序号,我们有没有方法可以从算法中看出序号呢 ?



[PART. 2 第二部分]


按 F7 单步跟踪,细心观察它在干甚么


00401230  /$ 8B0D BC564000  MOV ECX,DWORD PTR DS:[4056BC]
00401236  |. 83EC 30        SUB ESP,30                        // 在 stack 划出空间,作为变量
00401239  |. 8D4424 00      LEA EAX,DWORD PTR SS:[ESP]
0040123D  |. 53             PUSH EBX
0040123E  |. 56             PUSH ESI
0040123F  |. 8B35 94404000  MOV ESI,DWORD PTR DS:[<&USER32.GetDlgIte>;  USER32.GetDlgItemTextA
00401245  |. 6A 10          PUSH 10                                  ; /Count = 10 (16.)
00401247  |. 50             PUSH EAX                                 ; |Buffer                   //  <---- 注意这里,
00401248  |. 68 E8030000    PUSH 3E8                                 ; |ControlID = 3E8 (1000.)  //  可以看出存放字符串的地方
0040124D  |. 51             PUSH ECX                                 ; |hWnd => NULL            
0040124E  |. 33DB           XOR EBX,EBX                              ; |
00401250  |. FFD6           CALL ESI                                 ; \GetDlgItemTextA
00401252  |. 83F8 03        CMP EAX,3                         // 找到注册名字,如果字名的长度不小于 3,便跳,否则便完结
00401255  |. 73 0B          JNB SHORT ncrackme.00401262
00401257  |. 5E             POP ESI
00401258  |. B8 01000000    MOV EAX,1
0040125D  |. 5B             POP EBX
0040125E  |. 83C4 30        ADD ESP,30
00401261  |. C3             RETN                              // 如果来了这里,便完结了
00401262  |> A1 BC564000    MOV EAX,DWORD PTR DS:[4056BC]     // 从 JNB 来了这里
00401267  |. 8D5424 28      LEA EDX,DWORD PTR SS:[ESP+28]
0040126B  |. 6A 10          PUSH 10
0040126D  |. 52             PUSH EDX                          // <----- 注意这里,可以看出存放序号字符串的地方
0040126E  |. 68 E9030000    PUSH 3E9
00401273  |. 50             PUSH EAX
00401274  |. FFD6           CALL ESI                          // 再用 GetDlgItemTextA ,得到序号
00401276  |. 0FBE4424 08    MOVSX EAX,BYTE PTR SS:[ESP+8]     // 把名字的第一个位,放入 EAX
0040127B  |. 0FBE4C24 09    MOVSX ECX,BYTE PTR SS:[ESP+9]     // 把名字的第二个位,放入 ECX
00401280  |. 99             CDQ                               // 把 EAX 扩展,成为 EDX:EAX 的 QWORD(64 位长)
00401281  |. F7F9           IDIV ECX                          // 把 EDX:EAX 除以 ECX,余数放在 EDX
00401283  |. 8BCA           MOV ECX,EDX                       
00401285  |. 83C8 FF        OR EAX,FFFFFFFF                   // EAX = 0xffffffff
00401288  |. 0FBE5424 0A    MOVSX EDX,BYTE PTR SS:[ESP+A]     // 把名字的第一个位,放入 EDX
0040128D  |. 0FAFCA         IMUL ECX,EDX                      // 把刚才的余数乘以 EDX
00401290  |. 41             INC ECX                           // ECX 增加 1
00401291  |. 33D2           XOR EDX,EDX                       // EDX = 0
00401293  |. F7F1           DIV ECX                           // 以 0xffffffff 除以 ECX

以 C 语言表达,想象成

EAX 的值:  0xFFFFFFFF / (1+(buffer[0] % buffer[1] * buffer[2])

数值相等于 4546e6


00401295  |. 50             PUSH EAX                          // push 了 4546e6
00401296  |. E8 A5000000    CALL ncrackme.00401340            // 进去看看


00401340  /$ 8B4424 04      MOV EAX,DWORD PTR SS:[ESP+4]      // 把刚才传进来的值 (4546e6),放到 EAX
00401344  |. A3 AC504000    MOV DWORD PTR DS:[4050AC],EAX     // 又由 EAX 放到 DS:[4050AC]
00401349  \. C3             RETN

这两行把上面的运算结果放到 DS:[4050AC] 去,不明白原因,继续跟下去

回到外面

0040129B  |. 83C4 04        ADD ESP,4                         // 清理 stack
0040129E  |. 33F6           XOR ESI,ESI                       // 设 ESI 为 0
004012A0  |> E8 A5000000    /CALL ncrackme.0040134A           // 走进去看看


0040134A  /$ A1 AC504000    MOV EAX,DWORD PTR DS:[4050AC]     // 把那个值 (4546e6) 放入 EAX
0040134F  |. 69C0 FD430300  IMUL EAX,EAX,343FD                // EAX 乘以 343FD
00401355  |. 05 C39E2600    ADD EAX,269EC3                    // 再加 269EC3
0040135A  |. A3 AC504000    MOV DWORD PTR DS:[4050AC],EAX     // 把结果放回 DS:[4050AC]
0040135F  |. C1F8 10        SAR EAX,10                        // 向右方 bit shift 10 位
00401362  |. 25 FF7F0000    AND EAX,7FFF                      // 再 AND 了 7fff
00401367  \. C3             RETN                              // 得到一个古怪的值,返回了

没有甚么特别,只是一大轮运算,得到一个新的值 3add

返回外面


004012A5  |. 99             |CDQ                              // 把那个值扩展成 EDX:EAX
004012A6  |. B9 1A000000    |MOV ECX,1A                       // ECX = 1a
004012AB  |. F7F9           |IDIV ECX                         // 把 EDX:EAX 除以 1a
004012AD  |. 80C2 41        |ADD DL,41                        // 把余数加 41,说起来,41 是 ASCII 的大写 A
004012B0  |. 885434 18      |MOV BYTE PTR SS:[ESP+ESI+18],DL  // 把这个余数的下 8 位,放入 SS:[ESP+ESI+18] 的地方
004012B4  |. 46             |INC ESI                          // ESI 增加 1 了
004012B5  |. 83FE 0F        |CMP ESI,0F                       // 检查 ESI 是否已经等于 0f
004012B8  |.^72 E6          \JB SHORT ncrackme.004012A0       // 跳回上面,这像是一个小型 loop

从这里的结构,我们估计 ESI 由 0 增加至 0f ,不停把一组数字放入 stack 里面,这是一个回圈十六次的 loop
要注意这些数值都加上了 41 (ASCII A),它们都是由 41 开始

我们在 OD 下方的数值窗口,把数值打开来看

0012FC34  50 49 5A 49 56 4A 45 44  PIZIVJED
0012FC3C  42 47 5A 41 50 53 49 00  BGZAPSI.

这像是一堆 ASCII 字

我们继续看


004012BA  |. 57             PUSH EDI                          // 没有特别,只是保存 EDI,我们看见程序末段会有一个 pop edi
004012BB  |. 8D7C24 0C      LEA EDI,DWORD PTR SS:[ESP+C]      // 从 OD 发现,SS:[ESP+C] 是注册名字位置,现在位置放入EDI
004012BF  |. 83C9 FF        OR ECX,FFFFFFFF                   // ECX = 0xffffffff
004012C2  |. 33C0           XOR EAX,EAX                       // EAX = 0
004012C4  |. 33F6           XOR ESI,ESI                       // ESI = 0
004012C6  |. F2:AE          REPNE SCAS BYTE PTR ES:[EDI]      

这里有一个 REPNE SCAS 的设置,它扫瞄注册名字,最多 ECX 次 (这里 ECX 是 0xffffffff 次),
或是遇上 AL 便停止 (这里 AL 是 0)

看来它在注册名字中寻找出 0 (字符串结尾位)

004012C8  |. F7D1           NOT ECX                           // 把 ECX 反转,变成了扫瞄的次数
004012CA  |. 49             DEC ECX                           // ECX 减 1

现在 ECX 得到了注册名字的长度,是 6

004012CB  |. 74 59          JE SHORT ncrackme.00401326        // 如果 ECX 是 0,跳到完结处
004012CD  |> 8A4434 0C      /MOV AL,BYTE PTR SS:[ESP+ESI+C]   // 把注册名字第 esi 个位,放入 AL (刚开始是第 0 个)
004012D1  |. C0F8 05        |SAR AL,5                         // 向右方 bit shift 5 位
004012D4  |. 0FBEC0         |MOVSX EAX,AL                     // 把 AL 扩展到 EAX
004012D7  |. 8D1480         |LEA EDX,DWORD PTR DS:[EAX+EAX*4] // EDX = EAX + (EAX * 4)
004012DA  |. 8D04D0         |LEA EAX,DWORD PTR DS:[EAX+EDX*8] // EAX = EAX + (EDX * 8)
004012DD  |. 8D0440         |LEA EAX,DWORD PTR DS:[EAX+EAX*2] // EAX = EAX + (EAX * 2)
004012E0  |. 85C0           |TEST EAX,EAX                     // 检查结果是否 0
004012E2  |. 7E 0A          |JLE SHORT ncrackme.004012EE      // 如果 0,便跳
004012E4  |. 8BF8           |MOV EDI,EAX                      // EAX 放入 EDI
004012E6  |> E8 5F000000    |/CALL ncrackme.0040134A          // 很面熟,这是刚才的 call,把 EAX 进行一堆运算
004012EB  |. 4F             ||DEC EDI                         // EDI 减 1
004012EC  |.^75 F8          |\JNZ SHORT ncrackme.004012E6     // 当 EDI 不等于 0 的时候,便跳上去,这是 loop

这个小型 loop 会循环 EDI 次,不停呼叫 ncrackme.0040134A,这个 call 我们刚才看过,
它会把 DS:[4050AC] 这个值改变。这样看来,随着我注册名字的位不同, EDI 的值
不同,它循环的次数便会不同

char X, Y

X =  (buffer[esi] >> 5)
Y = X + (X * 4)
X = X + (Y * 8)
X = X + (X * 2)   

呼叫 ncrackme.0040134A X 次

继续看下去

004012EE  |> E8 57000000    |CALL ncrackme.0040134A           // 再呼叫一次,现在 EAX 是返回值
004012F3  |. 99             |CDQ                              // 把 EAX 扩展到 EDX:EAX
004012F4  |. B9 1A000000    |MOV ECX,1A                       // ECX = 1a ( decimal 26)
004012F9  |. 8D7C24 0C      |LEA EDI,DWORD PTR SS:[ESP+C]     // 把注册名字的位置放在 EDI
004012FD  |. F7F9           |IDIV ECX                         // EDX:EAX 除以 ECX (1a)
004012FF  |. 0FBE4C34 2C    |MOVSX ECX,BYTE PTR SS:[ESP+ESI+2C]   

从 OD 查看,SS:[ESP+ESI+2C] 是序号字符串,这里把序号的第 esi 个字符放进 ECX


00401304  |. 80C2 41        |ADD DL,41                        // 把 DL (除数的余值,低位值) 加 41
00401307  |. 0FBEC2         |MOVSX EAX,DL                     // 把 DL 放入 EAX
0040130A  |. 2BC1           |SUB EAX,ECX                      // EAX 减 ECX ,得到一个新的值,放在 EAX
0040130C  |. 885434 1C      |MOV BYTE PTR SS:[ESP+ESI+1C],DL  

    从 OD 查看,SS:[ESP+ESI+1C] 是那一组特别字符的位置,

    0012FC34  50 49 5A 49 56 4A 45 44  PIZIVJED
    0012FC3C  42 47 5A 41 50 53 49 00  BGZAPSI.

    这行把 DL 放入这里,取代原有的值

00401310  |. 99             |CDQ                              // 把 EAX 扩展成 EDX:EAX
00401311  |. 33C2           |XOR EAX,EDX                      // 把 EAX 跟 EDX 进行 XOR 合并
00401313  |. 83C9 FF        |OR ECX,FFFFFFFF                  // ECX = 0xffffffff
00401316  |. 2BC2           |SUB EAX,EDX                      // 把 EAX 减 EDX
00401318  |. 03D8           |ADD EBX,EAX                     

    把 EBX 加 EAX,这真是奇怪,那里来的 EBX ?  我们向上追查,发现 EBX 一直没有使用, EBX 在 0040124E
    这一行被清空,它的初始值是 0。


0040131A  |. 33C0           |XOR EAX,EAX                      // EAX = 0
0040131C  |. 46             |INC ESI                          // ESI 增加 1
0040131D  |. F2:AE          |REPNE SCAS BYTE PTR ES:[EDI]     

    看看 004012F9 这一行,发现 EDI 是注册名字的位置。
    现在 SCAS 扫瞄注册名字,找出 AL (0)


0040131F  |. F7D1           |NOT ECX                          // 还记得上面的结构吗 ? 把 ECX 反转,便是注册名字的长度 + 1
00401321  |. 49             |DEC ECX                          // 把 ECX 减 1,现在 ECX 是注册名字长度
00401322  |. 3BF1           |CMP ESI,ECX                      // 比较 ESI 和注册名字长度
00401324  |.^72 A7          \JB SHORT ncrackme.004012CD       // 当 ESI 小于注册名字长度,便往上跳,这是 loop

    以上的结构,是一个大型的 loop,以不停增加 ESI 的值,把注册名字和序号进行运算,
    其运算结果便累加到 EBX 上


00401326  |> 5F             POP EDI                           // 这是完结部分,复原 EDI
00401327  |. 8BC3           MOV EAX,EBX                       // 把 EBX 的最后值,放入 EAX ,EAX 将会是这个 CALL 的返回值
00401329  |. 5E             POP ESI                           // 复原 ESI
0040132A  |. 5B             POP EBX                           // 复原 EBX
0040132B  |. 83C4 30        ADD ESP,30                        // 清理 stack 空间
0040132E  \. C3             RETN                              // 离开


    还记得我们在 PART. 1 的结尾部份吗 ? 我们遇上了跳出成功注册信息的检查程序,如果这个 CALL 的返回值是 0 的话,
    我们便会成功注册。我们的任务是分析上面的算法,找出一个序号,可以使返回值 (前身是 EBX) 最后的结果是 0。

    用 OD 跟踪后,我们可以观察到以下特点:

    1.  EBX 的初始值是 0,它在每一次循环都会被增加,只会增加,不会减少

    2.  如果要 EBX 的值保持是 0,那么它上面  SUB EAX,EDX 这一行的结果,必须是 0,也就是说, EAX 和 EDX 必须相等

    3.  再往上看,发现 EAX 和 EDX 是由 00401310 这一行, CDQ 指令产生的。 CDQ 把原来的 EAX 扩展成 EDX:EAX (带正负值),
        这个指令把 EAX 的第 31 bit 复制到 EDX 的每一个 bit 上。例如 :

        假设 EAX 是 FFFFFFFB (-5) ,它的第 31 bit (最左边) 是 1,
        执行 CDQ 后, CDQ 把第 31 bit 复制至 EDX 所有 bit

        EDX 变成 FFFFFFFF

        这时候, EDX:EAX 变成  FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5


        我们发现, 00401310 和 00401311 这两行,执行了 CDQ 和 XOR EAX,EDX ,这说明了它的目的,当 EAX 的值是一个
        负数,执行 CDQ 后 EDX 变成 FFFFFFFF,它再跟 EAX 进行 XOR,便会使 EAX 反转,变成一个正数。假如 EAX 本来
        是一个正数,执行 CDQ 后 EDX 是 0,它再跟 EAX 进行 XOR ,没有任何效果,保留 EAX 的值

        由始可见,

00401310  |. 99             |CDQ         
00401311  |. 33C2           |XOR EAX,EDX

        这两行其实是从 EAX 得出 "绝对值"。如果 EAX 是 -5,便会变成 5,如果 EAX 是 5,便依然是 5


    4.  由上面的分析可见,要使 SUB EAX,EDX 这一行的结果是 0,只有一个可能性,这就是 EAX 本来是 0 。因为当 EAX 是 0
        的时候, CDQ EAX 才会使 EDX 和 EAX 成为 0,这种情况下  SUB EAX,EDX 便会是 0

    5.  再往上追查,我们发现了这行

0040130A  |. 2BC1           |SUB EAX,ECX

        这行把 EAX 减去 ECX,这里的 ECX 是甚么 ?  再往上追查 004012FF,发现它正是我们的序号字符串
        

004012FF  |. 0FBE4C34 2C    |MOVSX ECX,BYTE PTR SS:[ESP+ESI+2C]

        由此可见,这个 EAX 的值会减去相对位置的序号字符。

        只要我们得到每一个 EAX 的值,把它们串行成序号,便可以用它顺利成功注册,我们找到了光明之路了 !

        

   我们把 crackme 再运行一次,在 0040130A 设下断点,记录每一个 EAX 的值

   50  4d  51  4a  44

   把这些值转换成 ASCII,那就是

   P M Q J D

   现在,我们再运行 crackme ,以 riijj 作为名字, PMQJD 作为序号,成功注册了  !!


[完结]

   我的破解功力不好,所以很少写这种破文。大侠们看了,一定是觉得很初级的,没有价值。我希望这篇文可以让刚入门的
   兄弟,找到了一点成功的信心和感觉,要破解一个市面上的软件,必定是比这个 crackme 艰难很多,可是只要有继续下去
   的精神,始终会完成的。

评分

参与人数 1飘云币 +40 收起 理由
月之精灵 + 40 您的贴子很精彩,希望能再次分享!

查看全部评分

PYG19周年生日快乐!
  • TA的每日心情
    奋斗
    昨天 07:43
  • 签到天数: 1697 天

    [LV.Master]伴坛终老

    发表于 2010-5-23 18:45:19 | 显示全部楼层
    学习学习,您的贴子很精彩
    PYG19周年生日快乐!
  • TA的每日心情
    慵懒
    2019-3-12 17:25
  • 签到天数: 3 天

    [LV.2]偶尔看看I

    发表于 2010-5-23 19:24:15 | 显示全部楼层
    写的不错,楼主继续加油呀!
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2017-10-25 13:07
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2010-5-23 20:46:41 | 显示全部楼层
    /:good顶上了 好文章
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2019-9-9 09:51
  • 签到天数: 82 天

    [LV.6]常住居民II

    发表于 2010-5-26 23:07:44 | 显示全部楼层
    楼主 再次带来了好的破文 支持
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2016-5-11 20:23
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2010-5-28 20:41:13 | 显示全部楼层
    回复 1# meishenm


        写的不错,楼主继续加油呀!
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表