首页
社区
课程
招聘
[原创]菜鸟的第一篇破文
发表于: 2009-9-5 14:26 6171

[原创]菜鸟的第一篇破文

2009-9-5 14:26
6171

【文章标题】: 菜鸟的第一篇破文
【软件名称】: CrackMe3
【保护方式】: 序列号,有anti-debug
【使用工具】: OD, IDA
【操作平台】: WINDOWS XP SP2
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!

暑假时才开始学逆向,这是我的从第一篇破文。经验不足,如果有一些错误,还请各位大虾莫见笑。:-)
这个CrackMe是师兄发来当练习的,可能经过一些修改,存在一些BUG,来源信息被删了,所以真实来源我无法考究。这篇破文是在我的练习报告的基础上改的,在此斗胆贴出来,希望版主能发发慈悲给个邀请码,呵呵。

首先直接运行CrackMe3,输入用户名overwind和假码0123456789,并没有期待的错误提示信息。用OD加载运行,连个界面都不出来便自动退出了,开了HideOD V0.181插件也未见成效。身为菜鸟的我刚开始还真是束手无策。后来在论坛上看到这种自动退出的情况可以尝试命令bp ExitProcess,试了一下,果然在004022CB处断了下来:

0040227D   .  8BF4          mov     esi, esp
0040227F   .  8D4D B8       lea     ecx, dword ptr [ebp-48]
00402282   .  51            push    ecx                                            ; /pStartupinfo
00402283   .  FF15 5C734100 call    dword ptr [<&KERNEL32.GetStartupInfoA>]        ; \GetStartupInfoA
00402289   .  3BF4          cmp     esi, esp
0040228B   .  E8 44F8FFFF   call    <jmp.&MSVCRTD._chkesp>
00402290   .  837D C8 00    cmp     dword ptr [ebp-38], 0                          ; 以下是对返回的STARTUPINFO结构一些成员的值进行检查
00402294   .  75 31         jnz     short 004022C7                                 ; 若发现调试器则调用ExitProcess退出,可改为jmp short 004022D8爆破之
00402296   .  837D CC 00    cmp     dword ptr [ebp-34], 0
0040229A   .  75 2B         jnz     short 004022C7
0040229C   .  837D D8 00    cmp     dword ptr [ebp-28], 0
004022A0   .  75 25         jnz     short 004022C7
004022A2   .  837D DC 00    cmp     dword ptr [ebp-24], 0
004022A6   .  75 1F         jnz     short 004022C7
004022A8   .  837D E0 00    cmp     dword ptr [ebp-20], 0
004022AC   .  75 19         jnz     short 004022C7
004022AE   .  837D D0 00    cmp     dword ptr [ebp-30], 0
004022B2   .  75 13         jnz     short 004022C7
004022B4   .  837D D4 00    cmp     dword ptr [ebp-2C], 0
004022B8   .  75 0D         jnz     short 004022C7
004022BA   .  8B55 E4       mov     edx, dword ptr [ebp-1C]
004022BD   .  81E2 80000000 and     edx, 80
004022C3   .  85D2          test    edx, edx
004022C5   .  74 11         je      short 004022D8                                 ; 若没有发现被调试,跳过,继续执行
004022C7   >  8BF4          mov     esi, esp                                       ; 否则调用ExitProcess退出
004022C9   .  6A 00         push    0                                              ; /ExitCode = 0
004022CB   .  FF15 60734100 call    dword ptr [<&KERNEL32.ExitProcess>]            ; \ExitProcess
004022D1   .  3BF4          cmp     esi, esp
004022D3   .  E8 FCF7FFFF   call    <jmp.&MSVCRTD._chkesp>
004022D8   >  B8 01000000   mov     eax, 1                                         ; 通过反调试的入口地址
004022DD   .  5F            pop     edi
004022DE   .  5E            pop     esi
004022DF   .  5B            pop     ebx
004022E0   .  81C4 88000000 add     esp, 88
004022E6   .  3BEC          cmp     ebp, esp
004022E8   .  E8 E7F7FFFF   call    <jmp.&MSVCRTD._chkesp>
004022ED   .  8BE5          mov     esp, ebp
004022EF   .  5D            pop     ebp
004022F0   .  C3            retn

由上面的代码可以看出,它的反调试主要是调用GetStartupInfoA函数,根据返回的STARTUPINFO结构,来判断程序是否被调试。(关于GetStartupInfoA和STARTUPINFO结构可参考MSDN)可将00402294处改为jmp short 004022D8,之后便可进行动态跟踪了。

