系统 : Windows xp

程序 : KeyGenMe 1 by Taliesin

程序下载地址 :http://pan.baidu.com/s/1c2HTuqk

要求 : 注册机编写 

使用工具 : OD

可在看雪论坛中查看关于此程序的讨论:传送门

 

老规矩,先查看下字符串,定位关键代码:

004014DF  |.  B8 6C304000   mov     eax, 0040306C                    ;  great job!
004014E4  |.  8BD8          mov     ebx, eax
004014E6  |.  83C3 0B       add     ebx, 0B
004014E9  |.  6A 00         push    0                                ; /Style = MB_OK|MB_APPLMODAL
004014EB  |.  50            push    eax                              ; |Title => "Great Job!"
004014EC  |.  53            push    ebx                              ; |Text => "You have completed Key Gen Me #1."
004014ED  |.  6A 00         push    0                                ; |hOwner = NULL
004014EF  |.  E8 B4000000   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA
004014F4  |.  C3            retn
004014F5  |>  6A 00         push    0                                ; /Style = MB_OK|MB_APPLMODAL
004014F7  |.  68 C2304000   push    004030C2                         ; |not this time.
004014FC  |.  68 99304000   push    00403099                         ; |try again, something did not work right.
00401501  |.  6A 00         push    0                                ; |hOwner = NULL
00401503  |.  E8 A0000000   call    <jmp.&user32.MessageBoxA>        ; \MessageBoxA
00401508  |.  6A 00         push    0                                ; /ExitCode = 0
0040150A  \.  E8 A5000000   call    <jmp.&kernel32.ExitProcess>      ; \ExitProcess

大概就是简单的一个判断逻辑,现在我们下断点看看程序的流程。

输入密码之后发现程序没有断在API上,反而显示:"Try Again, something did not work right."

看来是遇到反调试了,反复查验发现有一处检测内存是否中断指令的代码:

00401519   $  BF 96124000   mov     edi, 00401296                    ;  Entry address
0040151E   .  B9 00010000   mov     ecx, 100
00401523   .  B0 99         mov     al, 99
00401525   .  34 55         xor     al, 55                           ;  al = cc
00401527   .  F2:AE         repne   scas byte ptr es:[edi]           ;  扫描
00401529   .  85C9          test    ecx, ecx
0040152B   .  74 06         je      short 00401533
0040152D   .  5E            pop     esi
0040152E   .  33F6          xor     esi, esi
00401530   .  57            push    edi
00401531   .^ EB C2         jmp     short 004014F5
00401533   >  C3            retn

可以粗暴点直接爆破,也可以将ZF Set为1覆盖判断结果。

接下来还有一处反调试代码:

00401485   .  33DB          xor     ebx, ebx
00401487   .  BF 80144000   mov     edi, 00401480
0040148C   .  83EF 60       sub     edi, 60
0040148F   .  B8 DE000000   mov     eax, 0DE
00401494   .  83F0 12       xor     eax, 12
00401497   .  B9 59000000   mov     ecx, 59
0040149C   .  F2:AE         repne   scas byte ptr es:[edi]           ;  内存检查
0040149E   .  85C9          test    ecx, ecx
004014A0   .  74 06         je      short 004014A8
004014A2   .  5E            pop     esi
004014A3   .  33F6          xor     esi, esi
004014A5   .  57            push    edi
004014A6   .  EB 4D         jmp     short 004014F5
004014A8   >  C3            retn
004014A9   $  BE 9C154000   mov     esi, <jmp.&user32.GetDlgItemText>;  Entry address
004014AE   .  8B7E 02       mov     edi, dword ptr [esi+2]
004014B1   .  8B3F          mov     edi, dword ptr [edi]             ;  API地址
004014B3   .  B9 06000000   mov     ecx, 6
004014B8   .  B0 CC         mov     al, 0CC
004014BA   .  F2:AE         repne   scas byte ptr es:[edi]           ;  扫描
004014BC   .  85C9          test    ecx, ecx
004014BE   .  74 06         je      short 004014C6
004014C0   .  5E            pop     esi
004014C1   .  33F6          xor     esi, esi
004014C3   .  57            push    edi
004014C4   .  EB 2F         jmp     short 004014F5
004014C6   >  C3            retn

小心点绕过即可,当然啦,如果会执行太多次还是直接爆破为好。

接下来是算法部分:

