吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 10034|回复: 14
收起左侧

[原创] blowfish加密算法代码还原--分析[反汇编练习] 160个CrackMe之092(crackme1)

[复制链接]
pk8900 发表于 2018-1-25 12:42
本帖最后由 pk8900 于 2018-1-26 12:02 编辑

【前言】      
          最近几天都在研究160个CrackMe之092(crackme1) 这个标了五星难度的crackme,直到昨晚总算将其分析完成,也可以说是将汇编代码逆向还原为C++代码后,并找到注册方法,今天将分析的过程总结整理到笔记中,同时也分享给坛友们。
【crackme简介】
       下载地址:http://pan.baidu.com/share/link?shareid=541269&uk=4146939145
        Visual C++ 6.0编写,UPX 0.89.6 - 1.02壳,是一个computer ID+Unlock code方式验证方式,有提示文字。标注难度为5星。
工具:IDA/X64dbg/OD,代码编写:VS2013
【crackme截图】
Image 1.png
【算法分析】
         本文主要讲算法部分,调试跟踪可能不会太详细,具体操作可以结合我分析算法对相应代码位置下断点,单步运行,即可明白程序的代码流程。
       通过搜索字符串,找到Don't give up. Try agagin!代码处。
[Asm] 纯文本查看 复制代码
004015E0 | E8 EB FA FF FF               | call <crackm.sub_4010D0>                        |
004015E5 | 8B 44 24 64                  | mov eax, dword ptr ss:[esp+0x64]                |
004015E9 | 8B 0D F0 99 40 00            | mov ecx, dword ptr ds:[0x4099F0]                |  直正注册码KEY1
004015EF | 83 C4 1C                     | add esp, 0x1C                                   |
004015F2 | 3B C1                        | cmp eax, ecx                                    |
004015F4 | 75 29                        | jne crackm.40161F                               |
004015F6 | 8B 4C 24 4C                  | mov ecx, dword ptr ss:[esp+0x4C]                |
004015FA | A1 EC 99 40 00               | mov eax, dword ptr ds:[0x4099EC]                |  直正注册码KEY1
004015FF | 3B C8                        | cmp ecx, eax                                    |
00401601 | 75 1C                        | jne crackm.40161F                               |
00401603 | 6A 30                        | push 0x30                                       | UINT uType = MB_OK | MB_ICONEXCLAMATION | MB_ICONQUESTION | MB_ICONSTOP
00401605 | 68 F0 80 40 00               | push <crackm.sub_4080F0>                        | LPCTSTR lpCaption = "Success"
0040160A | 68 D4 80 40 00               | push crackm.4080D4                              | LPCTSTR lpText = "You have done a good job."
0040160F | 56                           | push esi                                        | HANDLE hWnd
00401610 | FF 15 20 61 40 00            | call dword ptr ds:[<&MessageBoxA>]              | MessageBoxA
00401616 | 33 C0                        | xor eax, eax                                    |
00401618 | 5E                           | pop esi                                         |
00401619 | 83 C4 40                     | add esp, 0x40                                   |
0040161C | C2 10 00                     | ret 0x10                                        |
0040161F | 6A 30                        | push 0x30                                       | UINT uType = MB_ICONEXCLAMATION | MB_ICONERROR | MB_ICONHAND | MB_ICONQUESTION | MB_ICONSTOP | MB_ICONWARNING | MB_OK
00401621 | FF 15 24 61 40 00            | call dword ptr ds:[<&MessageBeep>]              | MessageBeep
00401627 | 6A 10                        | push 0x10                                       |
00401629 | 68 CC 80 40 00               | push crackm.4080CC                              | 4080CC:"Failed"
0040162E | 68 B0 80 40 00               | push <crackm.sub_4080B0>                        | 4080B0:"Don't give up. Try agagin!"
00401633 | 56                           | push esi                                        |
00401634 | FF 15 20 61 40 00            | call dword ptr ds:[<&MessageBoxA>]              |

       往上找到关键比较代码,直正注册码KEY1和Key2保存在:[0x4099F0]和:[0x4099EC] 中,这是两个连续的地址,也就是两个DWORD值,在OD中对该地址下内存写入断点,缕清程序的流程。