上面的对反调试的破解是后来才发现的,身为菜鸟的我起初是偶然发现当用户名长度不足5位时有个错误提示,于是便从搜索提示字符串"用户名长度必须大于4!"入手,来到注册算法部分,用IDA进行静态反汇编的。以下是注册算法:

首先是函数的初始化
00401EA0  /> \55            push    ebp
00401EA1  |.  8BEC          mov     ebp, esp
00401EA3  |.  81EC D4000000 sub     esp, 0D4
00401EA9  |.  53            push    ebx
00401EAA  |.  56            push    esi
00401EAB  |.  57            push    edi
00401EAC  |.  51            push    ecx
00401EAD  |.  8DBD 2CFFFFFF lea     edi, dword ptr [ebp-D4]
00401EB3  |.  B9 35000000   mov     ecx, 35
00401EB8  |.  B8 CCCCCCCC   mov     eax, CCCCCCCC
00401EBD  |.  F3:AB         rep     stos dword ptr es:[edi]
00401EBF  |.  59            pop     ecx
00401EC0  |.  894D FC       mov     dword ptr [ebp-4], ecx
00401EC3  |.  6A 01         push    1
00401EC5  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
00401EC8  |.  E8 43F2FFFF   call    <jmp.&MFC42D.#5056_CWnd::UpdateData>

一些局部变量字串的初始化
00401ECD  |.  C745 9C 01000>mov     dword ptr [ebp-64], 1         ;  初始化[ebp-64]为1,待用
00401ED4  |.  A1 7C514100   mov     eax, dword ptr [41517C]                              ;  "bxm"
00401ED9  |.  8945 98       mov     dword ptr [ebp-68], eax                              ;  将校验字串放在[ebp-68]之后
00401EDC  |.  8B0D 64524100 mov     ecx, dword ptr [415264]                              ;  "恭喜你"
00401EE2  |.  894D 90       mov     dword ptr [ebp-70], ecx                              ;  与下面四句配合,将字串"恭喜你"放在[ebp-70]之后
00401EE5  |.  66:8B15 68524>mov     dx, word ptr [415268]
00401EEC  |.  66:8955 94    mov     word ptr [ebp-6C], dx
00401EF0  |.  A0 6A524100   mov     al, byte ptr [41526A]
00401EF5  |.  8845 96       mov     byte ptr [ebp-6A], al
00401EF8  |.  B9 05000000   mov     ecx, 5
00401EFD  |.  BE 0C534100   mov     esi, 0041530C                                        ;  "用户名长度必须大于4!"
00401F02  |.  8DBD 78FFFFFF lea     edi, dword ptr [ebp-88]
00401F08  |.  F3:A5         rep     movs dword ptr es:[edi], dword ptr [esi]             ;  将"用户名长度必须大于4!"放在[ebp-88]之后
00401F0A  |.  66:A5         movs    word ptr es:[edi], word ptr [esi]
00401F0C  |.  8B0D 58504100 mov     ecx, dword ptr [415058]                              ;  字符串"真的很厉害!"
00401F12  |.  898D 6CFFFFFF mov     dword ptr [ebp-94], ecx                              ;  与下面四句配合,将"真的很厉害!"放在[ebp-94]之后
00401F18  |.  8B15 5C504100 mov     edx, dword ptr [41505C]
00401F1E  |.  8995 70FFFFFF mov     dword ptr [ebp-90], edx
00401F24  |.  A1 60504100   mov     eax, dword ptr [415060]
00401F29  |.  8985 74FFFFFF mov     dword ptr [ebp-8C], eax

取用户名,并判断长度是否大于4,不满足则弹出出错提示框
00401F2F  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
00401F32  |.  83C1 60       add     ecx, 60
00401F35  |.  E8 DCF1FFFF   call    <jmp.&MFC42D.#880_CString::operator char const *>    ;  eax返回用户名地址
00401F3A  |.  50            push    eax                                                  ; /src
00401F3B  |.  8D4D E4       lea     ecx, dword ptr [ebp-1C]                              ; |
00401F3E  |.  51            push    ecx                                                  ; |dest
00401F3F  |.  E8 90F1FFFF   call    <jmp.&MSVCRTD.strcpy>                                ; \strcpy
00401F44  |.  83C4 08       add     esp, 8                                               ;  取用户名放在[ebp-1C]之后
00401F47  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
00401F4A  |.  83C1 60       add     ecx, 60
00401F4D  |.  E8 12F2FFFF   call    <jmp.&MFC42D.#2640_CString::GetLength>               ;  求用户名长度
00401F52  |.  83F8 05       cmp     eax, 5                                               ;  检查用户名长度是否大于4
00401F55  |.  7D 18         jge     short 00401F6F                                       ;  满足跳过
00401F57  |.  6A 00         push    0                                                    ;  否则......
00401F59  |.  6A 00         push    0
00401F5B  |.  8D95 78FFFFFF lea     edx, dword ptr [ebp-88]                              ;  "用户名长度必须大于4!"
00401F61  |.  52            push    edx
00401F62  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
00401F65  |.  E8 00F2FFFF   call    <jmp.&MFC42D.#3517_CWnd::MessageBoxA>                ;  弹出出错提示框
00401F6A  |.  E9 F8010000   jmp     00402167                                             ;  玩完

