• 标 题:一篇破解教程-----面向初学者 (15千字)
  • 作 者:Fpc
  • 时 间:2001-4-1 11:44:22
  • 链 接:http://bbs.pediy.com

一篇破解教程-----面向初学者(转载请保持完整,且不得用于商业用途)
作者: Fpc
工具:Trw2000,W32dasm 8.93

软件名称:五子棋99 Ver 2.0
作    者: 华东计算技术研究所  蒋祥刚
文件大小:497KB
授权方式:共享软件
使用平台:Win95/98

软件简介:一个五子棋游戏软件,与常见的同类软件差不多,棋力不强

软件来源:《网页设计素材2000》(3CD)中的游戏目录
下载地址:暂时不可用,大家耐心等



    见到需要输入注册名和注册码的软件就忍不住去按 ^D,可能是破解综合症吧 ~_~。在同事的机器上发现了一个五子棋的游戏,玩了几局,居然要注册。简直是*#*@&&,也不看看棋力有多差,都不忍心去破。想一想,权当是一个CrackMe,开始吧。
    多说两句,大多数五子棋软件的棋力都不行,都是乱走棋,只要你看过一两本棋谱,对付它们不成问题。记得在学校时接触过一个“五子棋大师 二”,体积很大,但棋力非常强。其实优秀的棋类软件应该是算法和棋谱管理的结合体。象棋的“将族3”就很厉害,我连“小猪头”都对付不了,自己也是太笨:-)

  用TRW2000(感谢LTT和ZNH,我决定注册,不知注册费是多少¥)载入FIVE,在“关于”中要求输入注册码。这是一个典型的“注册名”加“注册码”注册形式。在姓名中输入“Fpc”,注册码中输入“494949”(在下面的分析中你会看到,正好满足了6位注册码的条件)。
  ^N到TRW,下bpx hmemcpy(万能中断);^N返回,单击“注册”,立刻到TRW中,按一次^N(因为有两个输入的文本框),还会回到TRW中;
  下BD *,按几次F12,你会返回到下面,见分析:

:0045E884 8B80E0020000            mov eax, dword ptr [eax+000002E0]
:0045E88A E81DC6FCFF              call 0042AEAC                <==== 这次调用取得 注册名
:0045E88F 8B45FC                  mov eax, dword ptr [ebp-04]
:0045E892 50                      push eax

* Possible StringData Ref from Code Obj ->"Name"
                                  |
:0045E893 B92CE94500              mov ecx, 0045E92C

* Possible StringData Ref from Code Obj ->"Register"
                                  |
:0045E898 BA3CE94500              mov edx, 0045E93C
:0045E89D A1649A4600              mov eax, dword ptr [00469A64]
:0045E8A2 8B18                    mov ebx, dword ptr [eax]
:0045E8A4 FF5304                  call [ebx+04]                <==== 写入 ini 文件
:0045E8A7 8D55FC                  lea edx, dword ptr [ebp-04]
:0045E8AA A15C9A4600              mov eax, dword ptr [00469A5C]
:0045E8AF 8B80E8020000            mov eax, dword ptr [eax+000002E8]
:0045E8B5 E8F2C5FCFF              call 0042AEAC                <==== 取得输入的注册码
:0045E8BA 8B45FC                  mov eax, dword ptr [ebp-04]        《=== 你会返回到这里
:0045E8BD 50                      push eax

* Possible StringData Ref from Code Obj ->"RegNo"
                                  |
:0045E8BE B950E94500              mov ecx, 0045E950

* Possible StringData Ref from Code Obj ->"Register"
                                  |
:0045E8C3 BA3CE94500              mov edx, 0045E93C
:0045E8C8 A1649A4600              mov eax, dword ptr [00469A64]
:0045E8CD 8B18                    mov ebx, dword ptr [eax]
:0045E8CF FF5304                  call [ebx+04]                <==== 写入 ini 文件
:0045E8D2 E855FDFFFF              call 0045E62C                <==== Call+Cmp+Jmp 的注册码判断模式非常常见
:0045E8D7 803D609A460000          cmp byte ptr [00469A60], 00        <==== [469A60]是个标志,如果不为0,则注册成功
:0045E8DE 750C                    jne 0045E8EC

