系统 : Windows xp

程序 : TDC‘s keygenme

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

要求 : 脱壳 & 注册机编写

使用工具 :OD & IDA & PEID & LordPE & Import REC

可在“PEDIY CrackMe 2007”中查找关于此程序的分析,标题为“TDC.Crackme10算法分析”。

 

首先了解一下壳是什么:

作者编好软件后,编译成exe可执行文件。

1.有一些版权信息需要保护起来,不想让别人随便改动,如作者的姓名等,即为了保护软件不被破解,通常都是采用加壳来进行保护。

2.需要把程序搞的小一点,从而方便使用。于是,需要用到一些软件,它们能将exe可执行文件压缩,

3.在黑客界给木马等软件加壳脱壳以躲避杀毒软件。     

                                                --源自百度百科

简单说来,壳就是用来防止破解者对于程序文件的非法修改。壳又分为压缩壳、加密壳。他们的应用范围也不尽相同,压缩壳主要用来压缩程序文件的体积,对于破解者来说强度不大。而加密壳以加密保护为重点,用尽了各种反跟踪技术,保护重点是在OEP(程序入口点)的隐藏和IAT(输入表)的加密上。

 

这篇博文中我们来尝试破解一个被压缩壳保护、带有简单反调试的程序。

将程序拖入PEID查看状态:

显示该程序加了upx壳,可以利用网上的脱壳机来脱壳。这里,为了加深印象,我们尝试手动脱壳。od载入程序,断在外壳处:

00446330 > $  60            pushad
00446331   .  BE 00804200   mov     esi, 00428000
00446336   .  8DBE 0090FDFF lea     edi, dword ptr [esi+FFFD9000]
0044633C   .  57            push    edi
0044633D   .  83CD FF       or      ebp, FFFFFFFF
00446340   .  EB 10         jmp     short 00446352

单步执行pushad之后,esp指向0012FFA4 ,键入命令hr 12FFA4下硬件断点,F9运行程序断在此处 

00446493   .- E9 73ABFBFF   jmp     0040100B

单步执行跳转进入OEP:

0040100B    6A 0A           push    0A
0040100D    68 B90B0000     push    0BB9
00401012    FF35 18624000   push    dword ptr [406218]
00401018    E8 05030000     call    00401322                         ; jmp to kernel32.FindResourceA
0040101D    A3 1C624000     mov     dword ptr [40621C], eax
00401022    FF35 1C624000   push    dword ptr [40621C]
00401028    FF35 18624000   push    dword ptr [406218]
0040102E    E8 07030000     call    0040133A                         ; jmp to kernel32.LoadResource
00401033    A3 20624000     mov     dword ptr [406220], eax
00401038    E8 C3FFFFFF     call    00401000
0040103D    FF35 1C624000   push    dword ptr [40621C]
00401043    FF35 18624000   push    dword ptr [406218]
00401049    E8 F8020000     call    00401346                         ; jmp to kernel32.SizeofResource
0040104E    A3 24624000     mov     dword ptr [406224], eax
00401053    FF35 20624000   push    dword ptr [406220]
00401059    E8 E2020000     call    00401340                         ; jmp to kernel32.SetHandleCount
0040105E    8BF0            mov     esi, eax
00401060    A1 24624000     mov     eax, dword ptr [406224]
00401065    83C0 04         add     eax, 4
00401068    50              push    eax
00401069    6A 40           push    40
0040106B    E8 C4020000     call    00401334                         ; jmp to kernel32.GlobalAlloc
00401070    A3 28624000     mov     dword ptr [406228], eax

此时单击菜单Debug->hardware breakpoints删除之前设置的硬件断点。

打开LordPE选择keygenme并单击右键选择“完整转存”:

保存dump文件之后,再打开输入表重建工具Import REC附加到keygenme:

填写OEP为“0000100B”,依次单击“自动查找IAT”、“获取输入表”:

最后,单击“修复转存文件”,选中之前的dump文件,则脱壳成功。

 

脱壳成功之后我们用IDA载入程序,shift + F12查看字符串表:

发现提示成功的字串“Congratulations! You are now level 2! :-)”,双击定位,通过交叉引用来到调用它的位置并向上翻找出关键算法:

