吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3326|回复: 0
收起左侧

[原创] 160 Crackme 之 157 -- thecodingone.2 的跟踪分析及注册机实现

[复制链接]
solly 发表于 2019-12-20 18:06
本帖最后由 solly 于 2019-12-20 18:18 编辑

160 之 157 - thecodingone.2 是一个命令行形式的 Crackme,通过命令行交互方式输入注册信息,并输出结果。


首先看看文件信息:
00.png
节的信息如下:
01.png
可以看到,这是一个由 Borland C++ 编译的 Crackme,没有加壳,因此跟踪分析也比较容易。


首先运行一下 Crackme,输出如下图:
09.png
需要一个 keyfile 文件验证,我们用 OD 载入 Crackme 程序来分析。载入后如下图所示:
10.png
可以看到,这一个调用 C++ 库写成的 Crackme,一开始 Crackme 创建了两个文件流(fstream)。并对流进行初始化,如下图所示:
11.png
初始化了两个流,文件名为空(NULL)。按 F8 执行,执行到下图所示位置:
12.png
可以看出,Crackme 将自身执行文件作为文件流打开了。同时,还需要打开另一个文件 crack.dat ,这个文件就是 Keyfile,目前不存在,我们先创建一个这样的文件,文件内容随意填,如下图所示:
13.png

用本文编辑器生成一个 crack.dat,输入一些内容并保存。
14.png
如上图所示,crackme 通过 >> 操作符,读入 crack.dat 的内容,并且是作为一个整数数据读入的。按 F8 执行上面代码,进入下面 keyfile 验证循环,如下图所示:
16.png
keyfile 的算法也简单,就是对 Crackme 的执行程序进行累加和的计算并与 crack.dat 的内容进行对比。
如上图所示,[ebp-168] 中是 crack.dat 内容 78787878 ( 十六进制 0x04B23526)。ESI 中是 crackme 计算的累加校验和0xD80A721A,转成 10 进制为 3624563226。这个 3624563226 就是 crack.dat 的正确内容了。
累加校验和的计算方式为:sum = ∑[crackme[ i ] xor (i+1)],其中,i=0,1,2,...n-1,n 为文件长度。如下图所示:
18.png
具体代码如下:
[Asm] 纯文本查看 复制代码
00401223  |.  EB 2E                    jmp     short 00401253
00401225  |>  8D8D 97FEFFFF            /lea     ecx, dword ptr [ebp-169]               ;  char buff
0040122B  |.  51                       |push    ecx
0040122C  |.  8D85 14FFFFFF            |lea     eax, dword ptr [ebp-EC]                ;  stream_v130 内部成员:输入流, stream_v130.istream
00401232  |.  50                       |push    eax
00401233  |.  E8 24620000              |call    istream::get                           ;  从流(CrackMe自身文件)中读取取一个字节至[ebp-169](istream::get(char &)),流指针后移一字节
00401238  |.  83C4 08                  |add     esp, 8
0040123B  |.  8D95 14FFFFFF            |lea     edx, dword ptr [ebp-EC]
00401241  |.  52                       |push    edx
00401242  |.  E8 9D620000              |call    istream::tellg                         ;  取流的读取指针位置, eax
00401247  |.  59                       |pop     ecx                                    ;  eax == 1....
00401248  |.  0FBE8D 97FEFFFF          |movsx   ecx, byte ptr [ebp-169]                ;  ecx == CRACKME[i], i=0....,最终 ecx = 0xFFFFFFFF, [EBP-169] == 0xFF, 当Eof时返回-1
0040124F  |.  33C1                     |xor     eax, ecx
00401251  |.  03F0                     |add     esi, eax                               ;  sum += (tellg() ^ CRACKME[i]), 最终 sum = 0xD80A721A
00401253  |>  8B85 D0FEFFFF             mov     eax, dword ptr [ebp-130]               ;  eax ===> stream_v130
00401259  |.  F640 0C 01               |test    byte ptr [eax+C], 1                    ;  std::basic_ios::eof(), 最终 [eax+c] == 0x00000003, Badbit | Eofbit
0040125D  |.^ 74 C6                    \je      short 00401225
0040125F  |.  8D95 D0FEFFFF            lea     edx, dword ptr [ebp-130]
00401265  |.  52                       push    edx
00401266  |.  E8 39580000              call    fstreambase::close                      ;  stream_v130.close()
0040126B  |.  59                       pop     ecx
0040126C  |.  3BB5 98FEFFFF            cmp     esi, dword ptr [ebp-168]                ;  sum < 0x04B23526 (78787878)Crack.dat 文件保存的数字
00401272  |.  72 08                    jb      short 0040127C                          ;  低于跳转, esi = D80A721A = 3624563226 不小于 78787878,不跳转
00401274  |.  3BB5 98FEFFFF            cmp     esi, dword ptr [ebp-168]                ;  sum <= 0x04B23526 (78787878)
0040127A  |.  76 6B                    jbe     short 004012E7                          ;  不高于跳转,因此 Crack.dat 的文件内容为 3624563226 才会跳转到下一步验证,否则退出