* Possible StringData Ref from Code Obj ->"名字和注册号不匹配!"    <==== 可惜在 TRW 和 W32dasm 的正文中都无法看到汉字 :-(
                                  |
:0045E8E0 B860E94500              mov eax, 0045E960
:0045E8E5 E876CAFEFF              call 0044B360                <==== 调用MessageBoxA
:0045E8EA EB14                    jmp 0045E900

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E8DE(C)
|

* Possible StringData Ref from Code Obj ->"恭喜你已经成功注册!"
                                  |
:0045E8EC B88CE94500              mov eax, 0045E98C
:0045E8F1 E86ACAFEFF              call 0044B360                <==== 同样调用MessageBoxA
:0045E8F6 A15C9A4600              mov eax, dword ptr [00469A5C]
:0045E8FB E85C51FEFF              call 00443A5C

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E8EA(U)
|
:0045E900 33C0                    xor eax, eax
:0045E902 5A                      pop edx
:0045E903 59                      pop ecx
:0045E904 59                      pop ecx
:0045E905 648910                  mov dword ptr fs:[eax], edx
:0045E908 681DE94500              push 0045E91D                <==== 这里我想不明白是如何产生的,这种调用(跳转)在W32dasm中不会给出提示
                                    <==== 这个push 和之后的 ret 会越过下面的两个 jmp
* Referenced by a (U)nconditional or (C)onditional Jump at Address:    <==== 跳到45E91D去执行,明白其中道理的请告诉我
|:0045E91B(U)
|
:0045E90D 8D45FC                  lea eax, dword ptr [ebp-04]
:0045E910 E8AB4FFAFF              call 004038C0
:0045E915 C3                      ret


:0045E916 E9E149FAFF              jmp 004032FC
:0045E91B EBF0                    jmp 0045E90D
:0045E91D 5B                      pop ebx                <====
:0045E91E 59                      pop ecx
:0045E91F 5D                      pop ebp
:0045E920 C3                      ret


  很明显,:0045E8D2 处的调用的 call 0045E62C 是判断注册码的关键。下bpx 45E8D2,^N返回到FIVE中,用原来的信息再注册。这次会直接中断到 45E8D2,按F8追进这个call:

* Referenced by a CALL at Addresses:
|:0045E8D2  , :0045E9DA 
|
:0045E62C 55                      push ebp
:0045E62D 8BEC                    mov ebp, esp
:0045E62F 81C4CCFDFFFF            add esp, FFFFFDCC
:0045E635 53                      push ebx
:0045E636 56                      push esi

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E5D0(C)
|
:0045E637 57                      push edi
:0045E638 33C0                    xor eax, eax
:0045E63A 8985CCFDFFFF            mov dword ptr [ebp+FFFFFDCC], eax
:0045E640 33C0                    xor eax, eax
:0045E642 55                      push ebp
:0045E643 68A0E74500              push 0045E7A0
:0045E648 64FF30                  push dword ptr fs:[eax]
:0045E64B 648920                  mov dword ptr fs:[eax], esp
:0045E64E 6A00                    push 00000000
:0045E650 8D85CCFDFFFF            lea eax, dword ptr [ebp+FFFFFDCC]
:0045E656 50                      push eax

* Possible StringData Ref from Code Obj ->"Name"
                                  |
:0045E657 B9B8E74500              mov ecx, 0045E7B8

* Possible StringData Ref from Code Obj ->"register"
                                  |
:0045E65C BAC8E74500              mov edx, 0045E7C8
:0045E661 A1649A4600              mov eax, dword ptr [00469A64]
:0045E666 8B18                    mov ebx, dword ptr [eax]
:0045E668 FF13                    call dword ptr [ebx]            <==== 从ini文件中取得注册名
:0045E66A 8B95CCFDFFFF            mov edx, dword ptr [ebp+FFFFFDCC]    \
:0045E670 8D85D0FDFFFF            lea eax, dword ptr [ebp+FFFFFDD0]    |
:0045E676 B9FF000000              mov ecx, 000000FF            |
:0045E67B E89854FAFF              call 00403B18                |
:0045E680 8D95D0FDFFFF            lea edx, dword ptr [ebp+FFFFFDD0]    |
:0045E686 8D459B                  lea eax, dword ptr [ebp-65]        \ 将注册名信息存放到 ebp-64到ebp处,ebp-65是注册名长度
                                    /
* Referenced by a (U)nconditional or (C)onditional Jump at Address:    |
|:0045E622(C)                                |
|                                    |
:0045E689 B164                    mov cl, 64                |
:0045E68B E8F041FAFF              call 00402880                  /
:0045E690 6A00                    push 00000000
:0045E692 8D85CCFDFFFF            lea eax, dword ptr [ebp+FFFFFDCC]
:0045E698 50                      push eax

* Possible StringData Ref from Code Obj ->"RegNo"
                                  |
:0045E699 B9DCE74500              mov ecx, 0045E7DC

* Possible StringData Ref from Code Obj ->"register"
                                  |
:0045E69E BAC8E74500              mov edx, 0045E7C8
:0045E6A3 A1649A4600              mov eax, dword ptr [00469A64]
:0045E6A8 8B18                    mov ebx, dword ptr [eax]
:0045E6AA FF13                    call dword ptr [ebx]            <==== 从ini文件中取得注册码
:0045E6AC 8B95CCFDFFFF            mov edx, dword ptr [ebp+FFFFFDCC]    \
:0045E6B2 8D85D0FDFFFF            lea eax, dword ptr [ebp+FFFFFDD0]    |
:0045E6B8 B9FF000000              mov ecx, 000000FF            |
:0045E6BD E85654FAFF              call 00403B18                \ 将注册名信息存放到 ebp-c9到ebp-66处,ebp-ca是注册名长度
:0045E6C2 8D95D0FDFFFF            lea edx, dword ptr [ebp+FFFFFDD0]    /
:0045E6C8 8D8536FFFFFF            lea eax, dword ptr [ebp+FFFFFF36]    |
:0045E6CE B164                    mov cl, 64                |
:0045E6D0 E8AB41FAFF              call 00402880                  /
:0045E6D5 8A459B                  mov al, byte ptr [ebp-65]
:0045E6D8 84C0                    test al, al                <==== 检查注册名长度是否为0
:0045E6DA 7409                    je 0045E6E5
:0045E6DC 80BD36FFFFFF06          cmp byte ptr [ebp+FFFFFF36], 06    <==== 检查注册码长度是否为6
:0045E6E3 740C                    je 0045E6F1

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E6DA(C)
|
:0045E6E5 C605609A460000          mov byte ptr [00469A60], 00        <==== 错误则置标志为0
:0045E6EC E996000000              jmp 0045E787                <==== 跳到下面,返回

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E6E3(C)
|
:0045E6F1 8BF0                    mov esi, eax
:0045E6F3 81E6FF000000            and esi, 000000FF
:0045E6F9 85F6                    test esi, esi
:0045E6FB 7C21                    jl 0045E71E
:0045E6FD 46                      inc esi                <==== esi为注册名长度加1
:0045E6FE 8D4D9B                  lea ecx, dword ptr [ebp-65]        <==== ecx指向 注册名长度字节+注册名
:0045E701 8D9DD1FEFFFF            lea ebx, dword ptr [ebp+FFFFFED1]    <==== ebx指向欲生成的注册码地址

* Referenced by a (U)nconditional or (C)onditional Jump at Address:    <==== 下面这个循环生成注册码,设为 注册码A
|:0045E71C(C)
|
:0045E707 33C0                    xor eax, eax
:0045E709 8A01                    mov al, byte ptr [ecx]
:0045E70B BF0A000000              mov edi, 0000000A            <==== 除数为a,即为 10(十进制)
:0045E710 33D2                    xor edx, edx
:0045E712 F7F7                    div edi                <==== 相除后,edx中为余数
:0045E714 80C230                  add dl, 30                <==== 变为 ASCII 码
:0045E717 8813                    mov byte ptr [ebx], dl        <==== 按先后顺序存放于 [ebx]
:0045E719 43                      inc ebx
:0045E71A 41                      inc ecx
:0045E71B 4E                      dec esi
:0045E71C 75E9                    jne 0045E707                <==== 未完则继续

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E6FB(C)
|
:0045E71E 8A459B                  mov al, byte ptr [ebp-65]
:0045E721 3C06                    cmp al, 06                <==== 注册名长度超过6字节则不需要再处理
:0045E723 732F                    jnb 0045E754
:0045E725 33C9                    xor ecx, ecx
:0045E727 8AC8                    mov cl, al
:0045E729 41                      inc ecx
:0045E72A 83F906                  cmp ecx, 00000006            <==== 多此一举!!
:0045E72D 7F25                    jg 0045E754
:0045E72F 8D9C0DD0FEFFFF          lea ebx, dword ptr [ebp+ecx-00000130]    <==== 从 注册码A 的最后一个数开始补足7位

* Referenced by a (U)nconditional or (C)onditional Jump at Address:    <==== 下面这个循环将生成的注册码A 补足为7位
|:0045E752(C)
|
:0045E736 33C0                    xor eax, eax
:0045E738 8A03                    mov al, byte ptr [ebx]        <==== 取得[ebx]处的数字
:0045E73A 40                      inc eax                <==== 加一
:0045E73B 83E830                  sub eax, 00000030
:0045E73E BE0A000000              mov esi, 0000000A
:0045E743 33D2                    xor edx, edx
:0045E745 F7F6                    div esi                <==== 相除,取模到edx
:0045E747 80C230                  add dl, 30                <==== 变为 ASCII 码
:0045E74A 885301                  mov byte ptr [ebx+01], dl        <==== 存放到[ebx+1]处
:0045E74D 41                      inc ecx
:0045E74E 43                      inc ebx
:0045E74F 83F907                  cmp ecx, 00000007            <==== 不足7位则继续
:0045E752 75E2                    jne 0045E736


    可以分析出,注册码支持所有可打印字符和汉字;将 注册码A 补足为7位的规律是:将最后一位数加一作为下一位数,如果9加一则变为0,依次处理。例如:
    注册码A=285,则补足为 2856789; 注册码A=3619,则补足为 3619012。

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0045E723(C), :0045E72D(C)
|
:0045E754 C685D8FEFFFF00          mov byte ptr [ebp+FFFFFED8], 00    <==== 很粗暴的在第八个字节处置0,之后的数字被舍弃
:0045E75B C605609A460001          mov byte ptr [00469A60], 01        <==== 预置注册标志为1(成功)
:0045E762 B906000000              mov ecx, 00000006            <==== 比较的字节数
:0045E767 8D85D2FEFFFF            lea eax, dword ptr [ebp+FFFFFED2]    <==== 算出的注册码,注意第一位被舍弃
:0045E76D 8D9537FFFFFF            lea edx, dword ptr [ebp+FFFFFF37]    <==== 你输入的注册码

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E785(C)
|
:0045E773 8A18                    mov bl, byte ptr [eax]        
:0045E775 3A1A                    cmp bl, byte ptr [edx]
:0045E777 7409                    je 0045E782
:0045E779 C605609A460000          mov byte ptr [00469A60], 00        <==== 比较失败则置标志为0
:0045E780 EB05                    jmp 0045E787

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E777(C)
|
:0045E782 42                      inc edx
:0045E783 40                      inc eax
:0045E784 49                      dec ecx
:0045E785 75EC                    jne 0045E773                <==== 不到6位则继续

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0045E6EC(U), :0045E780(U)
|
:0045E787 33C0                    xor eax, eax
:0045E789 5A                      pop edx
:0045E78A 59                      pop ecx
:0045E78B 59                      pop ecx
:0045E78C 648910                  mov dword ptr fs:[eax], edx
:0045E78F 68A7E74500              push 0045E7A7

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0045E7A5(U)
|
:0045E794 8D85CCFDFFFF            lea eax, dword ptr [ebp+FFFFFDCC]
:0045E79A E82151FAFF              call 004038C0
:0045E79F C3                      ret


:0045E7A0 E9574BFAFF              jmp 004032FC
:0045E7A5 EBED                    jmp 0045E794
:0045E7A7 5F                      pop edi
:0045E7A8 5E                      pop esi
:0045E7A9 5B                      pop ebx
:0045E7AA 8BE5                    mov esp, ebp
:0045E7AC 5D                      pop ebp
:0045E7AD C3                      ret                    <==== 返回


上面的判断过程用C表示:

int Success;

Registration()
{
    int LenName, LenCode;
    char UserName[100], RegCode[100], InputRegCode[6];
    int i;

    GetInfo( &UserName, LenName);        /*  取得注册信息 */
    GetInfo( &InputRegCode, LenCode);    /*         */
    
    if ( LenName==0 || LenCode!=6 )
    {
        Success=0;
        return;
    }
    
    RegCode[0]= LenName%10;
    for( i=0; i<LenName; i++)
        RegCode[i+1]= UserName[i]%10;

    if ( LenName<6 )
        for( i=0; i<6-LenName; i++)
            RegCode[i+LenName+1]= (RegCode[i+lenName]+1)%10;

    RegCode[7]='\0';
    Success=1;
    if (!strcmp( &RegCode, &InputRegCode))
        Success=0;
    return;
}

    这基本上就是注册机,但没有必要。FIVE的注册形式比较简单,也未采取保护措施,容易分析,只要当作一个CrackMe就好。
    破解完成加上这篇教程,一看时钟,ONLY 9:30,还作点什么的呢?这时有人满嘴酒气的冲进来,莫名其妙的问我是不是黑客,真是“好事不出门,坏事***”:大众根本就不懂HAKKER,CRAKKER是什么含义,也不能解释,真是扫兴。这时抬头看墙上的时钟,CALL 11:30!明天早晨又起不来了。“我单知道,TRW和SI会停系统时钟的,可怜我家的机器阿毛.......”(台下口号声此起彼伏:打倒唐僧),只有谢幕了。
   


                            @20001/03/21