首页
社区
课程
招聘
[原创]密码学CrackMe之BlowFish算法CM分析
发表于: 2007-10-12 14:27 5149

[原创]密码学CrackMe之BlowFish算法CM分析

2007-10-12 14:27
5149
【文章标题】: BlowfishCrackMe.exe分析过程
【文章作者】: coolstar14
【使用工具】: IDA, Peid
【软件名称】: BlowfishCrackMe.exe
【软件大小】: 204KB
【下载地址】: http://bbs.pediy.com/attachment.php?attachmentid=6585&d=1183561757
【软件介绍】: 用BlowFish作为校验算法的CrackMe。
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
具体过程:
Ida反汇编, Strings参考, 看到 '恭喜你, 注册码正确!'字样, 查看引用位置, 定位到关键点如下:
在此之前, 是判断两个编辑框不能输入为空等等的操作.
.text:00407255 loc_407255:                             ; CODE XREF: DialogFunc+12Dj
.text:00407255                 lea     eax, [ebp+var_310]
.text:0040725B                 push    0               ; int
.text:0040725D                 push    eax             ; char *
.text:0040725E                 lea     ecx, [ebp+inputSn]
.text:00407264                 push    offset s_Windrand ; "WindRand"
.text:00407269                 push    ecx             ; inputSn
.text:0040726A                 mov     [ebp+var_4], 0
.text:00407271                 call    sub_406F70                ;获得输入内容和明码比较前, 就这一个函数了, 关键调用.
.text:00407271
.text:00407276                 add     esp, 10h
.text:00407279                 lea     edx, [ebp+var_310]
.text:0040727F                 lea     eax, [ebp+InputName]
.text:00407285                 push    edx             ; int
.text:00407286                 push    eax             ; lpString2
.text:00407287                 call    sub_407030                ;如果做过WindRand密码学的CM, 很容易知道, 这个是比较函数.
.text:00407287
.text:0040728C                 add     esp, 8
.text:0040728F                 cmp     eax, 1
.text:00407292                 push    40h             ; uType
.text:00407294                 push    offset Caption  ; "注册提示"
.text:00407299                 jnz     short loc_4072BA
.text:00407299
.text:0040729B                 push    offset s_ZUgmVSI ; "恭喜你,注册码正确!"
.text:004072A0                 push    esi             ; hWnd
.text:004072A1                 call    ds:MessageBoxA
.text:004072A7                 xor     eax, eax
.text:004072A9                 mov     ecx, [ebp+var_C]

Peid 插件 Kanal检查加密算法, 发现Blowfish_s_init在426168, 被地址401372使用, 可以怀疑401372所在函数sub_4010A0是Blowfins_init了.
bp 4010a0 中断它, 运行, 观察堆栈.
0:000> bp 4010a0
*** WARNING: Unable to verify checksum for image00400000
*** ERROR: Module load completed but symbols could not be loaded for image00400000
0:000> g
Breakpoint 0 hit
eax=0012f6e0 ebx=77d6ae36 ecx=003c2138 edx=00000000 esi=00070510 edi=0012f87b
eip=004010a0 esp=0012f6c8 ebp=0012f74c iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000212
image00400000+0x10a0:
004010a0 64a100000000    mov     eax,dword ptr fs:[00000000h] fs:003b:00000000=0012f728
0:000> dds esp L4
0012f6c8  00406f41 image00400000+0x6f41 函数返回地址
0012f6cc  0012f6f0                        第一个参数, 查看内容为 WindRand
0012f6d0  00000038                第二个参数, 长度56
0012f6d4  0012f6e0
OK, BlowFish 密钥获得, 长度为56的串, 前面部分为WindRand,不足部分补0.
这儿补充一下, blowfish_init函数key是需要补齐56位的, 不要直接想当然的使用WindRand做为Key, 最初我在这上面是花了不少无用功的.