00401332   $  33C0          xor     eax, eax
00401334   .  B9 00000000   mov     ecx, 0
00401339   .  BE 23304000   mov     esi, 00403023                    ;  ASCII "TELBJTMPWM"
0040133E   .  8A06          mov     al, byte ptr [esi]
00401340   .  EB 10         jmp     short 00401352
00401342   >  0FB6C0        movzx   eax, al                          ;  遍历序列号
00401345   .  80B8 50314000>cmp     byte ptr [eax+403150], 2         ;  是否是大写字母
0040134C   .  75 0A         jnz     short 00401358                   ;  不是则 输入不合法
0040134E   .  41            inc     ecx
0040134F   .  8A0431        mov     al, byte ptr [ecx+esi]
00401352   >  3C 00         cmp     al, 0                            ;  字符创结尾?
00401354   .^ 77 EC         ja      short 00401342                   ;  是则结束循环
00401356   .  EB 07         jmp     short 0040135F
00401358   >  C605 44304000>mov     byte ptr [403044], 40
0040135F   >  BE 00304000   mov     esi, 00403000                    ;  ASCII "pediy"
00401364   .  33C9          xor     ecx, ecx
00401366   .  B8 01000000   mov     eax, 1
0040136B   .  33D2          xor     edx, edx
0040136D   .  C705 45304000>mov     dword ptr [403045], 0
00401377   >  B9 00000000   mov     ecx, 0
0040137C   .  8A0C32        mov     cl, byte ptr [edx+esi]           ;  遍历用户名
0040137F   .  80F9 00       cmp     cl, 0                            ;  字符创结尾?
00401382   .  74 09         je      short 0040138D                   ;  是则结束循环
00401384   .  42            inc     edx
00401385   .  000D 45304000 add     byte ptr [403045], cl            ;  累加
0040138B   .^ EB EA         jmp     short 00401377
0040138D   >  A1 45304000   mov     eax, dword ptr [403045]
00401392   .  B9 18000000   mov     ecx, 18
00401397   .  99            cdq
00401398   .  F7F9          idiv    ecx
0040139A   .  8815 4F304000 mov     byte ptr [40304F], dl            ;  保存 累加结果除以 18 的余数
004013A0   .  8A0D 44304000 mov     cl, byte ptr [403044]
004013A6   .  80F9 40       cmp     cl, 40
004013A9   .  75 05         jnz     short 004013B0
004013AB   .  E9 45010000   jmp     004014F5
004013B0   >  E9 CB000000   jmp     00401480
004013B5   .  C3            retn

比较序列号:

004013B6   $  55            push    ebp
004013B7   .  8BEC          mov     ebp, esp
004013B9   .  68 23304000   push    00403023                         ;  ASCII "TELBJTMPWM"
004013BE   .  E8 7D010000   call    00401540                         ;  算出长度
004013C3   .  83F8 0A       cmp     eax, 0A
004013C6   .  0F85 29010000 jnz     004014F5
004013CC   .  BE 23304000   mov     esi, 00403023                    ;  ASCII "TELBJTMPWM"
004013D1   .  B8 00000000   mov     eax, 0
004013D6   .  BB 00000000   mov     ebx, 0
004013DB   .  33C9          xor     ecx, ecx
004013DD   .  EB 06         jmp     short 004013E5
004013DF   >  8A0C30        mov     cl, byte ptr [eax+esi]
004013E2   .  03D9          add     ebx, ecx                         ;  累加字符串前九位
004013E4   .  40            inc     eax
004013E5   >  83F8 09       cmp     eax, 9                           ;  循环结束?
004013E8   .^ 72 F5         jb      short 004013DF
004013EA   .  8BC3          mov     eax, ebx
004013EC   .  B9 09000000   mov     ecx, 9
004013F1   .  99            cdq
004013F2   .  F7F9          idiv    ecx                              ;  累加数除以9
004013F4   .  A3 4A304000   mov     dword ptr [40304A], eax          ;  保存商
004013F9   .  8B7D 08       mov     edi, dword ptr [ebp+8]
004013FC   .  8A15 4F304000 mov     dl, byte ptr [40304F]
00401402   .  8AC2          mov     al, dl
00401404   .  3C 18         cmp     al, 18                           ;  之前的余数大于18?
00401406   .  76 02         jbe     short 0040140A
00401408   .  2C 18         sub     al, 18
0040140A   >  A2 4E304000   mov     byte ptr [40304E], al            ;  保存
0040140F   .  33C0          xor     eax, eax
00401411   .  A0 4E304000   mov     al, byte ptr [40304E]
00401416   .  8A2438        mov     ah, byte ptr [eax+edi]           ;  取字符
00401419   .  8A36          mov     dh, byte ptr [esi]
0040141B   .  38F4          cmp     ah, dh                           ;  是否一致?
0040141D   .  0F85 D2000000 jnz     004014F5
00401423   .  80EE 41       sub     dh, 41
00401426   .  8AF2          mov     dh, dl
00401428   .  B4 00         mov     ah, 0
0040142A   .  A2 4E304000   mov     byte ptr [40304E], al
0040142F   .  33C0          xor     eax, eax
00401431   .  A0 4E304000   mov     al, byte ptr [40304E]
00401436   .  02C2          add     al, dl                           ;  余数相加
00401438   .  3C 18         cmp     al, 18                           ;  大于18?
0040143A   .  76 02         jbe     short 0040143E
0040143C   .  2C 18         sub     al, 18
0040143E   >  B9 02000000   mov     ecx, 2
00401443   .  8A2438        mov     ah, byte ptr [eax+edi]           ;  从表中取字符
00401446   .  8A3431        mov     dh, byte ptr [ecx+esi]
00401449   .  38F4          cmp     ah, dh                           ;  是否一致?
0040144B   .  0F85 A4000000 jnz     004014F5
00401451   .  EB 24         jmp     short 00401477
00401453   >  A2 4E304000   mov     byte ptr [40304E], al
00401458   .  33C0          xor     eax, eax
0040145A   .  A0 4E304000   mov     al, byte ptr [40304E]
0040145F   .  80EE 41       sub     dh, 41                           ;  对于首字母A的偏移
00401462   .  8AD6          mov     dl, dh
00401464   .  41            inc     ecx
00401465   .  02C2          add     al, dl
00401467   .  3C 18         cmp     al, 18                           ;  低于等于18?
00401469   .  76 02         jbe     short 0040146D
0040146B   .  2C 18         sub     al, 18
0040146D   >  8A2438        mov     ah, byte ptr [eax+edi]
00401470   .  8A3431        mov     dh, byte ptr [ecx+esi]
00401473   .  38F4          cmp     ah, dh
00401475   .  75 7E         jnz     short 004014F5
00401477   >  83F9 08       cmp     ecx, 8                           ;  3 - 8
0040147A   .^ 72 D7         jb      short 00401453
0040147C   .  C9            leave
0040147D   .  C2 0400       retn    4