接着将用户名字串各字符的ASCII码进行相乘,乘积放[ebp-64],[ebp-64]在前面已初始化为1。
00401F6F  |>  8B4D FC       mov     ecx, dword ptr [ebp-4]
00401F72  |.  83C1 60       add     ecx, 60
00401F75  |.  E8 EAF1FFFF   call    <jmp.&MFC42D.#2640_CString::GetLength>               ;  求用户名长度
00401F7A  |.  8945 A8       mov     dword ptr [ebp-58], eax                              ;  放入[ebp-58]
00401F7D  |.  8B45 A8       mov     eax, dword ptr [ebp-58]
00401F80  |.  8945 AC       mov     dword ptr [ebp-54], eax                              ;  用户名长度放入循环变量[ebp-54]
00401F83  |.  EB 09         jmp     short 00401F8E                                       ;  跳入for循环体
00401F85  |>  8B4D AC       /mov     ecx, dword ptr [ebp-54]
00401F88  |.  83E9 01       |sub     ecx, 1
00401F8B  |.  894D AC       |mov     dword ptr [ebp-54], ecx                             ;  循环变量[ebp-54]自减一
00401F8E  |>  837D AC 01     cmp     dword ptr [ebp-54], 1                               ;  若循环变量[ebp-54]大于1,则继续循环
00401F92  |.  7E 13         |jle     short 00401FA7                                      ;  否则跳出
00401F94  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]
00401F97  |.  0FBE4415 E4   |movsx   eax, byte ptr [ebp+edx-1C]                          ;  取用户名第[ebp-54]个(从0开始)字符放入eax
00401F9C  |.  8B4D 9C       |mov     ecx, dword ptr [ebp-64]
00401F9F  |.  0FAFC8        |imul    ecx, eax
00401FA2  |.  894D 9C       |mov     dword ptr [ebp-64], ecx                             ;  [ebp-64] *= eax; [ebp-64]放本循环对用户名的运算结果
00401FA5  |.^ EB DE         \jmp     short 00401F85
这里程序可能有一个相当严重的BUG,在取串中各字符时,是从字串末尾的结束符'\0'开始依次往前取的,而'\0'的ASCII码是0,这样无论如何,最终乘积的结果[ebp-64]总是为0。该部分对应C源程序大致如下:

ebp_58 = strlen(sName);
for (ebp_54=ebp_58; ebp_54>1; ebp_54--)
    ebp_64 *= (DWORD)sName[ebp_54];

在此推测作者的原意大概是:

ebp_58 = strlen(sName);
for (ebp_54=ebp_58-1; ebp_54>0; ebp_54--)
    ebp_64 *= (DWORD)sName[ebp_54];

呵呵,这只是个人推测,或许是这个CrackMe被修改了吧

