zoukankan      html  css  js  c++  java
  • 破解 crackme(“不可逆“函数)

    系统 : Windows xp

    程序 : crackme

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

    要求 : 注册机编写

    使用工具 : IDA Pro & OD

    可在“PEDIY CrackMe 2007”中查找关于此程序的讨论,标题为“简单分析crackme算法之一”。

     

     

    运行程序,在Help-》Register菜单项中注册,输入测试用户名密码。程序提示错误,错误字串为:“No luck there, mate!”。打开IDA载入程序,通过字串表定位错误字串,并通过交叉参考在关键算法处下断:

    00401209   > 6A 00         push    0                                ; /lParam = NULL
    0040120B   .  68 53124000   push    00401253                         ; |DlgProc = CRACKME.00401253
    00401210   .  FF75 08       push    dword ptr [ebp+8]                ; |hOwner
    00401213   .  68 15214000   push    00402115                         ; |pTemplate = "DLG_REGIS"
    00401218   .  FF35 CA204000 push    dword ptr [4020CA]               ; |hInst = 00400000
    0040121E   .  E8 7D020000   call    <jmp.&USER32.DialogBoxParamA>    ; DialogBoxParamA
    00401223   .  83F8 00       cmp     eax, 0
    00401226   .^ 74 BE         je      short 004011E6
    00401228   .  68 8E214000   push    0040218E                         ;  用户名入栈
    0040122D   .  E8 4C010000   call    0040137E                         ;  根据用户名进行一些操作得出一个值
    00401232   .  50            push    eax
    00401233   .  68 7E214000   push    0040217E                         ;  序列号入栈
    00401238   .  E8 9B010000   call    004013D8                         ;  根据序列号进行一些操作得出一个值
    0040123D   .  83C4 04       add     esp, 4                           ;  平衡堆栈
    00401240   .  58            pop     eax
    00401241   .  3BC3          cmp     eax, ebx                         ;  F(用户名) == F(序列号)?
    00401243   .  74 07         je      short 0040124C

    40137E处汇编代码:

    0040137E  /$  8B7424 04     mov     esi, dword ptr [esp+4]           ;  取用户名
    00401382  |.  56            push    esi
    00401383  |>  8A06          /mov     al, byte ptr [esi]
    00401385  |.  84C0          |test    al, al                          ;  如果迭代结束
    00401387  |.  74 13         |je      short 0040139C                  ;  跳出循环
    00401389  |.  3C 41         |cmp     al, 41                          ;  字符小于0x41,则提示错误
    0040138B  |.  72 1F         |jb      short 004013AC
    0040138D  |.  3C 5A         |cmp     al, 5A                          ;  如果字符大于等于5A,
    0040138F  |.  73 03         |jnb     short 00401394                  ;  则转化字符为大写
    00401391  |.  46            |inc     esi                             ;  循环变量自增
    00401392  |.^ EB EF         |jmp     short 00401383
    00401394  |>  E8 39000000   |call    004013D2
    00401399  |.  46            |inc     esi                             ;  循环变量自增
    0040139A  |.^ EB E7         jmp     short 00401383
    0040139C  |>  5E            pop     esi                              ;  取用户名
    0040139D  |.  E8 20000000   call    004013C2
    004013A2  |.  81F7 78560000 xor     edi, 5678
    004013A8  |.  8BC7          mov     eax, edi                         ;  异或结果保存在eax里
    004013AA  |.  EB 15         jmp     short 004013C1
    004013AC  |>  5E            pop     esi
    004013AD  |.  6A 30         push    30                               ; /Style = MB_OK|MB_ICONEXCLAMATION|MB_APPLMODAL
    004013AF  |.  68 60214000   push    00402160                         ; |Title = "No luck!"
    004013B4  |.  68 69214000   push    00402169                         ; |Text = "No luck there, mate!"
    004013B9  |.  FF75 08       push    dword ptr [ebp+8]                ; |hOwner
    004013BC  |.  E8 79000000   call    <jmp.&USER32.MessageBoxA>        ; MessageBoxA
    004013C1  >  C3            retn
    004013C2  /$  33FF          xor     edi, edi
    004013C4  |.  33DB          xor     ebx, ebx
    004013C6  |>  8A1E          /mov     bl, byte ptr [esi]
    004013C8  |.  84DB          |test    bl, bl                          ;  如果迭代结束
    004013CA  |.  74 05         |je      short 004013D1                  ;  跳出循环
    004013CC  |.  03FB          |add     edi, ebx                        ;  累加字符值
    004013CE  |.  46            |inc     esi                             ;  循环变量自增
    004013CF  |.^ EB F5         jmp     short 004013C6
    004013D1  >  C3            retn                                     ;  结果保存在edi
    004013D2  /$  2C 20         sub     al, 20
    004013D4  |.  8806          mov     byte ptr [esi], al
    004013D6  .  C3            retn

    4013D8处汇编代码:

    004013D8  /$  33C0          xor     eax, eax
    004013DA  |.  33FF          xor     edi, edi
    004013DC  |.  33DB          xor     ebx, ebx
    004013DE  |.  8B7424 04     mov     esi, dword ptr [esp+4]           ;  取序列号
    004013E2  |>  B0 0A         /mov     al, 0A
    004013E4  |.  8A1E          |mov     bl, byte ptr [esi]
    004013E6  |.  84DB          |test    bl, bl                          ;  如果迭代结束
    004013E8  |.  74 0B         |je      short 004013F5                  ;  则跳出循环
    004013EA  |.  80EB 30       |sub     bl, 30                          ;  将字符转化为它对应的数字
    004013ED  |.  0FAFF8        |imul    edi, eax
    004013F0  |.  03FB          |add     edi, ebx
    004013F2  |.  46            |inc     esi                             ;  循环变量自增
    004013F3  |.^ EB ED         jmp     short 004013E2
    004013F5  |>  81F7 34120000 xor     edi, 1234                        ;  结果与0x1234异或
    004013FB  |.  8BDF          mov     ebx, edi                         ;  存入ebx
    004013FD  .  C3            retn

    以上就是关键处代码,通过观察不难发现,该程序采用的是非明码比较(F(用户名)=F(序列号))。这样的程序我们用高级语言写出F(用户名),再算出F(序列号)的逆算法即可。

    打开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();
        bool StrIsOk = true;
    
        for ( int i = 0 ; i != len ; i++ ){
            if ( str[i] < 0x41 ){
                StrIsOk = false;
                break;
            }
        }
    
        if ( StrIsOk ){                                            //格式控制。
            str.MakeUpper();                                    //转化为大写。
            
            unsigned int sum = 0;
            int i;
            for ( i = 0 ; i != len ; i++ )
                sum += str[i];
    
            sum ^= 0x5678;
    
            /*
            //模拟F(序列号)
            CString Serial = "1234";
            unsigned int res = 0,num = 0;
            int al = 0xA;
            for ( i = 0 ; i != len ; i++ ){
                num = Serial[i] - 0x30;
                res *= al;
                res += num;
            }
    
            res ^= 0x1234;
            */
    
            //写出F(序列号)的逆算法
            /*
                一开始想的是进行异或操作后,逆向整个for循环,类似于从最后一个元素Serial[len]反推,
                res-=num;res/=al;这样,但发现要求的就是num的值,感觉该函数根本不可逆。
                想来想去只好翻书看看有没有求解方法,发现程序与《解密与解密》P107处的Serial程序算法居然一样。
                原来F(序列号)中的for循环只是将字串转化为了它对应的十进制数!最后做了一个异或而已。
                也就是说,将F(用户名)的结果与0x1234进行异或,得出的就是本来的序列号!
                通过这次破解,我知道了逆向不一定要微观上一步步反推,还可以从宏观上判断函数到底做了什么。
            */
    
            str.Format( "%d",( sum ^ 0x1234 ) );
            SetDlgItemText( IDC_EDIT_PASSWORD,str );
        }
        else
            MessageBox( "用户名格式错误!" );
    }

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

    运行效果:

     

    -------------------------------------------------------------------------------------------------------------------

    后记:

      在测试注册机时发现了一个问题,当输入的字串长度过长,注册机算出的序列号是错误的。反复确认算法无误之后,怀疑是CRACKME中的字串有“猫腻”。这里,对GetDlgItemText下断点:

    004012B5  |.  6A 0B         |push    0B                              ; /Count = B (11.)
    004012B7  |.  68 8E214000   |push    0040218E                        ; |Buffer = CRACKME.0040218E
    004012BC  |.  68 E8030000   |push    3E8                             ; |ControlID = 3E8 (1000.)
    004012C1  |.  FF75 08       |push    dword ptr [ebp+8]               ; |hWnd
    004012C4  |.  E8 07020000   |call    <jmp.&USER32.GetDlgItemTextA>   ; GetDlgItemTextA

    对比GetDlgItemText的定义:

    int GetDlgItemText( HWND hDlg , int nID, LPTSTR lpStr, int nMaxCount) const;

    nID 指定了要获取其标题的控件的整数标识符。 lpStr 指向要接收控件的标题或文本的缓冲区。 nMaxCount 指定了要拷贝到lpStr的字符串的最大长度(以字节为单位)。如果字符串比nMaxCount要长,它将被截断。 rString 对一个CString对象的引用。

    nMaxCount 在程序中被设为11,实际保存的字符也就是10个。怪不得过长的用户名算出的序列号是错误的。这里,我们将OnBtnDecrypt函数的if ( StrIsOk )判断语句做个改动,在开始处增加一行代码:str = str.Left( 10 );就可以解决问题。

    我们一路奋战,不是为了改变世界,而是不让世界改变我们 ——《熔炉》
  • 相关阅读:
    WPF 获取本机所有字体拿到每个字符的宽度和高度
    WPF 自己封装 Skia 差量绘制控件
    WPF 漂亮的现代化控件 新 ModernWPF 界面库
    dotnet 在 UOS 国产系统上使用 MonoDevelop 创建 GTK 全平台带界面应用
    dotnet 在 UOS 国产系统上使用 MonoDevelop 进行拖控件开发 GTK 应用
    dotnet 在 UOS 国产系统上安装 MonoDevelop 开发工具
    通过java采集PC麦克风音频及播放wav音频文件
    nginx-http之ssl(九)
    nginx-http之proxy(八)
    nginx-http之upstream(七)
  • 原文地址:https://www.cnblogs.com/ZRBYYXDM/p/5116197.html
Copyright © 2011-2022 走看看