序列号的最后一位字符 和 第二位:

004014CE  /$  BE 23304000   mov     esi, 00403023                    ;  ASCII "TELBJTMPWM"
004014D3  |.  A1 4A304000   mov     eax, dword ptr [40304A]          ;  取出商
004014D8  |.  8A5E 09       mov     bl, byte ptr [esi+9]
004014DB  |.  38D8          cmp     al, bl
004014DD  |.  75 16         jnz     short 004014F5
00401510   $  A0 24304000   mov     al, byte ptr [403024]
00401515   .  3C 45         cmp     al, 45                           ;  序列号第二个字符必须是E
00401517   .^ 75 DC         jnz     short 004014F5

看得晕了?不要紧,我来总结一下构成序列号的过程:

1.必须全部大写

2.长度为10.

3.第一个字符按余数从表中载入

4.第二个字符为“E”

5.第三个字符按余数从表中载入

6.第4-9个字符按照前一个字符取数,再从表中载入

7.最后一位为前9位的平均数。

 

整体算法不困难,就是稍微有点复杂,有点耐心就可以搞定啦。

打开http://www.cnblogs.com/ZRBYYXDM/p/5115596.html中搭建的框架,将OnBtnDecrypt函数编辑如下:

void CKengen_TemplateDlg::OnBtnDecrypt() 
{
    // TODO: Add your control notification handler code here
    CString str;
    GetDlgItemText( IDC_EDIT_NAME,str );                    //获取用户名字串基本信息。
    int len = str.GetLength();

    if ( len != 0 ){                                        //格式控制。
        char* KeyList = "ZWATRQLCGHPSXYENVBJDFKMU";
        char Key[11];
        byte sum = 0;

        for ( int i = 0 ; i != len ; i++ ){
            sum += str[i];
        }
        byte remainder = sum % 0x18;

        Key[0] = KeyList[remainder];
        Key[1] = 'E';
        
        remainder *= 2;

        if ( remainder > 0x18 )
            remainder -= 0x18;

        Key[2] = KeyList[remainder];

        for ( i = 3 ; i != 9 ; i++ ){
            remainder += (Key[i-1] - 'A');

            if ( remainder > 0x18 )
                remainder -= 0x18;

            Key[i] = KeyList[remainder];
        }

        DWORD KeySum = 0;
        
        for ( i = 0 ; i != 9 ; i++ ){
            KeySum += Key[i];
        }

        Key[9] = KeySum / 9;
        Key[10] = '\0';

        SetDlgItemText( IDC_EDIT_PASSWORD,Key );
    }
    else
        MessageBox( "用户名格式错误!" );
}

再在OnInitDialog中添加此代码修改标题:SetWindowText(_T("Keygen"));

运行效果:

 

本例中,分别对几个程序关键点和API检测断点,在流程中不断则执行这些代码。这就让破解者比较头疼。

我认为比较强大的防护不是无法攻破的城堡,而是尽量拖延时间,消耗对方的精力。能做到 破解成本 > 软件价值,那就是很好的防御措施。

 

例如,代码开始执行的时候 重复检测断点,启动防调试机制(格盘,关机,等等不可描述的行为),让破解者头疼不已,只能选择爆破程序。

这时,将反调试代码送入MD5函数,生成一段不可逆的子串。

再给子串做操作生成特殊数值,最终跳转到特定地址( 基地址 + 特殊数值 )。

 

这样,一旦动了反调试代码,程序将执行未知指令,即刻崩溃。

 

想要逆向这样的程序还是比较痛苦的,特别是对于不熟悉加密算法的骇客来说。就算掌握了整个流程,要得到真正的地址就要先默默忍受反调试的折磨:)