然后是将上面求得的乘积ebp_64转化为十进制数字符串(ebp-4C)。
00401FA7  |>  C745 AC 00000>mov     dword ptr [ebp-54], 0                                ;  一个do-while循环的开始,索引变量[ebp-54]初始化为0
00401FAE  |>  8B45 9C       /mov     eax, dword ptr [ebp-64]                             ;  将[ebp-64]放入eax
00401FB1  |.  99            |cdq                                                         ;  扩展为EDX:EAX
00401FB2  |.  B9 0A000000   |mov     ecx, 0A
00401FB7  |.  F7F9          |idiv    ecx                                                 ;  EDX:EAX对0A求模
00401FB9  |.  83C2 30       |add     edx, 30                                             ;  再将结果加上30h,转化为数字ASSII码
00401FBC  |.  8B45 AC       |mov     eax, dword ptr [ebp-54]
00401FBF  |.  885405 B4     |mov     byte ptr [ebp+eax-4C], dl                           ;  再将生成的数字放入[ebp-4C+[ebp-54]]; ebp-4C显然是字串首地址
00401FC3  |.  8B45 9C       |mov     eax, dword ptr [ebp-64]                             ;  将[ebp-64]放入eax
00401FC6  |.  99            |cdq                                                         ;  扩展为EDX:EAX
00401FC7  |.  B9 0A000000   |mov     ecx, 0A
00401FCC  |.  F7F9          |idiv    ecx                                                 ;  EDX:EAX对0A求商
00401FCE  |.  8945 9C       |mov     dword ptr [ebp-64], eax                             ;  将商放回[ebp-64]
00401FD1  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]
00401FD4  |.  83C2 01       |add     edx, 1
00401FD7  |.  8955 AC       |mov     dword ptr [ebp-54], edx                             ;  索引变量[ebp-54]加一
00401FDA  |.  837D 9C 00    |cmp     dword ptr [ebp-64], 0
00401FDE  |.^ 75 CE         \jnz     short 00401FAE                                      ;  循环直到[ebp-64]为0
00401FE0  |.  8B45 AC       mov     eax, dword ptr [ebp-54]
00401FE3  |.  C64405 B4 00  mov     byte ptr [ebp+eax-4C], 0                             ;  在字串(ebp-4C)末尾加'\0',标记字串结束
00401FE8  |.  8D4D B4       lea     ecx, dword ptr [ebp-4C]
00401FEB  |.  51            push    ecx                                                  ; /s
00401FEC  |.  E8 E9F0FFFF   call    <jmp.&MSVCRTD.strlen>                                ; \strlen
00401FF1  |.  83C4 04       add     esp, 4                                               ;  求生成的字串(ebp-4C)的长度
00401FF4  |.  8945 A4       mov     dword ptr [ebp-5C], eax                              ;  放入[ebp-5C]
该部分C源程序大致描述为:

ebp_54 = 0;
do {
    ebp_4C[ebp_54] = ebp_64 % 0Ah + 30h;
    ebp_64 /= 0Ah;
    ebp_54++;
} while (ebp_64);
ebp_4C[ebp_54] = '\0';
ebp_5C = strlen(ebp_4C);

由于ebp_64总是为0,所以总有ebp_4C="0"

再然后,是对用户名字串进行处理,将用户名各字符分别加上循环变量(也即该字符在串中的索引值)的2倍,所得结果再放回串中。
00401FF7  |.  C745 AC 00000>mov     dword ptr [ebp-54], 0                                ;  循环变量[ebp-54]初始化为0
00401FFE  |.  EB 09         jmp     short 00402009                                       ;  又一个for循环的开始,跳入循环体
00402000  |>  8B55 AC       /mov     edx, dword ptr [ebp-54]
00402003  |.  83C2 01       |add     edx, 1
00402006  |.  8955 AC       |mov     dword ptr [ebp-54], edx                             ;  循环变量[ebp-54]自增一
00402009  |>  8B45 AC        mov     eax, dword ptr [ebp-54]
0040200C  |.  3B45 A8       |cmp     eax, dword ptr [ebp-58]                             ;  比较[ebp-54]与[ebp-58]的大小
0040200F  |.  7D 31         |jge     short 00402042                                      ;  若[ebp-54]>=[ebp-58]则跳出循环,否则继续
00402011  |.  8B4D AC       |mov     ecx, dword ptr [ebp-54]
00402014  |.  0FBE540D E4   |movsx   edx, byte ptr [ebp+ecx-1C]                          ;  取用户名第[ebp-54]个(从0开始)字符放入edx
00402019  |.  8B45 AC       |mov     eax, dword ptr [ebp-54]
0040201C  |.  8D0C42        |lea     ecx, dword ptr [edx+eax*2]                          ;  ecx = edx + [ebp-54]*2
0040201F  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]
00402022  |.  884C15 E4     |mov     byte ptr [ebp+edx-1C], cl                           ;  再将运算结果的低8位放回用户名第[ebp-54]个(从0开始)字符位置
00402026  |.  8B45 AC       |mov     eax, dword ptr [ebp-54]
00402029  |.  0FBE4C05 E4   |movsx   ecx, byte ptr [ebp+eax-1C]                          ;  再取出
0040202E  |.  83F9 7A       |cmp     ecx, 7A                                             ;  将该字符与'z'比较
00402031  |.  7E 0D         |jle     short 00402040                                      ;  若小于等于'z'则跳过
00402033  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]                             ;  否则......
00402036  |.  83C2 61       |add     edx, 61                                             ;  将该字符加上'a'
00402039  |.  8B45 AC       |mov     eax, dword ptr [ebp-54]
0040203C  |.  885405 E4     |mov     byte ptr [ebp+eax-1C], dl                           ;  再放回
00402040  |>^ EB BE         \jmp     short 00402000
用C源程序描述大概是:

for (ebp_54=0; ebp_54<ebp_58; ebp_54++) {
    sName[ebp_54] += var_54 * 2;
    if (sName[ebp_54] > 'z')
        sName[ebp_54] = ebp_54 + 'a';
}

接下来,程序开始构造另一个字串(ebp-3C),在此之前,它先比较处理后的用户名字串和(ebp-4C)字串的长度大小,[ebp-60]取较小的长度,[ebp-50]指向较小者的串尾(即'\0'处)。
00402042  |>  8B4D A8       mov     ecx, dword ptr [ebp-58]                              ;  用户名长度放ecx
00402045  |.  3B4D A4       cmp     ecx, dword ptr [ebp-5C]                              ;  与(ebp-4C)字串长度比较
00402048  |.  7D 12         jge     short 0040205C                                       ;  若用户名较长则跳
0040204A  |.  8B55 A8       mov     edx, dword ptr [ebp-58]
0040204D  |.  8955 A0       mov     dword ptr [ebp-60], edx                              ;  [ebp-60] = [ebp-58]; 用户名长度
00402050  |.  8B45 A0       mov     eax, dword ptr [ebp-60]
00402053  |.  8D4C05 E4     lea     ecx, dword ptr [ebp+eax-1C]
00402057  |.  894D B0       mov     dword ptr [ebp-50], ecx                              ;  [ebp-50] = [ebp-1C+[ebp-60]]; 即指向用户名串尾
0040205A  |.  EB 10         jmp     short 0040206C
0040205C  |>  8B55 A4       mov     edx, dword ptr [ebp-5C]
0040205F  |.  8955 A0       mov     dword ptr [ebp-60], edx                              ;  [ebp-60] = [ebp-5C]; (ebp-4C)串长
00402062  |.  8B45 A0       mov     eax, dword ptr [ebp-60]
00402065  |.  8D4C05 B4     lea     ecx, dword ptr [ebp+eax-4C]
00402069  |.  894D B0       mov     dword ptr [ebp-50], ecx                              ;  [ebp-50] = [ebp-4C+[ebp-60]]; 即指向(ebp-4C)串尾

字串(ebp-3C)的构造就是将处理后的用户名字串和(ebp-4C)字串轮流从头开始取字符添加到字串(ebp-3C)之后,直到其中一个字串取完字符。例如处理后的用户名为"abcd",字串(ebp-4C)为"123456"。则字串(ebp-3C)最后为"a1b2c3d4"。
0040206C  |>  C745 AC 00000>mov     dword ptr [ebp-54], 0                                ;  循环变量[ebp-54]初始化为0
00402073  |.  EB 09         jmp     short 0040207E                                       ;  又一个for循环的开始,跳入循环体
00402075  |>  8B55 AC       /mov     edx, dword ptr [ebp-54]
00402078  |.  83C2 01       |add     edx, 1
0040207B  |.  8955 AC       |mov     dword ptr [ebp-54], edx                             ;  循环变量[ebp-54]自增一
0040207E  |>  8B45 AC        mov     eax, dword ptr [ebp-54]
00402081  |.  3B45 A0       |cmp     eax, dword ptr [ebp-60]
00402084  |.  7D 1E         |jge     short 004020A4                                      ;  若[ebp-54]>=[ebp-60]则跳出循环
00402086  |.  8B4D AC       |mov     ecx, dword ptr [ebp-54]
00402089  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]
0040208C  |.  8A4415 B4     |mov     al, byte ptr [ebp+edx-4C]                           ;  将字串(ebp-4C)第[ebp-54]个(从0开始)字符
00402090  |.  88444D C4     |mov     byte ptr [ebp+ecx*2-3C], al                         ;  放入字串(ebp-3C)第[ebp-54]*2个(从0开始)字符位置
00402094  |.  8B4D AC       |mov     ecx, dword ptr [ebp-54]
00402097  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]                             ;  将用户名第[ebp-54]个(从0开始)字符
0040209A  |.  8A4415 E4     |mov     al, byte ptr [ebp+edx-1C]
0040209E  |.  88444D C5     |mov     byte ptr [ebp+ecx*2-3B], al                         ;  放入字串(ebp-3C)第[ebp-54]*2+1个(从0开始)字符位置
004020A2  |.^ EB D1         \jmp     short 00402075
004020A4  |>  8B4D AC       mov     ecx, dword ptr [ebp-54]
004020A7  |.  C6444D C4 00  mov     byte ptr [ebp+ecx*2-3C], 0                           ;  在新生成的字串(ebp-3C)串尾加'\0'标记结束

