吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3523|回复: 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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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++] 纯文本查看 复制代码
1
2
//// Dev-C++ 下的修正
crc = crc - (0 ^ (-1)) + ((-1) ^ 96879);

这个在下面的注册机中有体现,注册机源码如下。
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#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 感谢发布原创作品,吾爱破解论坛因你更精彩!
bypasshwid + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

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

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

本版积分规则

返回列表

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

GMT+8, 2025-5-22 22:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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