如果文件内容校验不对,则输出如下图所示:
15.png

显示文件不正确的提示。重新将 crack.dat 的内容改成 3624563226,如下图所示:
17.png
重新保存 crack.dat,再次运行到比较累加校验码的位置,如下图所示:
19.png
这次就是正确的了。所以 keyfile 文件 crack.dat 的内容就是 3624563226 了。


如果 keyfile 验证通过,则会进入第二个验证,用户名/机构名/序列号 的验证。如下图所示:
20.png
首先要求输入用户名称。
如下图所示,第二步验证,需要输入用户名,机构名,序列号等三条数据。
30.png
我们在命令行按要求输入相关信息,如下图所示,序列号先随便输入:
31.png
输入完后回车,就会进入序列号的验证,如下图所示:
32.png
首先判断用户名,机构名的长度,长度都必须大于3,否则会将序列号置”0“,因此,验证也会失败。


长度没有问题,则调用 call    dword ptr [ebp-154] (call 00401457)进行序列号验证,该验证函数如下图所示:
33.png
最后,输入的序列号(整数)与计算后的序列号进行比较,如下相等则表示序列号正确。而序列号的计算值只与用户名和机构名的第1个字符有关,其它字符没有参与序列号的计算。计算的具体代码如下:
[Asm] 纯文本查看 复制代码
0040146A  |.  33FF                     xor     edi, edi                                ;  int sum = 0;
0040146C  |.  EB 17                    jmp     short 00401485
0040146E  |>  0FBE06                   /movsx   eax, byte ptr [esi]                    ;  int a = (int)(* pName);
00401471  |.  0FBE13                   |movsx   edx, byte ptr [ebx]                    ;  int d = (int)(* pOrganization);
00401474  |.  F7EA                     |imul    edx
00401476  |.  03F8                     |add     edi, eax                               ;  sum += a * d;
00401478  |.  53                       |push    ebx                                    ; /pOrganization ===> "ite123"
00401479  |.  E8 AE0A0000              |call    strlen                                 ; \eax = strlen(pOrganization)
0040147E  |.  59                       |pop     ecx
0040147F  |.  85C0                     |test    eax, eax                               ;  无用检查
00401481  |.  FE03                     |inc     byte ptr [ebx]                         ;  Organization[0] ++, 第1个字符的 ASCII 码值加 1
00401483  |.  FE06                     |inc     byte ptr [esi]                         ;  Name[0] ++, 第1个字符的 ASCII 码值加 1
00401485  |>  56                        push    esi                                    ; /pName ===> "solly"
00401486  |.  E8 A10A0000              |call    strlen                                 ; \eax = strlen(pName)
0040148B  |.  59                       |pop     ecx
0040148C  |.  85C0                     |test    eax, eax                               ;  当 Name[0] 递增到 0xFF,再加1溢出为0x00即会退出循环
0040148E  |.^ 75 DE                    \jnz     short 0040146E
00401490  |.  FF75 10                  push    dword ptr [ebp+10]                      ; /pSerial = 0x04B23526 (78787878)
00401493  |.  57                       push    edi                                     ; |calcSerial = 0x0009E501 (648449)
00401494  |.  E8 08000000              call    checkfunc                               ; \thecodin.checkfunc