下面的这部分是多余的,前面[ebp-50]指向了较短字串的串尾,所以strcat并未改变字串(ebp-3C)。
004020AC  |.  8B55 B0       mov     edx, dword ptr [ebp-50]                              ;  [ebp-50]指向了用户名和字串(ebp-4C)较短串的串尾
004020AF  |.  52            push    edx                                                  ; /src
004020B0  |.  8D45 C4       lea     eax, dword ptr [ebp-3C]                              ; |
004020B3  |.  50            push    eax                                                  ; |dest
004020B4  |.  E8 51F0FFFF   call    <jmp.&MSVCRTD.strcat>                                ; \strcat
004020B9  |.  83C4 08       add     esp, 8                                               ;  将[ebp-58]所指字串接到字串(ebp-3C)之后
004020BC  |.  8D4D C4       lea     ecx, dword ptr [ebp-3C]
004020BF  |.  51            push    ecx                                                  ; /s
004020C0  |.  E8 15F0FFFF   call    <jmp.&MSVCRTD.strlen>                                ; \strlen
004020C5  |.  83C4 04       add     esp, 4                                               ;  求连接后字串(ebp-3C)的长度
004020C8  |.  8BF0          mov     esi, eax                                             ;  暂放在esi
以上构造字串(ebp-3C)的代码可用C语言大致描述为:

if (ebp_58 < ebp_5C) {
    ebp_60 = ebp_58;
    ebp_50 = sName + ebp_60; 
} else {
    ebp_60 = ebp_5C;
    ebp_50 = ebp_4C + ebp_60; 
}

for (ebp_54=0; ebp_54<ebp_60; ebp_54++) {
    ebp_3C[ebp_54*2] = ebp_4C[ebp_54];
    ebp_3C[ebp_54*2+1] = sName[ebp_54];
}
ebp_3C[ebp_54] = '\0';
strcat(ebp_3C, ebp_50);

这里可能有一处BUG,就是ebp_50的赋值,ebp_50似乎应指向较长字串的剩余部分,而非较短字串的串尾,这样后面的strcat才有意义。或许这又是因为被修改了吧
由于之前必有ebp_4C="0",故(ebp-3C)只有两个字符,"0X",其中X是用户名的首字母。

跟踪到这里,输入的假码一直没出场,看看后面的代码也不多了,正纳闷着,后面几句终于冒出来了。
004020CA  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
004020CD  |.  83C1 64       add     ecx, 64
004020D0  |.  E8 41F0FFFF   call    <jmp.&MFC42D.#880_CString::operator char const *>    ;  eax返回输入的序列号(以下称假码)的地址
004020D5  |.  50            push    eax                                                  ; /s
004020D6  |.  E8 FFEFFFFF   call    <jmp.&MSVCRTD.strlen>                                ; \strlen
004020DB  |.  83C4 04       add     esp, 4                                               ;  求假码长度
004020DE  |.  3BF0          cmp     esi, eax                                             ;  比较字串(ebp-3C)与假码长度
004020E0  |.  0F85 81000000 jnz     00402167                                             ;  若不相等玩完