程序三次调用子程序sub_401130,压栈参数中分别是三个不同的字符串,和相同的地址指针,通过IDA中反汇编的伪代码,将sub_401130整理还原代码如下:
[C++] 纯文本查看 复制代码
void __cdecl sub_401130(unsigned long  *base_a1, unsigned char *char_a2, signed int leng_a3)
{
        int z = 0;
        //unsigned long * key =(unsigned long *)char_a2;
        unsigned long tmp =0;
        unsigned long tmp_char = 0;
        unsigned long key1=0, key2=0;//   KEY初始为0
        unsigned long *k1=&key1;    
        unsigned long *k2=&key2;
        memcpy_s(base_a1 + 0x12, 0x1000 , mem_6198, 0x1000); //复制 00406198内存数据0x1000字节,无变动

        //print_base(base_a1, 20);
        for (int x = 0; x < 0x12; x++)
        {
                tmp = 0;
                for (int y = 0; y <4; y++)   //"94940361391";   //循环取字符串中4个字符组成一个DWORD值
                {
                        if (z == leng_a3) z = 0;
                        tmp_char = 0+char_a2[z];
                        tmp_char = tmp_char << ((3 - y) * 8);
                        tmp |= tmp_char;
                        z++;
                }
                base_a1[x] = mem_6150[x] ^ tmp;    //mem_6150 内存00406150数据,取48字节
        }
        //print_base(base_a1, 20);
        z = 0;
        for (int x = 0; x < 9; x++)   //分9次用Key异或加密数据表头部,每次写入两个DWORD值,共18次,即0x48(72.)个字节
        {
                sub_401070(base_a1, k1, k2);
                *(base_a1 + z) = *k1;
                *(base_a1 + z + 1) = *k2;
                z+=2;
        }
        //print_base(base_a1, 20);
        for (int y = 0; y < 4; y++)   //分4次,每次0x80(128),共0x200(512.)次,每次写入两个DWORD值,即0x1000(4096.)个字节
        {
                for (int x = 0; x < 0x80; x++)
                {
                        sub_401070(base_a1, k1, k2);
                        *(base_a1 + z) = *k1;
                        *(base_a1 + z + 1) = *k2;
                        z += 2;
                }
        }
}

        参考我代码后的备注可以看出,sub_401130函数的三个数参,unsigned long  *base_a1这是一个固定的地址,实际是一个用于加密的数据表的指针, unsigned char *char_a2是用于对数据表中的数据进行加密的字符串,循环取字符串中4个字符组成一个DWORD值与数据表中数据异或加密,signed int leng_a3为传入的字符串的长度。在sub_401130中又调用了sub_401070,于是对sub_401070进行分析和代码还原,整理如下:
[C++] 纯文本查看 复制代码
void sub_401070(unsigned long *base, unsigned long *k1, unsigned long *k2)  //加密密钥Key1和Key2,不变更数据表
{
        unsigned long tmp = *k1;
        for (int x = 0; x < 0x10; x++)
        {
                *k1 ^= base[x];
                tmp = *k1;
                *k1 = sub_401000(base, tmp);
                *k1 = *k1^*k2;
                *k2 = tmp;
        }
        *k1 ^= *(base + 0x10) ;
        *k2 ^= *(base + 0x11) ;
        swap(*k1, *k2);
}

      sub_401070有三个参数:unsigned long *base是数据表,另外还有 unsigned long *k1, unsigned long *k2,这是两个DWORD值,k1,k2,通过sub_401070子程序进行加密变换,sub_401070不变更数据表。在上面的sub_401130调用sub_401070,最初传入k1,k2值均为0,经sub_401070加密后得到的DWORD值,填充到数据表。所以说sub_401130是加密数据表的过程,sub_401070是加密KEY1KEY2的过程。在sub_401070中调用了sub_401000函数,sub_401000还原代码如下:
i
[C++] 纯文本查看 复制代码
nt __cdecl sub_401000(unsigned long *base, unsigned long a2)  //查表函数{
        unsigned char * x1 = (unsigned char *)&a2;   //转为char型指针,分别取出DWORD值中的四个字节值
        unsigned char  x2 = *(x1 + 1);
        unsigned long C1 = *(base + (*(x1 + 3)) + 0x48 / 4);
        unsigned long C2 = *(base + (*(x1 + 2)) + 0x448 / 4);
        unsigned long C3 = *(base + (*(x1 + 1)) + 0x848 / 4);
        unsigned long C4 = *(base + (*(x1 + 0)) + 0xC48 / 4);
        return ((C1 + C2) ^ C3) + C4;
}

      可以看出,sub_401000是一个查表函数,用给定的DWORD值进行查表,得到一个新的DWORD值返回。以上三个函数即程序启动用调用用于加密数据表,并生成Computer ID,并将第二次加密处理后的KEY1KEY2值保存在[0x4099F0]和:[0x4099EC] 中,后来用于对比验证。接下来跟程序对我们输入的假码进行加密的过程,程序调用了sub_4010D0,对sub_4010D0代码还原如下:
[C++] 纯文本查看 复制代码
void sub_4010D0(unsigned long *base, unsigned long *k1, unsigned long *k2)  //对输入的key进行加密,与sub_401070极似
{
        unsigned long tmp;
        int s = 0x44 / 4;
        for (int x = 0; x < 0x10; x++)
        {
                *k1 ^= base;
                tmp = *k1;                *k1 = sub_401000(base, tmp);
                *k1 = *k1^*k2;
                s--;
                *k2 = tmp;
        }
        *k1 ^= *(base + 1);
        *k2 ^= *base;
        swap(*k1, *k2);
}


      sub_4010D0接受三个参数,unsigned long *base,是引用数据表的指针, unsigned long *k1, unsigned long *k2是我们输入的Key1,key2,经sub_4010D0函数中对数据库数据异或操作加密后,与程序在初始过程中保存在[0x4099F0]和:[0x4099EC] 中两个DWORD值进行比较,如果一样,程序提示注册成功,不一样,则提示失败。通过以上分析后,我们将程序的流程进行还原整理,代码如下,这段代码中已包含了逆向找到直接Unlock code的部分:
[C++] 纯文本查看 复制代码
void main()
{
        unsigned char base_key[] ="94940361391";
        unsigned char base_key_CGG[] = "ChinaCrackingGroup";
        unsigned char base_key_CFF[] = "CrackingForFun";
        unsigned long key1 = 0x11111111;//测试用
        unsigned long key2 = 0x22222222;//测试用
        unsigned char base_key1[] = "fishblow";
        unsigned char* the_key =new unsigned char[260];
        unsigned long * k1 = &key1;  //测试用,可改变指针
        unsigned long * k2 = &key2;  //测试用,可改变指针
        
        unsigned long org_k1 = 0x776F6C62;  //"blow";
        unsigned long org_k2 = 0x68736966;  //"fish"
        LPTSTR  user_key = new TCHAR[16];  //wsprintf 函数缓冲区
        memset(the_key, 0, 260);
        getReg((char *)the_key);
        if (strlen((char *)the_key) == 0)
                the_key = base_key;            //注册表获取ProductID失败则采用默认:"94940361391";
        cout << "01--key word is : " << the_key << endl;
        unsigned long  * buf_8980 = new unsigned long[0x1000/4 + 0x12];   //分配加密数据表空间
        memset(buf_8980, 0, 0x1000 + 0x20);                           //填零

        sub_401130(buf_8980, the_key, strlen((char *)the_key));     //第一次初始化加密数据表
        sub_401070(buf_8980, &org_k1, &org_k2);   //加密字符:"fish"   "blow";

        wsprintf(user_key, "%08lX%08lX", org_k1, org_k2);  //输出结果为:界面上的 computer ID

        cout << "your computer ID:" << user_key << endl;

        the_key = base_key_CGG;   //"ChinaCrackingGroup";
        cout << "02--key word is : " << the_key << endl;
        sub_401130(buf_8980, the_key, strlen((const char *)the_key));   //第二次初始化加密数据表
        sub_401070(buf_8980, &org_k1, &org_k2);        //加密字符 “第一次结果”

        wsprintf(user_key, "%08lX%08lX", org_k1,org_k2);
        cout <<"key code is:" <<user_key << endl;

        
        the_key = base_key_CFF;    // "CrackingForFun"
        cout << "03--key word is : " << the_key << endl;
        sub_401130(buf_8980, the_key, strlen((const char *)the_key));   //第三次初始化加密数据表

        //int x2 = sub_401000(buf_8980, key1);
        k1 = &org_k1; k2 = &org_k2;  //改变指针,指向内存中正确KEY比较值
        cout << "input code is : " <<hex<<uppercase<< *k1 << *k2 << endl;
        crack_4010D0(buf_8980, k1, k2);  //解密得到用户应该输入的Unlock Code!!
        //sub_4010D0(buf_8980, k1, k2);  //对输入的KEY进行正常加密后判断是否正确
        cout << hex << "Unlock Code is:" << uppercase << "  " << *k1  << *k2 << "   " << endl;
cout<<"auto head...hello!"<<endl;
system("pause");
}

以上流程用一张流程图来给大家演示一下:
Image 2.png
通过上图演示,弄清了程序的完整流程,想做出注册机就得逆向sub_4010D0,得到Unlock Code,经分析,sub_4010D0是可以逆向推算的,具体代码如下:
[C++] 纯文本查看 复制代码
void crack_4010D0(unsigned long *base, unsigned long *k1, unsigned long *k2)  //注册机反推部分:根据生成过程反推应输入的unlock code
{
        unsigned long tmp1;
        unsigned long tmp2;
        int s = 0x44 / 4-0x10;
        swap(*k1, *k2);
        *k1 ^= *(base + 1);
        *k2 ^= *base;
        
        for (int x = 0; x < 0x10; x++)
        {
                s++;
                tmp2 = sub_401000(base, *k2);
                tmp1 = *k2^ base;  //最开始的k1
                *k2 = tmp2^(*k1);//最开始的k2
                *k1 = tmp1;
        }
        //tmp1 = *k2        
}

       到这里我们就能通过程序反推出正确的Unlock Code了,但我到虚拟机里测试了一下,发现这个注册机并不通用,在虚拟机里生成的computer ID和电脑里生成的不同,通过对比用于复制加密数据的00406198处0x1000个字节发现,这些代码中包含了四个函数的调用地址,这些函数地址是程序运行后动态填入的,因此可以说程序在不同的电脑上运行会出现不同的computer ID,也就是要做出通用的注册机只能注入程序中获取00406198处0x1000个字节的内容来计算注册码。