TDC0:00401181                 cmp     dword ptr [ebp+0Ch], 111h
TDC0:00401188                 jnz     loc_401292
TDC0:0040118E                 cmp     dword ptr [ebp+10h], 3E9h
TDC0:00401195                 jnz     loc_40125C
TDC0:0040119B                 push    33h
TDC0:0040119D                 push    offset dword_40622C
TDC0:004011A2                 push    7D1h
TDC0:004011A7                 push    dword ptr [ebp+8]
TDC0:004011AA                 call    GetDlgItemTextA
TDC0:004011AF                 imul    eax, 5
TDC0:004011B2                 cmp     eax, 0FAh
TDC0:004011B7                 ja      loc_401246
TDC0:004011BD                 cmp     eax, 1Eh
TDC0:004011C0                 jb      loc_401246
TDC0:004011C6                 push    0
TDC0:004011C8                 push    0
TDC0:004011CA                 push    7D2h
TDC0:004011CF                 push    dword ptr [ebp+8]
TDC0:004011D2                 call    GetDlgItemInt
;省略一部分关键代码:

这里,选中关键代码,单击“视图”->"图表"->"流程图"显示关键算法的流程:

可以直观的看出,关键算法处用了不少分支语句,程序中肯定加了不少对输入的判断。

打开OD载入程序,输入命令"bp 004011AA",F9运行,程序断在此处:

0040119B    6A 33           push    33
0040119D    68 2C624000     push    0040622C                         ; ASCII "pediy"
004011A2    68 D1070000     push    7D1
004011A7    FF75 08         push    dword ptr [ebp+8]
004011AA    E8 AF010000     call    <jmp.&user32.GetDlgItemTextA>
004011AF    6BC0 05         imul    eax, eax, 5                      ; 用户名字串长度*5
004011B2    3D FA000000     cmp     eax, 0FA                         ; 高于0xFA?
004011B7    0F87 89000000   ja      00401246                         ; 提示Name must be between 5 and 51 length
004011BD    83F8 1E         cmp     eax, 1E                          ; 低于0x1E?
004011C0    0F82 80000000   jb      00401246                         ; 提示Name must be between 5 and 51 length
004011C6    6A 00           push    0
004011C8    6A 00           push    0
004011CA    68 D2070000     push    7D2
004011CF    FF75 08         push    dword ptr [ebp+8]
004011D2    E8 81010000     call    <jmp.&user32.GetDlgItemInt>      ; 获取序列号
004011D7    A3 5F624000     mov     dword ptr [40625F], eax          ; 并保存
004011DC    68 2C624000     push    0040622C                         ; 用户名入栈
004011E1    E8 C2000000     call    004012A8

跟入4012A8:

004012A8    55              push    ebp
004012A9    8BEC            mov     ebp, esp
004012AB    803D B4124000 C>cmp     byte ptr [4012B4], 0CC           ; 检测断点,发现则跳转出错
004012B2    74 53           je      short 00401307
004012B4    33C0            xor     eax, eax
004012B6    33C9            xor     ecx, ecx
004012B8    803D D4124000 C>cmp     byte ptr [4012D4], 0CC           ; 检测断点,发现则跳转出错
004012BF    74 46           je      short 00401307
004012C1    33D2            xor     edx, edx
004012C3    8B55 08         mov     edx, dword ptr [ebp+8]           ; 取用户名字串
004012C6    8A02            mov     al, byte ptr [edx]               ; 迭代字串
004012C8    84C0            test    al, al                           ; 迭代完毕?
004012CA    74 08           je      short 004012D4                   ; 则跳转
004012CC    03C8            add     ecx, eax                         ; 累加
004012CE    C1C1 08         rol     ecx, 8                           ; 循环左移8位
004012D1    42              inc     edx                              ; 循环变量自增
004012D2  ^ EB F2           jmp     short 004012C6
004012D4    83F1 02         xor     ecx, 2                           ; 结果与2异或
004012D7    83E9 50         sub     ecx, 50
004012DA    81F1 37130000   xor     ecx, 1337                        ; 结果与1337异或
004012E0    56              push    esi
004012E1    66:8B35 7961400>mov     si, word ptr [406179]            ; 获取当前系统年份
004012E8    66:03CE         add     cx, si                           ; 加入cx
004012EB    803D FA124000 C>cmp     byte ptr [4012FA], 0CC           ; 检测断点,发现则跳转出错
004012F2    74 13           je      short 00401307
004012F4    5E              pop     esi
004012F5    A1 5F624000     mov     eax, dword ptr [40625F]          ; 获取序列号
004012FA    3BC1            cmp     eax, ecx                         ; 与F(用户名)的结果是否一致?
004012FC    75 04           jnz     short 00401302                   ; 否则置406263为1
004012FE    33C0            xor     eax, eax
00401300    EB 0E           jmp     short 00401310
00401302    33C0            xor     eax, eax
00401304    40              inc     eax
00401305    EB 10           jmp     short 00401317
00401307    C605 63624000 0>mov     byte ptr [406263], 1
0040130E    EB 07           jmp     short 00401317
00401310    C605 63624000 0>mov     byte ptr [406263], 0
00401317    C9              leave
00401318    C2 0400         retn    4