关键函数:
.text:00406F70 ; int __cdecl sub_406F70(char *inputSn,char *,char *,int)
.text:00406F70 sub_406F70      proc near               ; CODE XREF: DialogFunc+171p
.text:00406F70
.text:00406F70 inputSn         = dword ptr  8
.text:00406F70 WindRand        = dword ptr  0Ch
.text:00406F70 outbuf          = dword ptr  10h
.text:00406F70 arg_C           = dword ptr  14h
.text:00406F70
.text:00406F70                 push    ebp
.text:00406F71                 mov     ebp, esp
.text:00406F73                 mov     eax, [ebp+WindRand]
.text:00406F76                 mov     ecx, [ebp+arg_C]
.text:00406F79                 push    ebx
.text:00406F7A                 push    esi
.text:00406F7B                 push    edi
.text:00406F7C                 push    eax             ; char *
.text:00406F7D                 push    ecx             ; int
.text:00406F7E                 call    sub_406E50      ; 内有前面分析到的对sub_4010A0 blowfish_init 的调用
.text:00406F7E
.text:00406F83                 mov     edi, [ebp+inputSn]
.text:00406F86                 mov     ebx, eax        ;sub_406e50调用结果存储到ebx. 从后面分析, 406e50应该是初始化了一个类或结构.
.text:00406F88                 or      ecx, 0FFFFFFFFh
.text:00406F8B                 xor     eax, eax
.text:00406F8D                 add     esp, 8
.text:00406F90                 repne scasb
.text:00406F92                 not     ecx
.text:00406F94                 dec     ecx             ; ecx = strlen(inputSn)
.text:00406F95                 mov     esi, ecx
.text:00406F97                 shr     esi, 1
.text:00406F99                 mov     eax, esi
.text:00406F9B                 add     eax, 3
.text:00406F9E                 and     al, 0FCh
.text:00406FA0                 call    __alloca_probe
.text:00406FA0
.text:00406FA5                 mov     edx, [ebp+inputSn]
.text:00406FA8                 mov     eax, esp
.text:00406FAA                 push    esi
.text:00406FAB                 push    eax
.text:00406FAC                 push    edx
.text:00406FAD                 mov     [ebp+WindRand], eax
.text:00406FB0                 call    sub_406E00      ; inputSn-->Hex
.text:00406FB0
.text:00406FB5                 lea     eax, [esi+1]
.text:00406FB8                 add     esp, 0Ch
.text:00406FBB                 mov     [ebp+inputSn], eax
.text:00406FBE                 add     eax, 3
.text:00406FC1                 and     al, 0FCh
.text:00406FC3                 call    __alloca_probe
.text:00406FC3
.text:00406FC8                 mov     eax, [ebx]                ;eax指向前面怀疑为类的地址
.text:00406FCA                 mov     ecx, ebx
.text:00406FCC                 mov     edi, esp
.text:00406FCE                 call    dword ptr [eax+18h] ; 调用了一个类的函数, 具体做什么的, 不太清楚
.text:00406FD1                 mov     eax, [ebp+WindRand]
.text:00406FD4                 mov     edx, [ebx]                ;edx 指向前面怀疑为类的地址
.text:00406FD6                 push    esi
.text:00406FD7                 push    edi
.text:00406FD8                 push    eax
.text:00406FD9                 mov     ecx, ebx
.text:00406FDB                 call    dword ptr [edx+0Ch] ;  调用了一个类的函数, blowfish_de(in, out, len)
.text:00406FDE                 mov     eax, [ebp+inputSn]
.text:00406FE1                 mov     byte ptr [edi+esi], 0
.text:00406FE5                 add     eax, 3
.text:00406FE8                 and     al, 0FCh
.text:00406FEA                 call    __alloca_probe
.text:00406FEA
.text:00406FEF                 or      ecx, 0FFFFFFFFh
.text:00406FF2                 xor     eax, eax
.text:00406FF4                 repne scasb
.text:00406FF6                 not     ecx
.text:00406FF8                 sub     edi, ecx
.text:00406FFA                 mov     edx, esp
.text:00406FFC                 mov     eax, ecx
.text:00406FFE                 mov     esi, edi
.text:00407000                 mov     edi, edx
.text:00407002                 push    edx             ; lpString2
.text:00407003                 shr     ecx, 2
.text:00407006                 rep movsd
.text:00407008                 mov     ecx, eax
.text:0040700A                 and     ecx, 3
.text:0040700D                 rep movsb
.text:0040700F                 mov     ecx, [ebp+outbuf]
.text:00407012                 push    ecx             ; lpString1
.text:00407013                 call    ds:lstrcpyA
.text:00407019                 lea     esp, [ebp-0Ch]
.text:0040701C                 pop     edi
.text:0040701D                 pop     esi
.text:0040701E                 pop     ebx
.text:0040701F                 pop     ebp
.text:00407020                 retn
.text:00407020
.text:00407020 sub_406F70      endp
.text:00407020