附上源代码中用到的其它代码,这样代码就全贴上来了,平台VS2013,可以测试,注意mem_6198数据需要自己抓取:

[C++] 纯文本查看 复制代码
#include<iostream>
#include <windows.h>
using namespace std;
//typedef long _DWORD;
unsigned long mem_6198[] = {
        0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69, 0xA458FEA3, 0xF4933D7E, 0x0D95748F, 0x728EB658, 0x718BCD58, 0x82154AEE, 0x7B54A41D, 0xC25A59B5, 0x9C30D539, 0x2AF26013, 0xC5D1B023, 0x286085F0, 0xCA417918, 0xB8DB38EF, 0x8E79DCB0, 0x603A180E, 0x6C9E0E8B, 0xB01E8A3E,............此处省略..........
};
unsigned long mem_6150[] = {
        0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917,
        0x9216D5D9, 0x8979FB1B, 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99, 0x24A19947, 0xB3916CF7, 0x0801F2E2, 0x858EFC16, 0x636920D8, 0x71574E69
};
void getReg(char * ret_value)
{
        //HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion
        LPBYTE   p = new byte[260];
        char * val = (char*)p;
        HKEY reg_handle;
        HKEY h_main = HKEY_LOCAL_MACHINE;
        LPCSTR data = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion";
        LPCSTR data1 = "ProductID";
        DWORD keyType = REG_SZ;//定义数据类型
        DWORD DataLen = 260;//定义数据长度   
        if (RegOpenKeyExA(h_main, data, 0, KEY_READ, &#174;_handle) == ERROR_SUCCESS)
        {
                if (RegQueryValueExA(reg_handle, data1, NULL, &keyType, p, &DataLen) == ERROR_SUCCESS)
                {
                        if (strlen(val))
                        memcpy_s(ret_value,strlen(val), val,strlen(val));
                }                
        }
}
void print_base(unsigned long * base, int len) //打印输出数据表
{
        for (int x = 0; x < len; x++)
        {
                cout << hex << uppercase << *(base + x) << "   ";
                if (x % 4 == 3)cout << endl;
        }
}

总算总结完成了,希望大家能看懂,我也是还原完代码后,感觉这算法很精妙,不可能师出无名,于是百度搜索,找到了blowfish加密算法,大家可以百度查找。
帖子https://www.52pojie.cn/thread-470028-1-1.html中有对于此Crackme的介绍,大家可以参阅学习。
最后,给注册成功后的CRACKME拍个照:

Image 3.png
分析有不对的地方,还请大家指正,共同交流。

免费评分

参与人数 3威望 +2 吾爱币 +12 热心值 +3 收起 理由
Poner + 2 + 10 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
buddama + 1 + 1 我很赞同!
lin_xop + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

sky_bro 发表于 2019-10-24 16:18
楼主看下我刚写的分析 https://www.52pojie.cn/thread-1042630-1-1.html
00406198处0x1000个字节应该是固定的,是pi的十六进制形式的小数部分145~8336位(1~144位-72字节,为初始化S盒所用),每一位是4-bit的十六进制数
 楼主| pk8900 发表于 2018-1-25 20:29
zckun 发表于 2018-1-25 20:20
想玩crackme。。。就是有点太小白了,不知道能不能给个学习路线,汇编入门了

熟悉工具软件,从OD开始,我也没看过那些入门的书籍,网上教程多了。
buddama 发表于 2018-1-25 15:18
mayl8822 发表于 2018-1-25 17:23
感谢分享
february 发表于 2018-1-25 18:19
楼主牛了,这个也搞了
zckun 发表于 2018-1-25 20:20
想玩crackme。。。就是有点太小白了,不知道能不能给个学习路线,汇编入门了
939372735 发表于 2018-1-25 22:10
楼主,建议加个导航贴啊,从1开始到160
zq3332427 发表于 2018-1-26 07:54
感谢分享,暂时还没看明白
冰露㊣神 发表于 2018-1-26 10:01
pk8900 发表于 2018-1-25 20:29
熟悉工具软件,从OD开始,我也没看过那些入门的书籍,网上教程多了。

目前逆算法到第三个了。。开始卡主了
ygfygf_888 发表于 2018-1-26 10:16

谢谢大神分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 16:00

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表