该子程序布下了简单的反调试,众所周知,断点的原理是将指定地址的指令替换为int 3中断,int 3的十六进制表示为0CC。通过实时检测载入内存的指令是否是0CC,就可以判断自身有没有被调试。在不改动程序文件的条件下,我们只能尽量少下断点,避免触发反调试。

 

回到剩下的关键算法:

004011E6    803D 63624000 0>cmp     byte ptr [406263], 1
004011ED    74 43           je      short 00401232
004011EF    85C0            test    eax, eax
004011F1    75 17           jnz     short 0040120A
004011F3    68 06614000     push    00406106                         ; ASCII "Congratulations! You are now level 2! :-)"
004011F8    68 D2070000     push    7D2
004011FD    FF75 08         push    dword ptr [ebp+8]
00401200    E8 77010000     call    <jmp.&user32.SetDlgItemTextA>
00401205    E9 98000000     jmp     004012A2
0040120A    803D 1C124000 C>cmp     byte ptr [40121C], 0CC
00401211    74 1F           je      short 00401232
00401213    803D 23124000 C>cmp     byte ptr [401223], 0CC
0040121A    74 16           je      short 00401232
0040121C    6A 00           push    0
0040121E    68 D9604000     push    004060D9                         ; ASCII "Sorry, that is incorrect my mate. Try again."
00401223    68 D2070000     push    7D2
00401228    FF75 08         push    dword ptr [ebp+8]
0040122B    E8 4C010000     call    <jmp.&user32.SetDlgItemTextA>
00401230    EB 70           jmp     short 004012A2
00401232    68 55614000     push    00406155                         ; ASCII "Debugger detected, check cancelled!"
00401237    68 D2070000     push    7D2
0040123C    FF75 08         push    dword ptr [ebp+8]
0040123F    E8 38010000     call    <jmp.&user32.SetDlgItemTextA>
00401244    EB 5C           jmp     short 004012A2
00401246    68 30614000     push    00406130                         ; ASCII "Name must be between 5 and 51 length"
0040124B    68 D2070000     push    7D2
00401250    FF75 08         push    dword ptr [ebp+8]
00401253    E8 24010000     call    <jmp.&user32.SetDlgItemTextA>

至此,程序算法流程已经分析完毕。马上动手编写注册机。

我们直接打开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 <= 50 && len >= 6 ){                            //格式控制。
        unsigned int sum = 0;

        for ( int i = 0 ; i != len ; i++ ){
            sum += str[i];
            sum = ( sum >> (32 - 8) ) | ( sum << 8 );        //循环左移8位
        }

        sum = ( (sum ^ 2) - 0x50 ) ^ 0x1337;

        SYSTEMTIME st;
        CString strDate,strTime;
        GetLocalTime(&st);                                    //获取系统时间
        int year = st.wYear;

        __asm{                                                //为了防止进位内嵌汇编进行低位相加。
            push eax;
            push ebx;

            mov eax,year;
            mov ebx,sum;

            add bx,ax;
            mov sum,ebx;

            pop ebx;
            pop eax;
        }

        CString PassWord;
        PassWord.Format( "%u",sum );
        SetDlgItemText( IDC_EDIT_PASSWORD,PassWord );
    }
    else
        MessageBox( "用户名格式错误!" );

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

运行效果:

  至此,TDC‘s keygenme的破解就完成了。

 

PS:情人节到了,愿天下情侣可以天长地久,也希望和我一样的单身汪不要气馁,多多努力,争取早日脱单。

祝诸君,胸中沟壑自成!