检查字串(ebp-3C)与假码长度是否相等之后,进行如下校验:
首先从字串(ebp-3C)和假码中依次各取一个字符,两者进行异或,将结果暂存至esi。
然后从校验字串"bxm"中依次取一个字符,暂存至edx。若'm'取完则下次再从'b'开始。
接着比较esi与edx是否相等,若相等则继续循环,比较下一位,直至串尾,否则跳出循环。
004020E6  |.  C745 AC 00000>mov     dword ptr [ebp-54], 0                                ;  循环变量[ebp-54]初始化为0
004020ED  |.  EB 09         jmp     short 004020F8                                       ;  又一个for循环的开始,跳入循环体
004020EF  |>  8B55 AC       /mov     edx, dword ptr [ebp-54]
004020F2  |.  83C2 01       |add     edx, 1
004020F5  |.  8955 AC       |mov     dword ptr [ebp-54], edx                             ;  循环变量[ebp-54]自增一
004020F8  |>  8D45 C4        lea     eax, dword ptr [ebp-3C]
004020FB  |.  50            |push    eax                                                 ; /s
004020FC  |.  E8 D9EFFFFF   |call    <jmp.&MSVCRTD.strlen>                               ; \strlen
00402101  |.  83C4 04       |add     esp, 4                                              ;  求字串(ebp-3C)的长度
00402104  |.  3945 AC       |cmp     dword ptr [ebp-54], eax
00402107  |.  73 34         |jnb     short 0040213D                                      ;  若[ebp-54]不小于这个长度则跳出循环
00402109  |.  8B4D AC       |mov     ecx, dword ptr [ebp-54]
0040210C  |.  0FBE740D C4   |movsx   esi, byte ptr [ebp+ecx-3C]                          ;  将字串(ebp-3C)第[ebp-54]个(从0开始)字符放入esi
00402111  |.  8B55 AC       |mov     edx, dword ptr [ebp-54]
00402114  |.  52            |push    edx
00402115  |.  8B4D FC       |mov     ecx, dword ptr [ebp-4]
00402118  |.  83C1 64       |add     ecx, 64
0040211B  |.  E8 50F0FFFF   |call    <jmp.&MFC42D.#850_CString::operator[]>              ;  取假码第[ebp-54]个(从0开始)字符
00402120  |.  0FBEC0        |movsx   eax, al                                             ;  放入eax
00402123  |.  33F0          |xor     esi, eax                                            ;  esi ^= eax
00402125  |.  8B45 AC       |mov     eax, dword ptr [ebp-54]
00402128  |.  99            |cdq
00402129  |.  B9 03000000   |mov     ecx, 3
0040212E  |.  F7F9          |idiv    ecx                                                 ;  edx = [ebp-54] % 3
00402130  |.  0FBE5415 98   |movsx   edx, byte ptr [ebp+edx-68]                          ;  将校验字串"bxm"第edx个(从0开始)字符放入edx
00402135  |.  3BF2          |cmp     esi, edx                                            ;  比较esi与edx
00402137  |.  74 02         |je      short 0040213B                                      ;  若相等,继续循环
00402139  |.  EB 02         |jmp     short 0040213D                                      ;  否则,跳出循环
0040213B  |>^ EB B2         \jmp     short 004020EF

下面求字串(ebp-3C)长度,若字串(ebp-3C)长度不为0则注册成功。
0040213D  |>  8D45 C4       lea     eax, dword ptr [ebp-3C]
00402140  |.  50            push    eax                                                  ; /s
00402141  |.  E8 94EFFFFF   call    <jmp.&MSVCRTD.strlen>                                ; \strlen
00402146  |.  83C4 04       add     esp, 4                                               ;  求字串(ebp-3C)的长度
00402149  |.  8945 AC       mov     dword ptr [ebp-54], eax
0040214C  |.  837D AC 00    cmp     dword ptr [ebp-54], 0
00402150  |.  74 15         je      short 00402167                                       ;  若长度为0,跳走玩完
00402152  |.  6A 00         push    0                                                    ;  终于到了
00402154  |.  8D4D 90       lea     ecx, dword ptr [ebp-70]                              ;  "恭喜你"
00402157  |.  51            push    ecx
00402158  |.  8D95 6CFFFFFF lea     edx, dword ptr [ebp-94]                              ;  "真的很厉害!"
0040215E  |.  52            push    edx
0040215F  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
00402162  |.  E8 03F0FFFF   call    <jmp.&MFC42D.#3517_CWnd::MessageBoxA>                ;  弹出注册成功对话框
以上部分用C语言大致描述为:

if (strlen(ebp_3C) == strlen(sWrongSerial)) {
    for (ebp_54=0; ebp_54<strlen(ebp_3C); ebp_54++)
        if ((ebp_3C[ebp_54] ^ sWrongSerial[ebp_54]) != ebp_68[ebp_54 % 3])
            break;
    if (strlen(ebp_3C)) {
        成功了
    }
}

上面通过求字串(ebp-3C)长度来判断检验结果很令人费解,按理应是比较循环变量[ebp-54]与字串(ebp-3C)的长度是否相等,即检查循环是否正常退出。下面这两句代码多少说明了一些问题:

  00402149  |.  8945 AC       mov     dword ptr [ebp-54], eax
  0040214C  |.  837D AC 00    cmp     dword ptr [ebp-54], 0

在此作者可能在写程序时犯了一个错误,将比较语句:

if (ebp_54 == strlen(ebp_3C)) {注册成功}

写成了:

if (ebp_54 = strlen(ebp_3C)) {注册成功}

