逆向分析 FSViewer 并写出注册机本文详细描述了对 FSViewer7 5 版本的逆向分析过程 涉及 Delphi 编写的程序 重点剖析了其注册流程 包括验证函数的加密逻辑 如 sha1 sha512 Blowfish 和 Idea 算法的应用
大家好,欢迎来到IT知识分享网。
最近在整理之前的资料, 发现了一篇几年前刚学逆向那会儿写的文章, 是跟着看雪一位大牛的文章做的, 但逆向方法不同, 应该算是自己能逆向出算法的第一款软件了, 发出来纪念一下
借鉴了看雪 @深山修行之人 大牛的文章: FastStone Image Viewer注册算法分析+KeyGen

对于Delphi程序,自然要使用针对delphi的大杀器: IDR(Interactive Delphi Reconstructor)
通过IDR找到注册按钮被点击事件:

可知: btnRegisterCliced : 0x72F248
虽然IDR可以识别Delphi的大部分函数,但是只有反汇编,如果要看流程图和反编译还是需要ida
而IDR提供了导出map和idc文件的功能,所以用IDR导出map和idc文件分别供调试器和ida使用:
- 跑完idc脚本后ida效果还是可以的(需要4-5分钟)
- 这里我使用x32dbg加载map文件进行动态调试(OD的LoadMapEx总是崩溃,可以在xp下使用)
Delphi遵循_fastcall调用约定,但是与Windows的_fastcall略有不同,参数顺序为eax为第一个参数、edx为第二个参数、ecx为第三个参数,大于3个的参数通过堆栈传递,大于三个的堆栈顺序从左到右依次压栈,堆栈由被调用者恢复
整体的注册逻辑流程很简单,而且关键点在验证函数,所以不再赘述,这里贴出我分析出的伪C代码:
|
1
2
3
4
5
6
7
8
9
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
|
void OnBtnRegisterClicked(void *this)
{
String userName = Trim(TControl_GetText(edit)); // [ebp-0x18]->[ebp-0x4]
String regiCode = Trim(TControl_GetText(edit)); // [ebp-0x20]->[ebp-0x1C]
String upperCode = upperCase(regiCode); // [ebp-0xC]
String trueCode = NULL; // [ebp-0x10]
if((LStrLen(upperCode)-1) >= 0)
{
int len = LStrLen(upperCode); // [ebp-0x14]
for(int i = 0; i < len; i++)
{
if (upperCode[i] >= 0x41 && upperCode[i] <= 0x5A) // in [A,Z]
{
String toCat = NULL; // [ebp-0x24]
LStrFromChar(toCat, upperCode[i]);
LStrCat(trueCode, toCat);
}
if (LStrLen(trueCode) == 5 || 11 || 17)
LStrCat(trueCode, "-");
}
}
String cpTrueCode = NULL; // [ebp-0x8]
LStrLAsg(cpTrueCode, trueCode); // StrCopy
/*
userName = Viking
trueCode = ABCDE-FGHIJ-KLMNO-PQRST
*/
if (userName != NULL)
{
if ((sub_72E770(this, userName, cpTrueCode, 0) &&
sub_72EBFC(this, userName, cpTrueCode, 0))
||
(sub_72E770(this, userName, cpTrueCode, 1) &&
sub_72EBFC(this, userName, cpTrueCode, 1)))
{
int res = GetLicenseType(cpTrueCode); // sub_72F030
if (res > 1)
{
if (res == 4999)
MessageBox("Corporate Site");
else if (res >= 5000)
MessageBox("Corporate Worldwide");
else
MessageBox("Multiple User");
}
else
MessageBox("Singel User");
}
else
MessageBox("无效用户名或注册码");
}
else
MessageBox("用户名为空");
return 0;
}
|
总体流程就是:
注册码为20个字母, xxxxx-xxxxx-xxxxx-xxxxx五个一组, 只有纯字母
将用户名和注册码传入sub_72E770和sub_72EBFC这两个验证函数
只要sub_verify1(…, 0) && sub_verify2(…, 0)
或者sub_verify1(…, 1) && sub_verify2(…, 1)有一个分支成立即可