由上图可以看到,最后是调用 call checkFunc 进行序列号比较的,该函数如下图所示:
34.png

就是一个 cmp 指令进行整数比较,相等则显示成功,不相等则显示失败。由上图可以看到,正确的序列号为:0x0009E501(十进制为 648449)。
上图序列不正确,最后显示如下:
35.png
表示序列号不正确。


我们重新运行 Crackme,输入正确的序列号,如下图所示:
36.png
这次输出如下,表示序列号正确:
37.png


另外,这里説明一下,crackme是由 borland c++ 编译的。其在读取文件流时,当到达 eof 时,其 get(char& ch) 取得 ch 为 -1,并且 tellg() 取值还是文件长度,而我用 Dev-C++ 时进行累加校验和计算时,当到达 eof 时,其 get(char& ch) 取得 ch = 0,并且 tellg() 取值是 -1,不再是文件长度。所以,如果用当前的C++编译器编译的程序计算的校验值不对时,可能是这个问题引起的,需要进行修正,修正方式如下:
[C++] 纯文本查看 复制代码
        //// Dev-C++ 下的修正 
        crc = crc - (0 ^ (-1)) + ((-1) ^ 96879);

这个在下面的注册机中有体现,注册机源码如下。
[C++] 纯文本查看 复制代码
#include <iostream>
#include <fstream>
#include <string>
#include <string.h>

using namespace std;

int getCrcFile(string filename);
int getSN(char * name, char * organ);

int main(int argc, char** argv) {
        
        string filename = "I:\\Downloads\\crack\\157_thecodingone.2\\thecodingone.2.exe";
        
        int crc = getCrcFile(filename);
        
        cout<<"File CRC: "<<(unsigned)crc<<endl;
        
        char pName[] = "solly";
        char pOrgan[] = "ite123";
        
        int sn = getSN(pName, pOrgan);
        cout<<"SN: "<<sn<<endl;
        
        return 0;
}

// file-size: 96879, crc: 3624563226
int getCrcFile(string filename) {
        int crc = 0;
        fstream fs(NULL);
        fs.open(filename.c_str(), ios::in | ios::binary);
        
        char c;
        int i=0;
        do {
                fs.get(c);
                int p = fs.tellg();
                if(++i>=96878) {
                        cout<<"c = "<<(int)c<<", p = "<<p<<endl;
                }
                crc += p ^ (int)c; 
        } while(!fs.eof());
        
        /// Dev-C++ 下的修正 
        crc = crc - (0 ^ (-1)) + ((-1) ^ 96879);
        
        fs.close();
        
        return crc;
}

int getSN(char * name, char * organ) {
        if((strlen(name) < 3) || (strlen(organ) < 3)) {
                cout<<"Length of name and organ must be more than 3."<<endl;
                
                return -1;
        }
        char a = name[0];
        char b = organ[0];
        int sn = 0;
        while(a!=0) {
                sn += (int)a * (int)b;
                a++, b++;
        }
        
        return sn;
}


以上代码由 dev-c++ 调试通过。


内容简单,分析完毕!!!

免费评分

参与人数 7威望 +1 吾爱币 +17 热心值 +7 收起 理由
pk8900 + 3 + 1 用心讨论,共获提升!
天空藍 + 1 + 1 粉絲拜讀
庞晓晓 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
陈世界 + 1 + 1 我很赞同!
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
csjwaman + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
朱朱你堕落了 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

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

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

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

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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