关键函数比较简单而且清晰, 与BlowFish算法也相符. 引用一段:
7baK9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8Y4N6%4N6#2)9J5k6i4m8W2k6r3W2&6i4K6u0W2j5$3!0E0i4K6u0r3N6s2g2@1L8%4u0A6j5h3I4Q4x3V1k6U0K9r3q4H3y4W2)9J5c8V1y4Z5j5i4l9$3i4K6u0V1x3#2)9J5k6o6k6Q4x3X3g2Z5N6r3@1`.
用BlowFish算法解密,同样也需要两个过程。
1.密钥预处理
2.信息解密

可以看到这儿是把我们输入的序列号转为16进制解密, 解密后的结果放置到输出缓冲区, 用于在sub_406F70调用后, 使用sub_407030与输入的用户名比较, 如果相符即注册成功.
获得注册码, 我们需要做的是:
BlowFins_Encrypt(用户名) 用户名需补齐被8整除, 结果转为16进制字符串表示即OK.

注册机借用 PEDIY CrackMe2007 包中 加密算法\ blowfish\ happytown 23th crackme 中所带的注册机的实现代码, 稍微修改一下以符合本CM的要求.
CrackMe2007 可以从论坛置顶(110K9s2c8@1M7q4)9K6b7g2)9J5c8W2)9J5c8X3u0T1M7K6q4Q4x3X3g2H3k6h3c8A6P5g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Z5L8%4N6@1K9s2u0W2j5h3c8Q4x3X3g2H3K9s2m8Q4x3@1k6@1i4K6y4p5y4o6V1#2z5o6k6Q4x3U0W2Q4c8e0c8Q4b7U0S2Q4b7f1c8Q4c8e0k6Q4z5o6W2Q4b7V1g2Q4c8e0g2Q4z5o6S2Q4b7U0m8Q4x3V1x3`. 一层层点击进入, 即可找到happytown 23th 的注册机, 修改其main函数如下即可.
        string nameStr;
        cout << "Enter your name here:" << endl;
        cin >> nameStr;

        // BLOWFISH算法实现如下:
        unsigned char bfKey[56];
        memset(bfKey, 0, 56);
        memcpy(bfKey, "WindRand", 8);        // 密钥

        int lenFix = nameStr.size();
        unsigned char Text[1024];                        // 源串
        memset(Text, 0, 1024);                                // 这里是把源串补足部分全填0了. 如果不填0, 只需要保证字符串结尾处有个0, 后面的内容是随便填充的,得出的注册码也会不同.
        memcpy(Text, nameStr.c_str(), lenFix);
        if(lenFix % 8)
                lenFix = lenFix + (8-lenFix%8);
               
        CBlowFish myBlowFish(bfKey, 56);        // bushfish_init
        unsigned char out[1024] = {0x0};                // 目的缓冲区
        myBlowFish.Encrypt(Text, out, lenFix);        // lenFix需为8的倍数, 不足补0
       
        char bResult[1024] = {0x0};
        CharStr2HexStr(out, bResult, lenFix);

        cout << "\n"
                << "Your serial number is:"
                << "\n"
                << bResult
                << "\n\n";

一组符合的注册码:
coolstar14
05F4D42F5CC84A798BAC0B0CCF9E80AB
另外因为有填充字节的存在, 注册码并不唯一, 如下这一组也是可以的.
coolstar14
05F4D42F5CC84A7987214C3FB6D9501C

最后感谢WindRand大侠提供的加密算法系列的简单CM, 当我恰好想了解BlowFish时有例子可以做. 另:WindRand大侠是要求深入到加密算法中的, 这个... 偶功力实在不够而且深入到一个成熟的算法中个人觉得也没有太大必要的吧.

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回