|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
if (flag)
{
//0x72E99C
String toCat[2] = { 0,}; // [ebp-0x54,0x58]
WStrFromLStr(toCat[0], cp8Code);
WStrFromLStr(toCat[1], n8Str, "96888", toCat[0]);
String cp8N8Str = NULL; // [ebp-0x50]
WStrCatN(cp8N8Str, 3, toCat[1]);
String cp8N8Str1 = NULL; // [ebp-0x4C]
LStrFromWStr(cp8N8Str1, cp8N8Str);
DCPcrypt2.encrypt(VMT_723D04_TDCP_sha512, cp8N8Str1);// 0x71FB90
}
else
{
String toCat[2] = { 0,}; // [ebp-0x64,0x68]
WStrFromLStr(toCat[0], cp8Code);
WStrFromLStr(toCat[1], n8Str, "96332", toCat[0]); // ??
String cp8N8Str = NULL; // [ebp-0x60]
WStrCatN(cp8N8Str, 3, toCat[1]);
// result: cp8N8Str = cp8Code+96332+n8Str
// ABCDEFGH96332VAIBKCIDNEGFGH
String cp8N8Str1 = NULL; // [ebp-0x5C]
LStrFromWStr(cp8N8Str1, cp8N8Str);
DCPcrypt2.encrypt(VMT_723D04_TDCP_sha512, cp8N8Str1);// 0x71FB90
}
|
|
1
2
3
4
5
6
7
|
String encodeStr1 = NULL; // [ebp-0x20]
DCPcrypt2.sub_71FEB4(Base64, n8Str, encodeStr1);
// mzvoPqb8etggqNJ9TqI=
String encodeStr2 = NULL; // [ebp-0x6C]
DCPcrypt2.sub_71FEB4(Base64, encodeStr1, encodeStr2);
// 4hQ99VfA1SHNNrjvHQv78MSew2Q=
LStrLAsg(encodeStr1, encodeStr2); // copy
|

3.1.1 分析sub_71FB90函数

|
1
2
3
4
5
|
BASE64 table :: 0058677C :: 00987B7C
BLOWFISH [sbox] :: 005810C8 :: 009824C8
SHA1 [Compress] :: 003228C5 :: 007234C5
SHA-512 [init] :: 0032D559 :: 0072E159
IDEA
|







总体流程即:
用拼接字符串的sha1 hash值作为密钥初始化blowfish的s,p_box,再调用bf主加密函数加密8个0
(伪C代码:)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// ebx = arg1, [ebp-4] = arg2, [ebp-8] = arg3
void sub_0071FB90(void *arg1, String encodeStr, void *vtable)
{
if (arg1[48])
DCPblowfish.sub_7210D0(arg1, *arg); // init
int ret = 0;
if ( (ret = DCPsha1.sub_()) < 0) // 恒为160
ret += 7;
ret = ret >> 3; // 算数右移 = 20
BYTE *shaMem = GetMem(ret); // malloc // esi
TComponent.Create(1, arg1);
DCPsha1.initABCDE(shaMem); //sub_723A04();初始化链接变量ABCDE
// 进行sha1
DCPcrypt2.sub_71FA88(shaMem, encodeStr);//->DCPsha1.sub_723A7 (encodeStr);
DCPsha1.sub_723B24(shaMem);// 最终sha1,结果在mem
TObject.Free(...);
ret = DCPblowfish.sub_720E28(); // 恒为448
ret1 = DCPsha1.sub_(); // 恒为160
if (ret < ret1) // 恒不会进入的分支
{
...
}
DCPblockcipher.sub_(arg1, shaMen, 160);// 初始化p,s_box
Bf_fn();
}
|
同样的,在if分支里调用的sub_71FB90函数和上面的流程一样,但是传入的虚表变了,是用sha512 hash值作为密钥初始化idea算法
3.1.2 分析sub_71FEB4函数