于是造成了前一部分的校验形同虚设,只要字串(ebp-3C)不为空,即可注册成功。整个注册校验唯一需要满足条件的就是以下这两句判断:

  004020DE  |.  3BF0          cmp     esi, eax                                             ;  比较字串(ebp-3C)与假码长度
  004020E0  |.  0F85 81000000 jnz     00402167                                             ;  若不相等玩完

结合前面分析的可知,字串(ebp-3C)总是只有两个字母,所以注册码只要是任意的两个字符就可以了。

最后是函数的收尾工作......
00402167  |>  6A 00         push    0
00402169  |.  8B4D FC       mov     ecx, dword ptr [ebp-4]
0040216C  |.  E8 9FEFFFFF   call    <jmp.&MFC42D.#5056_CWnd::UpdateData>
00402171  |.  5F            pop     edi
00402172  |.  5E            pop     esi
00402173  |.  5B            pop     ebx
00402174  |.  81C4 D4000000 add     esp, 0D4
0040217A  |.  3BEC          cmp     ebp, esp
0040217C  |.  E8 53F9FFFF   call    <jmp.&MSVCRTD._chkesp>
00402181  |.  8BE5          mov     esp, ebp
00402183  |.  5D            pop     ebp
00402184  \.  C3            retn

这个CrackMe的算法对于菜鸟我来说分析起来还是颇费一番工夫的,在算法逆向过程中学到了不少的东西。可能是被修改过的缘故,程序存在一些BUG,而CrackMe的真实来源又找不到,实在可惜。
另外,回过头来看,与其说这是一篇破文,不如说是一篇逆文,贴了这么长的代码主要是为了说明对原算法的逆向与分析,破解倒成为次要的了。:-) 初次写这种文章,把握得不是很好,也没什么技术含量,还请高手们不要见笑了。就当是我发上来赚个发帖量吧,不要嫌我占了地方哟!


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 7
支持
分享
最新回复 (25)
雪    币: 100
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
嗨   又是长长的代码
2009-9-5 14:44
0
雪    币: 35
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
楼主很强
支持一下
2009-9-5 17:35
0
雪    币: 234
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
代码式文章,话说破文有这么好拿邀请码么
2009-9-5 21:13
0
雪    币: 139
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
因为是刚学的,所以还写不出深一点的技术类文章,不行的话,再努力努力了
2009-9-5 21:46
0
雪    币: 64
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
呵呵 不错 学习得真快
2009-9-5 23:17
0
雪    币: 84
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
都能暴破了...我还在入门中........
2009-9-5 23:26
0
雪    币: 31
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
种自动退出的情况可以尝试命令bp ExitProcess,

这个还比较管用哈,我一开始也遇见这种情况,自动退出!
2009-9-6 12:09
0
雪    币: 231
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
这样都不能有邀请码?那我是来参观的了,呵呵。
2009-9-6 13:07
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
大师啊,参观一下,我还不会OD
2009-9-6 13:39
0
雪    币: 39
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
不错了,起码可以看懂很多代码意思了。我这点还不会呢。
2009-9-6 13:54
0
雪    币: 278
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
楼主好样的哈
2009-9-8 21:39
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
不错的教程啊
2009-9-9 07:42
0
雪    币: 105
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
不错的教程啊
2009-9-9 17:26
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
谢谢楼主,基本上能看懂
2009-10-25 14:11
0
雪    币: 93
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
不错的教程,谢谢楼主
2009-10-25 23:37
0
雪    币: 58
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
把自己的学习过程记录下来大家一起分享大家都能进步,多谢楼主了
2009-10-27 08:57
0
雪    币: 233
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
楼主写得很详细,可以看懂,学习了
2009-10-27 10:05
0
雪    币: 82
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
呵呵 不错 可是我看不懂
2009-10-27 17:03
0
雪    币: 2370
活跃值: (2048)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
20
呵呵  详细!!谢谢
2009-10-27 21:14
0
雪    币: 136
活跃值: (1485)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
21
不错...学习了
2009-10-28 17:13
0
雪    币: 72
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
22
看雪的强人越来越多了,哈哈
2009-10-28 18:41
0
雪    币: 1013
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
其实那些代码大部分俺都看不懂,还正在学习中。。。
2009-10-28 23:06
0
雪    币: 93
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
楼主很强
支持一下
2009-10-28 23:12
0
雪    币: 72
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
25
这种破文,写的真整齐,好棒的。
2009-10-28 23:26
0
游客
登录 | 注册 方可回帖
返回
//