160 Crackme 之 157 -- thecodingone.2 的跟踪分析及注册机实现
本帖最后由 solly 于 2019-12-20 18:18 编辑160 之 157 - thecodingone.2 是一个命令行形式的 Crackme,通过命令行交互方式输入注册信息,并输出结果。
首先看看文件信息:
节的信息如下:
可以看到,这是一个由 Borland C++ 编译的 Crackme,没有加壳,因此跟踪分析也比较容易。
首先运行一下 Crackme,输出如下图:
需要一个 keyfile 文件验证,我们用 OD 载入 Crackme 程序来分析。载入后如下图所示:
可以看到,这一个调用 C++ 库写成的 Crackme,一开始 Crackme 创建了两个文件流(fstream)。并对流进行初始化,如下图所示:
初始化了两个流,文件名为空(NULL)。按 F8 执行,执行到下图所示位置:
可以看出,Crackme 将自身执行文件作为文件流打开了。同时,还需要打开另一个文件 crack.dat ,这个文件就是 Keyfile,目前不存在,我们先创建一个这样的文件,文件内容随意填,如下图所示:
用本文编辑器生成一个 crack.dat,输入一些内容并保存。
如上图所示,crackme 通过 >> 操作符,读入 crack.dat 的内容,并且是作为一个整数数据读入的。按 F8 执行上面代码,进入下面 keyfile 验证循环,如下图所示:
keyfile 的算法也简单,就是对 Crackme 的执行程序进行累加和的计算并与 crack.dat 的内容进行对比。
如上图所示, 中是 crack.dat 内容 78787878 ( 十六进制 0x04B23526)。ESI 中是 crackme 计算的累加校验和0xD80A721A,转成 10 进制为 3624563226。这个 3624563226 就是 crack.dat 的正确内容了。
累加校验和的计算方式为:sum = ∑ xor (i+1)],其中,i=0,1,2,...n-1,n 为文件长度。如下图所示:
具体代码如下:
00401223|.EB 2E jmp short 00401253
00401225|>8D8D 97FEFFFF /lea ecx, dword ptr ;char buff
0040122B|.51 |push ecx
0040122C|.8D85 14FFFFFF |lea eax, dword ptr ;stream_v130 内部成员:输入流, stream_v130.istream
00401232|.50 |push eax
00401233|.E8 24620000 |call istream::get ;从流(CrackMe自身文件)中读取取一个字节至(istream::get(char &)),流指针后移一字节
00401238|.83C4 08 |add esp, 8
0040123B|.8D95 14FFFFFF |lea edx, dword ptr
00401241|.52 |push edx
00401242|.E8 9D620000 |call istream::tellg ;取流的读取指针位置, eax
00401247|.59 |pop ecx ;eax == 1....
00401248|.0FBE8D 97FEFFFF |movsx ecx, byte ptr ;ecx == CRACKME, i=0....,最终 ecx = 0xFFFFFFFF, == 0xFF, 当Eof时返回-1
0040124F|.33C1 |xor eax, ecx
00401251|.03F0 |add esi, eax ;sum += (tellg() ^ CRACKME), 最终 sum = 0xD80A721A
00401253|>8B85 D0FEFFFF mov eax, dword ptr ;eax ===> stream_v130
00401259|.F640 0C 01 |test byte ptr , 1 ;std::basic_ios::eof(), 最终 == 0x00000003, Badbit | Eofbit
0040125D|.^ 74 C6 \je short 00401225
0040125F|.8D95 D0FEFFFF lea edx, dword ptr
00401265|.52 push edx
00401266|.E8 39580000 call fstreambase::close ;stream_v130.close()
0040126B|.59 pop ecx
0040126C|.3BB5 98FEFFFF cmp esi, dword ptr ;sum < 0x04B23526 (78787878)Crack.dat 文件保存的数字
00401272|.72 08 jb short 0040127C ;低于跳转, esi = D80A721A = 3624563226 不小于 78787878,不跳转
00401274|.3BB5 98FEFFFF cmp esi, dword ptr ;sum <= 0x04B23526 (78787878)
0040127A|.76 6B jbe short 004012E7 ;不高于跳转,因此 Crack.dat 的文件内容为 3624563226 才会跳转到下一步验证,否则退出
如果文件内容校验不对,则输出如下图所示:
显示文件不正确的提示。重新将 crack.dat 的内容改成 3624563226,如下图所示:
重新保存 crack.dat,再次运行到比较累加校验码的位置,如下图所示:
这次就是正确的了。所以 keyfile 文件 crack.dat 的内容就是 3624563226 了。
如果 keyfile 验证通过,则会进入第二个验证,用户名/机构名/序列号 的验证。如下图所示:
首先要求输入用户名称。
如下图所示,第二步验证,需要输入用户名,机构名,序列号等三条数据。
我们在命令行按要求输入相关信息,如下图所示,序列号先随便输入:
输入完后回车,就会进入序列号的验证,如下图所示:
首先判断用户名,机构名的长度,长度都必须大于3,否则会将序列号置”0“,因此,验证也会失败。
长度没有问题,则调用 call dword ptr (call 00401457)进行序列号验证,该验证函数如下图所示:
最后,输入的序列号(整数)与计算后的序列号进行比较,如下相等则表示序列号正确。而序列号的计算值只与用户名和机构名的第1个字符有关,其它字符没有参与序列号的计算。计算的具体代码如下:
0040146A|.33FF xor edi, edi ;int sum = 0;
0040146C|.EB 17 jmp short 00401485
0040146E|>0FBE06 /movsx eax, byte ptr ;int a = (int)(* pName);
00401471|.0FBE13 |movsx edx, byte ptr ;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 ;Organization ++, 第1个字符的 ASCII 码值加 1
00401483|.FE06 |inc byte ptr ;Name ++, 第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 递增到 0xFF,再加1溢出为0x00即会退出循环
0040148E|.^ 75 DE \jnz short 0040146E
00401490|.FF75 10 push dword ptr ; /pSerial = 0x04B23526 (78787878)
00401493|.57 push edi ; |calcSerial = 0x0009E501 (648449)
00401494|.E8 08000000 call checkfunc ; \thecodin.checkfunc
由上图可以看到,最后是调用 call checkFunc 进行序列号比较的,该函数如下图所示:
就是一个 cmp 指令进行整数比较,相等则显示成功,不相等则显示失败。由上图可以看到,正确的序列号为:0x0009E501(十进制为 648449)。
上图序列不正确,最后显示如下:
表示序列号不正确。
我们重新运行 Crackme,输入正确的序列号,如下图所示:
这次输出如下,表示序列号正确:
另外,这里説明一下,crackme是由 borland c++ 编译的。其在读取文件流时,当到达 eof 时,其 get(char& ch) 取得 ch 为 -1,并且 tellg() 取值还是文件长度,而我用 Dev-C++ 时进行累加校验和计算时,当到达 eof 时,其 get(char& ch) 取得 ch = 0,并且 tellg() 取值是 -1,不再是文件长度。所以,如果用当前的C++编译器编译的程序计算的校验值不对时,可能是这个问题引起的,需要进行修正,修正方式如下: //// Dev-C++ 下的修正
crc = crc - (0 ^ (-1)) + ((-1) ^ 96879);
这个在下面的注册机中有体现,注册机源码如下。
#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;
char b = organ;
int sn = 0;
while(a!=0) {
sn += (int)a * (int)b;
a++, b++;
}
return sn;
}
以上代码由 dev-c++ 调试通过。
内容简单,分析完毕!!!
页:
[1]