|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
0x71FEFE| call dword ptr ds:[esi+7C] | [esi+7C]:DCPblockciphers.sub_007205D8_007205D8
arg1@eax = this/vtable , arg2@edx = "VA.."待异或字符串 , arg3 = 0xA6FDB18 结果输出
{
// [ebp-4] = arg3@ecx
// [ebp-8] = arg2@edx
for (int i = 0; i < len(arg2); ++i)
{
BYTE *ret = ...;
BYTE var[8] = {全0数据加密后的8bytes};
DCPblowfish.sub_(this, var, result);
BYTE tmp = result[0]^arg2[i];
ret[i] = tmp;
move(var[0], var[1], 7);
var[7] = tmp;
}
}
|
|
1
2
3
4
5
6
7
|
String encodeStr1 = NULL; // [ebp-0x20]
DCPcrypt2.sub_71FEB4(Base64, n8Str, encodeStr1);// blowfis+xor+base64
// mzvoPqb8etggqNJ9TqI=
String encodeStr2 = NULL; // [ebp-0x6C]
DCPcrypt2.sub_71FEB4(Base64, encodeStr1, encodeStr2);// ide+xor+base64
// 4hQ99VfA1SHNNrjvHQv78MSew2Q=
LStrLAsg(encodeStr1, encodeStr2); // copy
|
sub_72EBFC与sub_72E770非常相似:
|
1
2
3
4
5
6
7
8
9
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
|
//int func@<eax> (String @<edx>, String @<ecx>, int @<stack>)
int sub_72EBFC(String userName, String trueCode, int flag)
{
/*
[ebp-0x4] = this
[ebp-0x8] = userName
[ebp-0xC] = trueCode
*/
LStrAddRef(userName);
LStrAddRef(trueCode); // 增加字符串引用计数
....
//与第一种加密函数相同
....
//sha512摘要作为密钥初始化blowfish
DCPcrypt2.encrypt(VMT_sha512, "09834...", vtable);// 0x71FB90
if (flag)
{
// 0x72EDD7
String code = 拼接注册码前8个字符+96888+交替码
DCPcrypt2.encrypt(VMT_sha1, code);// 0x71FB90
}
else
{
// 0x72EE31
String code = 拼接注册码前8个字符+96332+交替码
DCPcrypt2.encrypt(VMT_sha1, code);// 0x71FB90
// sha1摘要作为密钥初始化idea
}// 与第一种相同
String ecode = name & code 交错;
int count = regiCode[0] - 0x32;
if (cout >= 0)
{
cout += 1;
for (int i = 0; i < count; ++i)
{
DCPcrypt2.sub_71FEB4(this, ecode, resIdea);// idea+xor+base64
}
}
DCPcrypt2.sub_71FEB4(this, resIdea, resBlfh);// blowfish+xor+base64
String final4code = 从resBlfh里取前4个大写字母
String codeCmpStr = NULL; // [ebp-0x74]
LStrCopy(tuTrueCode, 17, 4, codeCmpStr); //(最后四个字符) QRST
if (!LStrCmp(final4code, codeCmpStr)) // 相等
return 1;
else
return 0;
}
|
当验证函数通过后就会调用sub_72F030函数获取注册类型

|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
int sub_72F030(this, cpTrueCode)
{
/*
[ebp-4] = arg2
*/
String regiCode = cpTrueCode; // [ebp-4]
regiCode = Trim(UpperCase(trueCode)); // [ebp-0x4]->[ebp-0x14]- [ebp-0x10]
if (LStrLen(regiCode) == 23)
{
regiCode = regiCode去掉横线; // [ebp-0x10]
}
if (LStrLen(regiCode) == 20)
{
/*
String toCat[] = { 0,}; // [ebp-0x2C]
LStrCopy(regiCode, 4, 1, toCat[0]);
IntToStr(toCat[0]-0x4D, result);
*/
取注册码的第4、8、6、2位的字符
得到[3]-77,[7]-68,[5]-73,[1]-79拼接而成的字符串
return 将字符串转为int;
}
}
|
注册类型见 2. 分析注册流程 伪代码部分
至此流程分析完毕,注册机按注册流程写一遍就出来了,详细代码请见附件, 逆向使用的FSViewer75版本也见附件


免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。
本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/117569.html