[新春礼物]_0_1_0_Editor 注册算法分析及全部版本注册机、网络在线验证分析和验证模拟
外面疫情严重,窝家里没事,对论坛中下手较多的 _010_Editor 进行完整和彻底的注册算法分析。通过分析和测试,其所有版本的算法是一样的(经测试,Windows 下从 v2.1.4 到 v10.0.0 的 x86 及 x64 版本是通用的,其它平台,如 MacOS 和 Linux,没有测试)。
下面以 v10.0.0 x86 版本为例进行分析。
惯例,首先看看文件信息:
是由 VS + QT5 编译而来的,没有加壳,是分析算法的良心软件。节信息如下:
没有什么不良信息,一切正常。
首先进入软件看看,启动该软件,界面如下:
这个配色,有点骚,进行注册看看(菜单 “Tools”->"Register"进入),输入注册假码数据:
点击 “Check License”,看到如下提示信息:
很良心地提示你下手的地点和方法了。
用 OD 载入软件,显示如下:
基于 QT 框架编译的标准模式,也没什么特别的地方。在 OD 中直接搜索字符串,定位 ”invalid name or password.“,如下图所示:
双击该字符串,来到引用其的代码处,如下图所示:
再在该代码指令上右键菜单,可以看到,有另一条指令跳转到这里,我们”转到“菜单,跳转到该指令,如下图所示:
可以看到,这里有3个比较,检查注册结果。上面(0x0123BA36,可能地址不一样,但应该也是 0xnnnnBA36形式,最高2字节可能不同)有一个 jmp 指令,在其下一条指令处右键”转到“,如下图所示:
可以看到,这里也是从另一指令跳转而来的,同样方法,跳转上一条指令,如下图所示:
这里又是一条比较指令(cmp edi, 0xDB),先説一下,这个0xDB是成功注册的标志。
再一次在这一条指令上右键”转到“,如下图所示:
可以看到有两个地方的指令跳转到这里了。我们先不跳转,再往上看,又有一条JMP 指令(JMP 0x0123BBD9),我们在这条指令的下面一条指令上右键”转到“,如下图所示:
可以看到,也有一条指令跳转到这里,这里跳转过去,有一条JNZ指令,如下图所示:
同样方法继续回溯,就会来到下面位置:
[Asm] 纯文本查看 复制代码
这里就是注册算法调用处了,如上图所示,这几行代码就是调用注册算法的:016FB7D3 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第三次引用[0371805C]
016FB7D9 . 68 3C470000
push
0x473C
; 18236
016FB7DE . 6A 0B
push
0xB
; 11
016FB7E0 . E8 07F350FF
call
00C0AAEC
; 注册码检查
016FB7E5 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第四次引用[0371805C]
016FB7EB . 8BD8
mov
ebx
,
eax
; 注册标志 ebx == eax
016FB7ED . 68 3C470000
push
0x473C
; 18236
016FB7F2 . 6A 0B
push
0xB
; 11
016FB7F4 . E8 5FE650FF
call
00C09E58
; 注册码检查 及 试用期扩充的检查
016FB7F9 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第五次引用[0371805C]
016FB7FF . 8BF8
mov
edi
,
eax
; 版本标识 edi == eax
016FB801 . 81FB E7000000
cmp
ebx
, 0xE7
; 成功标志3
016FB807 . 0F84 F3000000
je
016FB900
016FB80D . 8379 2C 00
cmp
dword
ptr
[
ecx
+0x2C], 0x0
; 成功标志4,与3二选一, ds:[0384547C]
016FB811 . 0F84 E9000000
je
016FB900
注意:前面説了,由于系统重定位的原因,每次运行的指令地址和数据地址的高2字节是变化的,从上面代码与截图的对比就可以看出来,因此我们只看低2字节的偏移量即可。
另外,下面的位置也调用了注册算法函数,我们前面也看过这个位置了,如下图所示:
[Asm] 纯文本查看 复制代码
具体调用代码如下:016FB8E6 > \8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 全部检查通过来到这里
016FB8EC . 68 3C470000
push
0x473C
; 18236
016FB8F1 . 6A 0B
push
0xB
; 11
016FB8F3 . E8 60E550FF
call
00C09E58
016FB8F8 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
016FB8FE . 8BF8
mov
edi
,
eax
016FB900 > 81FF DB000000
cmp
edi
, 0xDB
; 成功标志2,edi==0xDB
016FB906 . 0F85 2C010000
jnz
016FBA38
; 不相等则去试用期过期检查
下面我们开始跟踪其注册算法,先在这个位置下一个断点,如下图所示:
在 0xnnnnB7A7 位置下一个断点。
按 F9 运行该软件,在注册界面,输入注册码假码信息,如下图所示:
在上图中也可以看到目前的注册状态:Evaluation Version - 30 Days Left。点 ”Check License“ 按钮,OD 马上断了下来,如下图所示:
[Asm] 纯文本查看 复制代码
这一段代码如下所示:016FB7A7 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第一次引用[0371805C]
016FB7AD . 50
push
eax
; [eax] ===> 用户名
016FB7AE . C645 FC 08
mov
byte
ptr
[
ebp
-0x4], 0x8
; VC的资源计数
016FB7B2 . E8 F8E450FF
call
00C09CAF
; 检查名字是否由单、双引号包含,有则去除单、双引号
016FB7B7 . 8D4D D8
lea
ecx
,
dword
ptr
[
ebp
-0x28]
; [ecx] ===> 用户名
016FB7BA . C645 FC 01
mov
byte
ptr
[
ebp
-0x4], 0x1
016FB7BE . FF15 B094BD03
call
dword
ptr
[<&Qt5Core.QString::~QS>; 用户名字符串的引用计数减 1,为0时就释放内存
016FB7C4 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第二次引用[0371805C]
016FB7CA . 8D45 E8
lea
eax
,
dword
ptr
[
ebp
-0x18]
016FB7CD . 50
push
eax
016FB7CE . E8 B07550FF
call
00C02D83
; 将注册码中的大写'O'和小写'o'换成数 字'0',将小写字母'l'换成数 字'1'
016FB7D3 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第三次引用[0371805C]
016FB7D9 . 68 3C470000
push
0x473C
; 18236
016FB7DE . 6A 0B
push
0xB
; 11
016FB7E0 . E8 07F350FF
call
00C0AAEC
; 注册码检查
016FB7E5 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第四次引用[0371805C]
016FB7EB . 8BD8
mov
ebx
,
eax
; 注册标志 ebx == eax
016FB7ED . 68 3C470000
push
0x473C
; 18236
016FB7F2 . 6A 0B
push
0xB
; 11
016FB7F4 . E8 5FE650FF
call
00C09E58
; 注册码检查 及 试用期扩充的检查
016FB7F9 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第五次引用[0371805C]
016FB7FF . 8BF8
mov
edi
,
eax
; 版本标识 edi == eax
016FB801 . 81FB E7000000
cmp
ebx
, 0xE7
; 成功标志3
016FB807 . 0F84 F3000000
je
016FB900
016FB80D . 8379 2C 00
cmp
dword
ptr
[
ecx
+0x2C], 0x0
; 成功标志4,与3二选一, ds:[0384547C]
016FB811 . 0F84 E9000000
je
016FB900
016FB817 . 6A 00
push
0x0
016FB819 . 8BCE
mov
ecx
,
esi
016FB81B . E8 BB6D50FF
call
00C025DB
016FB820 . 85C0
test
eax
,
eax
016FB822 . 0F89 8C000000
jns
016FB8B4
; eax非负,需要再次判断(时长)
[/
size
][
size
=4]
按 F8 运行到如下位置:
这个调用就是调用注册算法函数。不过,所有的 call 调用,都是调用一个 JMP 指令,通过JMP指令再跳转到相应的函数。如下图所示,就是注册算法函数,但是其开始地址并不是 call 后面的地址。
按 F7 进入注册算法函数。
该函数结尾如下图所示,有三种退出方式:
我们回到函数前面,如下图所示:
可以看到,最下面有一个调用(call 00749D0E),这是一个对注册格式进行检查的函数,先 F8 执行到该调用,再按 F7 进入调用,如下图所示:
首先检查长度,并进行内码转换,由 Unicode 码转换成 utf-8 编码。转换结果如下图OD数据区所示:
[C++] 纯文本查看 复制代码 [Asm] 纯文本查看 复制代码
下面説一下 QT 字符串(QString)在内存中保存的格式,定义如下:struct
QString {
ULONG
Reference_Count;
ULONG
Length;
ULONG
Capacity;
ULONG
Unknow;
//// 附加属性,总是 0x10,包括 isRightToLeft 等
char
Buffer[16];
};
从上图中 OD 的数据区可以看到,保存的注册码数据存贮格式:引用次数为 1,长度为 0x18,字符串容量为 0x19,其它属性为 0x00000010,内容为 "1111-2222-3333-4444-5555"。
下面是注册码以 Unicode 格式保存时的存贮格式:06D45538 01 00 00 00 18 00 00 00 38 00 00 00 10 00 00 00 ......8......
06D45548 31 00 31 00 31 00 31 00 2D 00 32 00 32 00 32 00 1.1.1.1.-.2.2.2.
06D45558 32 00 2D 00 33 00 33 00 33 00 33 00 2D 00 34 00 2.-.3.3.3.3.-.4.
06D45568 34 00 34 00 34 00 2D 00 35 00 35 00 35 00 35 00 4.4.4.-.5.5.5.5.
06D45578 00 00 AD BA 0D F0 AD BA ...瓠?瓠?
函数再检查注册码的”-“号分隔符是否符合要求,并调用 call 0xnnnn984A 函数,将大写”O“,小写”o“字母转换成数 字”0“,将小写”l“字母转换成数 字”1“,然后将字母按 36 进制(0~9,A~Z)转换成数 字。调用完成后,将这此转换后成数 字组合成一个字节数组 。
从该函数返回后,就会将字符串注册码全部转换并组成一个字节数组了,如下图OD的数据区所示:
然后就从注册码字节数组(sn_int[])中取出 sn_int[3],这个字节标示的是注册类型,有3种类型,如下所示:
1、0x9C,这个是没有免费技术支持,没有免费升级的注册类型,注册码中有版本号检查,必须大于当前软件的版本,否则提示注册码过期。注册码只有4段。
2、0xAC,这个是有免费技术支持、有免费升级的完整注册类型,不过会检查注册码中的过期时间(天数,从发布时间算起),这些免费支持是有期限的,会过期。注册码有5段,是最完整的注册码。
3、0xFC,这个不算正式注册版本,是延长试用期版本,最长可延长为5年试用期(从发布日期算起),这个版本会有一些检查(注册表中的数据),保证延期合法。注册码只有4段。
具体见下图,是对 0x9C 类型的注册码进行检查:
[Asm] 纯文本查看 复制代码
上图中,调用函数 call 0xnnnn7351,就是解码版本数据,即解码 sn_int[0] ^ sn_int[6] 的值,这个值在 v10.0.0 版中,要大于或等于0x0B。
注册算法函数的完整代码如下,其中加了注释:01DDCC20 > \55
push
ebp
01DDCC21 . 8BEC
mov
ebp
,
esp
01DDCC23 . 6A FF
push
-0x1
01DDCC25 . 68 898F3002
push
02308F89
; SEH
01DDCC2A . 64:A1 00000000
mov
eax
,
dword
ptr
fs
:[0]
01DDCC30 . 50
push
eax
01DDCC31 . 83EC 18
sub
esp
, 0x18
01DDCC34 . 53
push
ebx
01DDCC35 . 56
push
esi
01DDCC36 . 57
push
edi
01DDCC37 . A1 E039BD03
mov
eax
,
dword
ptr
[0x3BD39E0]
01DDCC3C . 33C5
xor
eax
,
ebp
01DDCC3E . 50
push
eax
01DDCC3F . 8D45 F4
lea
eax
,
dword
ptr
[
ebp
-0xC]
01DDCC42 . 64:A3 00000000
mov
dword
ptr
fs
:[0],
eax
; SEH
01DDCC48 . 8BF9
mov
edi
,
ecx
; 函数参数
01DDCC4A . 8D5F 04
lea
ebx
,
dword
ptr
[
edi
+0x4]
; ebx=edi+4
01DDCC4D . C745 F0 00000000
mov
dword
ptr
[
ebp
-0x10], 0x0
01DDCC54 . 8BCB
mov
ecx
,
ebx
; [ecx] ===> 用户名
01DDCC56 . C747 30 00000000
mov
dword
ptr
[
edi
+0x30], 0x0
01DDCC5D . C747 18 00000000
mov
dword
ptr
[
edi
+0x18], 0x0
01DDCC64 . C747 34 00000000
mov
dword
ptr
[
edi
+0x34], 0x0
01DDCC6B . FF15 C494BD03
call
dword
ptr
[<&Qt5Core.QString::isEmpty>]
; Qt5Core.QString::isEmpty
01DDCC71 . 84C0
test
al
,
al
; al==0,非空
01DDCC73 . 0F85 75020000
jnz
01DDCEEE
01DDCC79 . 8D4F 08
lea
ecx
,
dword
ptr
[
edi
+0x8]
; [ecx] ===> 注册码
01DDCC7C . FF15 C494BD03
call
dword
ptr
[<&Qt5Core.QString::isEmpty>]
; Qt5Core.QString::isEmpty
01DDCC82 . 84C0
test
al
,
al
; al==0,非空
01DDCC84 . 0F85 64020000
jnz
01DDCEEE
01DDCC8A . 8D45 DC
lea
eax
,
dword
ptr
[
ebp
-0x24]
; [eax] ===> obj
01DDCC8D . 8BCF
mov
ecx
,
edi
; emptyString
01DDCC8F . 50
push
eax
01DDCC90 . E8 79D0E2FE
call
00C09D0E
; 将注册码转换成字节数组(按36进制转换)
01DDCC95 . BE 74ADBC03
mov
esi
, 03BCAD74
; [esi] ===> "999",黑名单列表
01DDCC9A . 8D9B 00000000
lea
ebx
,
dword
ptr
[
ebx
]
; [ebx] ===> 用户名
01DDCCA0 > FF36
push
dword
ptr
[
esi
]
; "999"
01DDCCA2 . 8BCB
mov
ecx
,
ebx
01DDCCA4 . FF15 A09DBD03
call
dword
ptr
[<&Qt5Core.QString::operator==>]
; Qt5Core.QString::operator==
01DDCCAA . 84C0
test
al
,
al
; (initial cpu selection)
01DDCCAC . 0F85 23020000
jnz
01DDCED5
; al == 1,则跳转到注册码错误
01DDCCB2 . 83C6 04
add
esi
, 0x4
01DDCCB5 . 81FE 78ADBC03
cmp
esi
, 03BCAD78
; 目前只有一组(黑名单)
01DDCC-B-B .^ 7C E3
jl
short
01DDCCA0
01DDCCBD . 8A5D
DF
mov
bl
,
byte
ptr
[
ebp
-0x21]
; sn_int[3]
01DDCCC0 . 8A7D E1
mov
bh
,
byte
ptr
[
ebp
-0x1F]
; sn_int[5]
01DDCCC3 . 80FB 9C
cmp
bl
, 0x9C
; Switch (cases 9C..FC)
01DDCCC6 . 75 70
jnz
short
01DDCD38
01DDCCC8 . 8A45 DC
mov
al
,
byte
ptr
[
ebp
-0x24]
; Case 9C of switch 01DDCCC3
01DDCCCB . 3245 E2
xor
al
,
byte
ptr
[
ebp
-0x1E]
01DDCCCE . 8845 E8
mov
byte
ptr
[
ebp
-0x18],
al
; UCHAR ch = sn_int[0] ^ sn_int[6]
01DDCCD1 . 8A45
DD
mov
al
,
byte
ptr
[
ebp
-0x23]
01DDCCD4 . 3245 E3
xor
al
,
byte
ptr
[
ebp
-0x1D]
01DDCCD7 . FF75 E8
push
dword
ptr
[
ebp
-0x18]
; 下面函数的参数:ch
01DDCCDA . 0FB6C8
movzx
ecx
,
al
; UWORD c = sn_int[1] ^ sn_int[7]
01DDCCDD . B8 00010000
mov
eax
, 0x100
01DDCCE2 . 66:0FAFC8
imul
cx
,
ax
; c *= 0x100
01DDCCE6 . 8A45 DE
mov
al
,
byte
ptr
[
ebp
-0x22]
01DDCCE9 . 32C7
xor
al
,
bh
01DDCCEB . 0FB6C0
movzx
eax
,
al
; UWORD a = sn_int[2] ^ sn_int[5]
01DDCCEE . 66:03C8
add
cx
,
ax
01DDCCF1 . 0FB7F1
movzx
esi
,
cx
; ULONG s = a + c = 0xA93B = 0xA900 + 0x3B
01DDCCF4 . E8 58A6E2FE
call
00C07351
; getCheck1(), a = ((ch ^ 0x18) + 0x3D) ^ 0xA7,解码版本标识
01DDCCF9 . 0FB6C0
movzx
eax
,
al
; ULONG ver_id = a
01DDCCFC . 56
push
esi
; 下面函数的参数:s, 0x5511
01DDCCFD . 8947 1C
mov
dword
ptr
[
edi
+0x1C],
eax
; 全局变量 ver_id == 0x2D, ds:[0x03F7218C] == 0x2D
01DDCD00 . E8 A1B5E2FE
call
00C082A6
; call getCheck2(),解码用户数
01DDCD05 . 8B4F 1C
mov
ecx
,
dword
ptr
[
edi
+0x1C]
; c = 0x2D, ver_id需要大于0x0B
01DDCD08 . 83C4 08
add
esp
, 0x8
01DDCD0B . 0FB7C0
movzx
eax
,
ax
; 函数返回值 a
01DDCD0E . 8947 20
mov
dword
ptr
[
edi
+0x20],
eax
; ULONG license_user_count = a, [0x03F72190] = 0x00000000
01DDCD11 . 85C9
test
ecx
,
ecx
; ver_id == 0 ???
01DDCD13 . 0F84 BC010000
je
01DDCED5
01DDCD19 . 85C0
test
eax
,
eax
; license_user_count == 0 ???
01DDCD1B . 0F84 B4010000
je
01DDCED5
01DDCD21 . 3D E8030000
cmp
eax
, 0x3E8
; license_user_count > 1000??
01DDCD26 . 0F87 A9010000
ja
01DDCED5
01DDCD2C . 83F9 02
cmp
ecx
, 0x2
; license_user_count < 2 ??
01DDCD2F . 1BF6
sbb
esi
,
esi
; esi = (ecx<2)?-1:0
01DDCD31 . 23F1
and
esi
,
ecx
; esi &= ecx
01DDCD33 . E9 B3000000
jmp
01DDCDEB
01DDCD38 > 80FB FC
cmp
bl
, 0xFC
01DDCD3B . 75 1F
jnz
short
01DDCD5C
01DDCD3D . C747 1C FF000000
mov
dword
ptr
[
edi
+0x1C], 0xFF
; ver_id = -1, ds:[0384546C]; Case FC of switch 01DDCCC3
01DDCD44 . BE FF000000
mov
esi
, 0xFF
; free_upgrade_days = -1, days
01DDCD49 . C747 20 01000000
mov
dword
ptr
[
edi
+0x20], 0x1
; license_user_count = 1, ds:[03845470]
01DDCD50 . C747 30 01000000
mov
dword
ptr
[
edi
+0x30], 0x1
; flag3 = 1, ds:[03F721A0]
01DDCD57 . E9 8F000000
jmp
01DDCDEB
01DDCD5C > 80FB AC
cmp
bl
, 0xAC
01DDCD5F . 0F85 70010000
jnz
01DDCED5
01DDCD65 . 8A45
DD
mov
al
,
byte
ptr
[
ebp
-0x23]
; sn_int[1]; Case AC of switch 01DDCCC3
01DDCD68 . 3245 E3
xor
al
,
byte
ptr
[
ebp
-0x1D]
; sn_int[7]
01DDCD6B . 0FB6C8
movzx
ecx
,
al
; c = sn_int[1] ^ sn_int[7]
01DDCD6E . B8 00010000
mov
eax
, 0x100
01DDCD73 . 66:0FAFC8
imul
cx
,
ax
01DDCD77 . 8A45 DE
mov
al
,
byte
ptr
[
ebp
-0x22]
; sn_int[2]
01DDCD7A . 32C7
xor
al
,
bh
; sn_int[5]
01DDCD7C . C747 1C 02000000
mov
dword
ptr
[
edi
+0x1C], 0x2
; ver_id = 2
01DDCD83 . 0FB6C0
movzx
eax
,
al
; a = sn_int[2] ^ sn_int[5]
01DDCD86 . 66:03C8
add
cx
,
ax
01DDCD89 . 0FB7C1
movzx
eax
,
cx
; enc_lic_user_count = (c<<8) + a
01DDCD8C . 50
push
eax
01DDCD8D . E8 14B5E2FE
call
00C082A6
; call getCheck2(),解码用户数
01DDCD92 . 0FB7C0
movzx
eax
,
ax
01DDCD95 . 83C4 04
add
esp
, 0x4
01DDCD98 . 8947 20
mov
dword
ptr
[
edi
+0x20],
eax
; license_user_count = eax
01DDCD9B . 85C0
test
eax
,
eax
; license_user_count > 0
01DDCD9D . 0F84 32010000
je
01DDCED5
01DDCDA3 . 3D E8030000
cmp
eax
, 0x3E8
; license_user_count <= 1000
01DDCDA8 . 0F87 27010000
ja
01DDCED5
01DDCDAE . 0FB655 E5
movzx
edx
,
byte
ptr
[
ebp
-0x1B]
; sn_int[9]
01DDCDB2 . 0FB64D E0
movzx
ecx
,
byte
ptr
[
ebp
-0x20]
; sn_int[4]
01DDCDB6 . 0FB6C7
movzx
eax
,
bh
01DDCDB9 . 33D0
xor
edx
,
eax
; ULONG a = sn_int[5] ^ sn_int[9]
01DDCDBB . 0FB645 E4
movzx
eax
,
byte
ptr
[
ebp
-0x1C]
; sn_int[8]
01DDCDBF . 33C8
xor
ecx
,
eax
; ULONG b = sn_int[4] ^ sn_int[8]
01DDCDC1 . C1E2 08
shl
edx
, 0x8
01DDCDC4 . 0FB645 E2
movzx
eax
,
byte
ptr
[
ebp
-0x1E]
; sn_int[6]
01DDCDC8 . 03D1
add
edx
,
ecx
01DDCDCA . 0FB64D DC
movzx
ecx
,
byte
ptr
[
ebp
-0x24]
; sn_int[0]
01DDCDCE . C1E2 08
shl
edx
, 0x8
01DDCDD1 . 33C8
xor
ecx
,
eax
; ULONG c = sn_int[0] ^ sn_int[6]
01DDCDD3 . 03D1
add
edx
,
ecx
; ULONG d = (((a<<8) + b)<<8) + c;
01DDCDD5 . 68 278C5B00
push
0x5B8C27
01DDCDDA . 52
push
edx
01DDCDDB . E8 71DCE2FE
call
00C0AA51
; call getCheck3(edx),解码免费升级时间
01DDCDE0 . 83C4 08
add
esp
, 0x8
01DDCDE3 . 8945 F0
mov
dword
ptr
[
ebp
-0x10],
eax
; free upgrade days
01DDCDE6 . 8947 34
mov
dword
ptr
[
edi
+0x34],
eax
; free upgrade days
01DDCDE9 . 8BF0
mov
esi
,
eax
; free upgrade days
01DDCDEB > 8D45 EC
lea
eax
,
dword
ptr
[
ebp
-0x14]
; 上面 case 处理完都到这里
01DDCDEE . 50
push
eax
01DDCDEF . 8D4F 04
lea
ecx
,
dword
ptr
[
edi
+0x4]
; [ecx] ===> 用户名
01DDCDF2 . FF15 1C9CBD03
call
dword
ptr
[<&Qt5Core.QString::toUtf8>]
; call (Qt5Core.QString::toUtf8),Unicode2utf8
01DDCDF8 . FF77 20
push
dword
ptr
[
edi
+0x20]
; license_user_count
01DDCDFB . 33C0
xor
eax
,
eax
01DDCDFD . C745 FC 00000000
mov
dword
ptr
[
ebp
-0x4], 0x0
01DDCE04 . 80FB FC
cmp
bl
, 0xFC
; bl == license_type
01DDCE07 . 8D4D EC
lea
ecx
,
dword
ptr
[
ebp
-0x14]
; [ecx] ===> 用户名(UTF8编码)
01DDCE0A . 56
push
esi
; esi == upgrade days
01DDCE0B . 0F95C0
setne
al
; bl != 0xFC
01DDCE0E . 50
push
eax
; al = (bl != 0xFC)?1:0,1-注册版,0-评估版
01DDCE0F . FF15 9C94BD03
call
dword
ptr
[<&Qt5Core.QByteArray::data>]
; Qt5Core.QByteArray::data
01DDCE15 . 50
push
eax
; eax ===> 用户名,字符串 "solly"
01DDCE16 . E8 D263E2FE
call
00C031ED
; getNameCheck(char * name, ULONG is_registered, ULONG free_upgrade_days, ULONG user_count)
01DDCE1B . 8BD0
mov
edx
,
eax
; nameCheck = 0xBAED7A3E
01DDCE1D . 83C4 10
add
esp
, 0x10
; 下面将 nameCheck 分成 4 字节与 sn_int[] 中的字节比较
01DDCE20 . 3855 E0
cmp
byte
ptr
[
ebp
-0x20],
dl
; sn_int[4]
01DDCE23 . 0F85 81000000
jnz
01DDCEAA
01DDCE29 . 8BCA
mov
ecx
,
edx
01DDCE2B . C1E9 08
shr
ecx
, 0x8
01DDCE2E . 3AF9
cmp
bh
,
cl
; sn_int[5]
01DDCE30 . 75 78
jnz
short
01DDCEAA
01DDCE32 . 8BCA
mov
ecx
,
edx
01DDCE34 . C1E9 10
shr
ecx
, 0x10
01DDCE37 . 384D E2
cmp
byte
ptr
[
ebp
-0x1E],
cl
; sn_int[6]
01DDCE3A . 75 6E
jnz
short
01DDCEAA
01DDCE3C . C1E8 18
shr
eax
, 0x18
01DDCE3F . 3845 E3
cmp
byte
ptr
[
ebp
-0x1D],
al
; sn_int[7]
01DDCE42 . 75 66
jnz
short
01DDCEAA
01DDCE44 . 80FB 9C
cmp
bl
, 0x9C
; Switch (cases 9C..FC)
01DDCE47 . 75 0F
jnz
short
01DDCE58
01DDCE49 . 8B45 08
mov
eax
,
dword
ptr
[
ebp
+0x8]
; Case 9C of switch 01DDCE44
01DDCE4C . 3B47 1C
cmp
eax
,
dword
ptr
[
edi
+0x1C]
; 0x0B(eax) <= version_id_in_sn,eax-函数参数1: 0x0B
01DDCE4F . 76 52
jbe
short
01DDCEA3
; 不高于跳转(小于或等于)
01DDCE51 . BE 4E000000
mov
esi
, 0x4E
; 过期,老版本的注册码
01DDCE56 . EB 57
jmp
short
01DDCEAF
01DDCE58 > 80FB FC
cmp
bl
, 0xFC
01DDCE5B . 75 2E
jnz
short
01DDCE8B
01DDCE5D . 0FB64D DE
movzx
ecx
,
byte
ptr
[
ebp
-0x22]
; sn_int[2]; Case FC of switch 01DDCE44
01DDCE61 . 0FB645
DD
movzx
eax
,
byte
ptr
[
ebp
-0x23]
; sn_int[1]
01DDCE65 . C1E1 08
shl
ecx
, 0x8
01DDCE68 . 03C8
add
ecx
,
eax
01DDCE6A . 0FB645 DC
movzx
eax
,
byte
ptr
[
ebp
-0x24]
; sn_int[0]
01DDCE6E . C1E1 08
shl
ecx
, 0x8
01DDCE71 . 52
push
edx
01DDCE72 . 03C8
add
ecx
,
eax
; ecx = sn_int[2]sn_int[1]sn_int[0]
01DDCE74 . 51
push
ecx
01DDCE75 . E8 D7DBE2FE
call
00C0AA51
; call getCheck3(ecx),解码试用期延长的日期
01DDCE7A . 83C4 08
add
esp
, 0x8
01DDCE7D . 85C0
test
eax
,
eax
01DDCE7F . 74 29
je
short
01DDCEAA
01DDCE81 . 8947 18
mov
dword
ptr
[
edi
+0x18],
eax
; evaluation extends days
01DDCE84 . BE 93000000
mov
esi
, 0x93
01DDCE89 . EB 24
jmp
short
01DDCEAF
01DDCE8B > 80FB AC
cmp
bl
, 0xAC
01DDCE8E . 75 1A
jnz
short
01DDCEAA
01DDCE90 . 8B45 F0
mov
eax
,
dword
ptr
[
ebp
-0x10]
; free upgrade days; Case AC of switch 01DDCE44
01DDCE93 . 85C0
test
eax
,
eax
; free upgrade days > 0
01DDCE95 . 74 13
je
short
01DDCEAA
01DDCE97 . 3945 0C
cmp
dword
ptr
[
ebp
+0xC],
eax
; free upgrade days >= release days(函数参数2:0x473C)
01DDCE9A . 76 07
jbe
short
01DDCEA3
01DDCE9C . BE 4E000000
mov
esi
, 0x4E
; 过期
01DDCEA1 . EB 0C
jmp
short
01DDCEAF
01DDCEA3 > BE 2D000000
mov
esi
, 0x2D
; 注册码正确
01DDCEA8 . EB 05
jmp
short
01DDCEAF
01DDCEAA > BE E7000000
mov
esi
, 0xE7
; 试用期已结束或注册码无效; Default case of switch 01DDCE44
01DDCEAF > 8D4D EC
lea
ecx
,
dword
ptr
[
ebp
-0x14]
; [ecx] ===> 用户名
01DDCEB2 . C745 FC FFFFFFFF
mov
dword
ptr
[
ebp
-0x4], -0x1
01DDCEB9 . FF15 8C94BD03
call
dword
ptr
[<&Qt5Core.QByteArray::~QByteArray>]
; Qt5Core.QByteArray::~QByteArray
01DDCEBF . 8BC6
mov
eax
,
esi
; esi == 0xE7, 0x2D, 0x4E, 0x93 等, 0x2D 表示注册码正确
01DDCEC1 . 8B4D F4
mov
ecx
,
dword
ptr
[
ebp
-0xC]
01DDCEC4 . 64:890D 00000000
mov
dword
ptr
fs
:[0],
ecx
01DDCECB . 59
pop
ecx
01DDCECC . 5F
pop
edi
01DDCECD . 5E
pop
esi
01DDCECE . 5B
pop
ebx
01DDCECF . 8BE5
mov
esp
,
ebp
01DDCED1 . 5D
pop
ebp
01DDCED2 . C2 0800
retn
0x8
01DDCED5 > B8 E7000000
mov
eax
, 0xE7
; 注册码无效; Default case of switch 01DDCCC3
01DDCEDA . 8B4D F4
mov
ecx
,
dword
ptr
[
ebp
-0xC]
01DDCEDD . 64:890D 00000000
mov
dword
ptr
fs
:[0],
ecx
01DDCEE4 . 59
pop
ecx
01DDCEE5 . 5F
pop
edi
01DDCEE6 . 5E
pop
esi
01DDCEE7 . 5B
pop
ebx
01DDCEE8 . 8BE5
mov
esp
,
ebp
01DDCEEA . 5D
pop
ebp
01DDCEEB . C2 0800
retn
0x8
01DDCEEE > B8 93000000
mov
eax
, 0x93
; 用户名或注册码为空到这里
01DDCEF3 . 8B4D F4
mov
ecx
,
dword
ptr
[
ebp
-0xC]
01DDCEF6 . 64:890D 00000000
mov
dword
ptr
fs
:[0],
ecx
01DDCEFD . 59
pop
ecx
01DDCEFE . 5F
pop
edi
01DDCEFF . 5E
pop
esi
01DDCF00 . 5B
pop
ebx
01DDCF01 . 8BE5
mov
esp
,
ebp
01DDCF03 . 5D
pop
ebp
01DDCF04 . C2 0800
retn
0x8
从上面函数返回后,我们马上再进入另一个函数(call 0xnnnn9E58),这个函数还会再次调用上面的注册算法函数,同时还会调另一个函数,对试用期延长的注册码进行检查,如下图所示:
[Asm] 纯文本查看 复制代码
从上图可以看,首先再次调用注册码检查函数(0xnnnnD55E call 0xnnnnAAEC),然后再调用另一个函数(call 0xnnnn2CE8),这个函数对 0xFC 类型的注册码进行进一步的合法性检查。下面我们"F7"再次进入注册码算法函数,看看函数内其它几个调用,如下所示:01DDCDF8 . FF77 20
push
dword
ptr
[
edi
+0x20]
; flag2
01DDCDFB . 33C0
xor
eax
,
eax
01DDCDFD . C745 FC 00000000
mov
dword
ptr
[
ebp
-0x4], 0x0
01DDCE04 . 80FB FC
cmp
bl
, 0xFC
; bl == license_type
01DDCE07 . 8D4D EC
lea
ecx
,
dword
ptr
[
ebp
-0x14]
; [ecx] ===> 用户名(UTF8编码)
01DDCE0A . 56
push
esi
; esi == 0x00000000, upgrade days
01DDCE0B . 0F95C0
setne
al
; bl != 0xFC
01DDCE0E . 50
push
eax
; al = (bl != 0xFC)?1:0,1-注册版,0-评估版
01DDCE0F . FF15 9C94BD03
call
dword
ptr
[<&Qt5Core.QByteArray::data>]
; Qt5Core.QByteArray::data
01DDCE15 . 50
push
eax
; eax ===> 用户名,字符串 "solly"
01DDCE16 . E8 D263E2FE
call
00C031ED
; getNameCheck(char * name, ULONG check1, ULONG check2, ULONG flag)
01DDCE1B . 8BD0
mov
edx
,
eax
; check = 0xBAED7A3E
注册算法函数调用了一个函数(call 0xnnnn31ED),对用户名进行处理,该函数如下图所示:
通过查表方式,对用户名进行变换,生成一个32位的整数,如下图所示:
循环对名字每个字符进行处理,如下图所示:
[Asm] 纯文本查看 复制代码
用户名处理函数具体代码如下:01DDC180 /> \55
push
ebp
01DDC181 |. 8BEC
mov
ebp
,
esp
01DDC183 |. 83EC 10
sub
esp
, 0x10
01DDC186 |. 8B55 08
mov
edx
,
dword
ptr
[
ebp
+0x8]
; edx ===> 用户名
01DDC189 |. 33C9
xor
ecx
,
ecx
; 0
01DDC18B |. 56
push
esi
01DDC18C |. 8BF2
mov
esi
,
edx
01DDC18E |. 894D FC
mov
dword
ptr
[
ebp
-0x4],
ecx
; long name_check = 0;
01DDC191 |. 57
push
edi
01DDC192 |. 8D7E 01
lea
edi
,
dword
ptr
[
esi
+0x1]
; edi ===> name[1]
01DDC195 |> 8A06 /
mov
al
,
byte
ptr
[
esi
]
; char ch = (* name);
01DDC197 |. 46 |
inc
esi
; name++
01DDC198 |. 84C0 |
test
al
,
al
01DDC19A |.^ 75 F9 \
jnz
short
01DDC195
01DDC19C |. 2BF7
sub
esi
,
edi
; esi === length(name)
01DDC19E |. 33FF
xor
edi
,
edi
; int i= 0;
01DDC1A0 |. 85F6
test
esi
,
esi
01DDC1A2 |. 0F8E F0000000
jle
01DDC298
01DDC1A8 |. 53
push
ebx
01DDC1A9 |. 8B5D 14
mov
ebx
,
dword
ptr
[
ebp
+0x14]
; 参数: 0x000003E8
01DDC1AC |. 894D F0
mov
dword
ptr
[
ebp
-0x10],
ecx
; 0
01DDC1AF |. 894D F4
mov
dword
ptr
[
ebp
-0xC],
ecx
; 0
01DDC1B2 |. 8B4D 10
mov
ecx
,
dword
ptr
[
ebp
+0x10]
; 参数: 0x00000000
01DDC1B5 |. C1E3 04
shl
ebx
, 0x4
01DDC1B8 |. 2B5D 14
sub
ebx
,
dword
ptr
[
ebp
+0x14]
; ebx = param4 * 15 = 0x03E8 * 0x0F = 0x00003A98
01DDC1BB |. C1E1 04
shl
ecx
, 0x4
01DDC1BE |. 034D 10
add
ecx
,
dword
ptr
[
ebp
+0x10]
; ecx = param3 + param3 * 16
01DDC1C1 |. 894D F8
mov
dword
ptr
[
ebp
-0x8],
ecx
; ecx = param3 * 17
01DDC1C4 |> 0FB60417 /
movzx
eax
,
byte
ptr
[
edi
+
edx
]
; ULONG ch = name[i]
01DDC1C8 |. 50 |
push
eax
; /c
01DDC1C9 |. FF15 B092BD03 |
call
dword
ptr
[<&MSVCR120.toupper>]
; \toupper
01DDC1CF |. 8BD0 |
mov
edx
,
eax
; long idx = uppcase(name[i])
01DDC1D1 |. 83C4 04 |
add
esp
, 0x4
01DDC1D4 |. 8B0C95 A8A8BC03 |
mov
ecx
,
dword
ptr
[
edx
*4+0x3BCA8A8]
; 查表, name_tbl_check = name_table[idx];
01DDC1DB |. 034D FC |
add
ecx
,
dword
ptr
[
ebp
-0x4]
; name_check += name_tbl_check;
01DDC1DE |. 837D 0C 00 |
cmp
dword
ptr
[
ebp
+0xC], 0x0
; 参数:1
01DDC1E2 |. 74 4A |
je
short
01DDC22E
01DDC1E4 |. 8D42 0D |
lea
eax
,
dword
ptr
[
edx
+0xD]
01DDC1E7 |. 25 FF000000 |
and
eax
, 0xFF
; long a = (idx + 0x0D) & 0xFF;
01DDC1EC |. 330C85 A8A8BC03 |
xor
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
; 查表, name_check ^= name_table[a];
01DDC1F3 |. 8D42 2F |
lea
eax
,
dword
ptr
[
edx
+0x2F]
01DDC1F6 |. 25 FF000000 |
and
eax
, 0xFF
; long b = (idx + 0x2F) & 0xFF;
01DDC1FB |. 0FAF0C85 A8A8BC03 |
imul
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
; 查表, name_check *= name_table[ b ];
01DDC203 |. 8B45 F8 |
mov
eax
,
dword
ptr
[
ebp
-0x8]
01DDC206 |. 0FB6C0 |
movzx
eax
,
al
; long c = (UCHAR)local_var1
01DDC209 |. 030C85 A8A8BC03 |
add
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
; 查表, name_check += name_table[c];
01DDC210 |. 0FB6C3 |
movzx
eax
,
bl
; long d = (UCHAR)(sn_flag * 15) , 0x98
01DDC213 |. 030C85 A8A8BC03 |
add
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
; 查表, name_check += name_table[d];
01DDC21A |. 8B45 F4 |
mov
eax
,
dword
ptr
[
ebp
-0xC]
; local_var, (0)
01DDC21D |. 0FB6C0 |
movzx
eax
,
al
; long e = (UCHAR)(local_var2) , 0x98
01DDC220 |. 030C85 A8A8BC03 |
add
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
; 查表, name_check += name_table[e];
01DDC227 |. 8BC1 |
mov
eax
,
ecx
; 保存返回值
01DDC229 |. 8945 FC |
mov
dword
ptr
[
ebp
-0x4],
eax
; 保存 name_check
01DDC22C |. EB 48 |
jmp
short
01DDC276
01DDC22E |> 8D42 3F |
lea
eax
,
dword
ptr
[
edx
+0x3F]
01DDC231 |. 25 FF000000 |
and
eax
, 0xFF
01DDC236 |. 330C85 A8A8BC03 |
xor
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
01DDC23D |. 8D42 17 |
lea
eax
,
dword
ptr
[
edx
+0x17]
01DDC240 |. 25 FF000000 |
and
eax
, 0xFF
01DDC245 |. 0FAF0C85 A8A8BC03 |
imul
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
01DDC24D |. 8B45 F8 |
mov
eax
,
dword
ptr
[
ebp
-0x8]
01DDC250 |. 0FB6C0 |
movzx
eax
,
al
01DDC253 |. 030C85 A8A8BC03 |
add
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
01DDC25A |. 0FB6C3 |
movzx
eax
,
bl
01DDC25D |. 030C85 A8A8BC03 |
add
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
01DDC264 |. 8B45 F0 |
mov
eax
,
dword
ptr
[
ebp
-0x10]
; local_var3
01DDC267 |. 0FB6C0 |
movzx
eax
,
al
01DDC26A |. 030C85 A8A8BC03 |
add
ecx
,
dword
ptr
[
eax
*4+0x3BCA8A8]
01DDC271 |. 8BC1 |
mov
eax
,
ecx
; 保存返回值
01DDC273 |. 894D FC |
mov
dword
ptr
[
ebp
-0x4],
ecx
; 保存 name_check
01DDC276 |> 8345 F4 13 |
add
dword
ptr
[
ebp
-0xC], 0x13
; 公共部分, local_var2 += 0x13;
01DDC27A |. 47 |
inc
edi
; i++
01DDC27B |. 8345 F8 09 |
add
dword
ptr
[
ebp
-0x8], 0x9
; local_var1 += 9;
01DDC27F |. 83C3 0D |
add
ebx
, 0xD
; 参数 ebx += 0x0D;
01DDC282 |. 8345 F0 07 |
add
dword
ptr
[
ebp
-0x10], 0x7
; local_var3 += 0x07;
01DDC286 |. 8B55 08 |
mov
edx
,
dword
ptr
[
ebp
+0x8]
; edx ===> 用户名,"solly"
01DDC289 |. 3BFE |
cmp
edi
,
esi
; esi == 0x05,用户名长度
01DDC28B |.^ 0F8C 33FFFFFF \
jl
01DDC1C4
01DDC291 |. 5B
pop
ebx
01DDC292 |. 5F
pop
edi
01DDC293 |. 5E
pop
esi
01DDC294 |. 8BE5
mov
esp
,
ebp
01DDC296 |. 5D
pop
ebp
01DDC297 |. C3
retn
; ret_value = 0xBAED7A3E
01DDC298 |> 5F
pop
edi
01DDC299 |. 8BC1
mov
eax
,
ecx
01DDC29B |. 5E
pop
esi
01DDC29C |. 8BE5
mov
esp
,
ebp
01DDC29E |. 5D
pop
ebp
01DDC29F \. C3
retn
另外,当注册类型为 0xFC 时,会读取注册表中的数据进行检查,如下图所示的几个注册表键值:
[Asm] 纯文本查看 复制代码 [Asm] 纯文本查看 复制代码
不过数据都是编码加密过的,需要解码才能识别,包括试用剩余次数(0x09),版本标识(0x0B),发布(或安装)日期,有效可用日期(试用期或正式版免费支持时间)等等数据。试用版有效时长检查调用(call 0xnnnn2CE8)的代码如下:01DDC720 > \55
push
ebp
01DDC721 . 8BEC
mov
ebp
,
esp
01DDC723 . 6A FF
push
-0x1
01DDC725 . 68 E98E3002
push
02308EE9
01DDC72A . 64:A1 00000000
mov
eax
,
dword
ptr
fs
:[0]
01DDC730 . 50
push
eax
01DDC731 . 51
push
ecx
01DDC732 . 57
push
edi
01DDC733 . A1 E039BD03
mov
eax
,
dword
ptr
[0x3BD39E0]
; SEH Object handler
01DDC738 . 33C5
xor
eax
,
ebp
01DDC73A . 50
push
eax
01DDC73B . 8D45 F4
lea
eax
,
dword
ptr
[
ebp
-0xC]
01DDC73E . 64:A3 00000000
mov
dword
ptr
fs
:[0],
eax
01DDC744 . 8BF9
mov
edi
,
ecx
01DDC746 . FF75 08
push
dword
ptr
[
ebp
+0x8]
; 0x0B
01DDC749 . E8 DDEAE2FE
call
00C0B22B
; 检查注册表中的 versiob_id 与软件内置的verid是否一致
01DDC74E . FF75 08
push
dword
ptr
[
ebp
+0x8]
; get 0x0B, (verid)
01DDC751 . 8BCF
mov
ecx
,
edi
01DDC753 . E8 71B1E2FE
call
00C078C9
; get object
01DDC758 . 8BCF
mov
ecx
,
edi
01DDC75A . E8 3262E2FE
call
00C02991
; get object (0x0FE19483 ==> 0x4770),检查是否存在??
01DDC75F . 8D45 F0
lea
eax
,
dword
ptr
[
ebp
-0x10]
01DDC762 . 50
push
eax
01DDC763 . E8 AFC2E2FE
call
00C08A17
; get object, registry object or file object: "010Editor.dat"
01DDC768 . 8BC8
mov
ecx
,
eax
01DDC76A . E8 1EF2E2FE
call
00C0B98D
; get 0x4770, (上次成功注册的日期)
01DDC76F . 84C0
test
al
,
al
; al == 1,读取成功
01DDC771 . 74 08
je
short
01DDC77B
01DDC773 . 8B45 F0
mov
eax
,
dword
ptr
[
ebp
-0x10]
; 0x4770
01DDC776 . 8947 10
mov
dword
ptr
[
edi
+0x10],
eax
; 通过参数返回
01DDC779 . EB 17
jmp
short
01DDC792
01DDC77B > E8 20BCE2FE
call
00C083A0
01DDC780 . 6A 01
push
0x1
01DDC782 . 50
push
eax
01DDC783 . 8947 10
mov
dword
ptr
[
edi
+0x10],
eax
01DDC786 . E8 8CC2E2FE
call
00C08A17
01DDC78B . 8BC8
mov
ecx
,
eax
01DDC78D . E8 81DAE2FE
call
00C0A213
01DDC792 > 837F 18 00
cmp
dword
ptr
[
edi
+0x18], 0x0
; 0x6F85,来自SN的值
01DDC796 . 76 0D
jbe
short
01DDC7A5
01DDC798 . 837F 30 00
cmp
dword
ptr
[
edi
+0x30], 0x0
; 0x01
01DDC79C . 74 07
je
short
01DDC7A5
01DDC79E . 8BCF
mov
ecx
,
edi
01DDC7A0 . E8 04BDE2FE
call
00C084A9
; 注册表中的用户名检查
01DDC7A5 > 8B4F 10
mov
ecx
,
dword
ptr
[
edi
+0x10]
; 0x4770
01DDC7A8 . 8D45 F0
lea
eax
,
dword
ptr
[
ebp
-0x10]
01DDC7AB . 83C1 1E
add
ecx
, 0x1E
; ecx == 0x4770 + 0x1E, 0x1E==30,30天试用期
01DDC7AE . 394F 18
cmp
dword
ptr
[
edi
+0x18],
ecx
; 时间比较(0x7755 < 0x478E ??)
01DDC7B1 . 50
push
eax
01DDC7B2 . 0F434F 18
cmovnb
ecx
,
dword
ptr
[
edi
+0x18]
; 不小于则取较大的时间值, ecx = (0x7755<ecx)?ecx:0x7755
01DDC7B6 . 894F 18
mov
dword
ptr
[
edi
+0x18],
ecx
01DDC7B9 . E8 59C2E2FE
call
00C08A17
; get object, registry object or file object: "010Editor.dat"
01DDC7BE . 8BC8
mov
ecx
,
eax
01DDC7C0 . E8 F09CE2FE
call
00C064B5
; get 0x7071 (上次成功注册的最长有效日期)
01DDC7C5 . 84C0
test
al
,
al
; al == 1,表示成功
01DDC7C7 . 0F84 8F000000
je
01DDC85C
01DDC7CD . 8B45 F0
mov
eax
,
dword
ptr
[
ebp
-0x10]
; 0x7071
01DDC7D0 . 8947 14
mov
dword
ptr
[
edi
+0x14],
eax
; 0x7071
01DDC7D3 . 3B47 18
cmp
eax
,
dword
ptr
[
edi
+0x18]
; 时间比较(0x7071<0x7755)
01DDC7D6 . 0F83 95000000
jnb
01DDC871
; 不低于跳转,因此SN中的days应等于或小于0x7071
01DDC7DC . E8 BFBBE2FE
call
00C083A0
; 生成当前日期,0x4771(2020/01/28)
01DDC7E1 . 8B4F 14
mov
ecx
,
dword
ptr
[
edi
+0x14]
; 0x7071, (max)
01DDC7E4 . 8BD0
mov
edx
,
eax
; current days, 0x4771
01DDC7E6 . 3BD1
cmp
edx
,
ecx
; 当前日期 < 最后成功的有效日期
01DDC7E8 . 73 17
jnb
short
01DDC801
; 需要跳转(不低于跳转)
01DDC7EA . B8 38010000
mov
eax
, 0x138
; over???
01DDC7EF . 8B4D F4
mov
ecx
,
dword
ptr
[
ebp
-0xC]
01DDC7F2 . 64:890D 00000000
mov
dword
ptr
fs
:[0],
ecx
01DDC7F9 . 59
pop
ecx
01DDC7FA . 5F
pop
edi
01DDC7FB . 8BE5
mov
esp
,
ebp
01DDC7FD . 5D
pop
ebp
01DDC7FE . C2 0400
retn
0x4
01DDC801 > 8B47 10
mov
eax
,
dword
ptr
[
edi
+0x10]
; 0x4770, (上次成功注册的日期)
01DDC804 . 85C0
test
eax
,
eax
01DDC806 . 74 1E
je
short
01DDC826
; 时间异常
01DDC808 . 85C9
test
ecx
,
ecx
; 0x7071, (上次成功注册的最长有效日期)
01DDC80A . 74 1A
je
short
01DDC826
; 时间异常
01DDC80C . 3BC8
cmp
ecx
,
eax
01DDC80E . 72 16
jb
short
01DDC826
; 时间异常(低于跳转)
01DDC810 . 8B47 18
mov
eax
,
dword
ptr
[
edi
+0x18]
; sn中的日期
01DDC813 . 2BC1
sub
eax
,
ecx
; SN中的日期 - (上次成功注册的最长有效日期)
01DDC815 . 3D 21070000
cmp
eax
, 0x721
; 最大试用期不能大于 0x721, 1825days(5年)
01DDC81A . 77 0A
ja
short
01DDC826
; 高于跳转
01DDC81C . 3BCA
cmp
ecx
,
edx
01DDC81E . 73 51
jnb
short
01DDC871
; 需要跳转
01DDC820 . 8957 14
mov
dword
ptr
[
edi
+0x14],
edx
01DDC823 . 52
push
edx
01DDC824 . EB 3F
jmp
short
01DDC865
01DDC826 > 68 38D16A03
push
036AD138
; ASCII "The system clock has been tampered with. Sorry, but this ends your trial period. Please register."
01DDC82B . 8D4D 08
lea
ecx
,
dword
ptr
[
ebp
+0x8]
01DDC82E . FF15 AC9DBD03
call
dword
ptr
[<&Qt5Core.QString::QString>]
; Qt5Core.QString::QString
01DDC834 . 8D45 08
lea
eax
,
dword
ptr
[
ebp
+0x8]
01DDC837 . C745 FC 00000000
mov
dword
ptr
[
ebp
-0x4], 0x0
01DDC83E . 50
push
eax
01DDC83F . E8 7151E2FE
call
00C019B5
01DDC844 . 83C4 04
add
esp
, 0x4
01DDC847 . C745 FC FFFFFFFF
mov
dword
ptr
[
ebp
-0x4], -0x1
01DDC84E . 8D4D 08
lea
ecx
,
dword
ptr
[
ebp
+0x8]
01DDC851 . FF15 B094BD03
call
dword
ptr
[<&Qt5Core.QString::~QString>]
; Qt5Core.QXmlStreamStringRef::~QXmlStreamStringRef
01DDC857 . 8B47 18
mov
eax
,
dword
ptr
[
edi
+0x18]
01DDC85A . EB 05
jmp
short
01DDC861
01DDC85C > E8 3FBBE2FE
call
00C083A0
; 取当前日期时间
01DDC861 > 50
push
eax
01DDC862 . 8947 14
mov
dword
ptr
[
edi
+0x14],
eax
01DDC865 > E8 ADC1E2FE
call
00C08A17
; 读取"010Editor.dat"的文件时间???
01DDC86A . 8BC8
mov
ecx
,
eax
01DDC86C . E8 0D00E3FE
call
00C0C87E
01DDC871 > 8B47 14
mov
eax
,
dword
ptr
[
edi
+0x14]
; 0x7071
01DDC874 . 3B47 18
cmp
eax
,
dword
ptr
[
edi
+0x18]
; 时间比较(0x7071<0x7755)
01DDC877 . 1BC0
sbb
eax
,
eax
; eax == -1,才正确, 最大有效日期必须小于SN中日期
01DDC879 . 83E0 ED
and
eax
, 0xFFFFFFED
; -19
01DDC87C . 83C0 2A
add
eax
, 0x2A
; 42
01DDC87F . 8B4D F4
mov
ecx
,
dword
ptr
[
ebp
-0xC]
01DDC882 . 64:890D 00000000
mov
dword
ptr
fs
:[0],
ecx
01DDC889 . 59
pop
ecx
01DDC88A . 5F
pop
edi
01DDC88B . 8BE5
mov
esp
,
ebp
01DDC88D . 5D
pop
ebp
01DDC88E . C2 0400
retn
0x4
上面説的调用注册算法函数和试用期检查函数的函数代码如下:01DDD540 /> \55
push
ebp
01DDD541 |. 8BEC
mov
ebp
,
esp
01DDD543 |. 56
push
esi
01DDD544 |. 8BF1
mov
esi
,
ecx
01DDD546 |. 837E 2C 00
cmp
dword
ptr
[
esi
+0x2C], 0x0
; flag6
01DDD54A |. 74 0A
je
short
01DDD556
01DDD54C |. B8 13010000
mov
eax
, 0x113
01DDD551 |. 5E
pop
esi
01DDD552 |. 5D
pop
ebp
01DDD553 |. C2 0800
retn
0x8
01DDD556 |> 57
push
edi
01DDD557 |. FF75 0C
push
dword
ptr
[
ebp
+0xC]
; 0x473C, release days
01DDD55A |. 8B7D 08
mov
edi
,
dword
ptr
[
ebp
+0x8]
; 0x0B, version_id
01DDD55D |. 57
push
edi
01DDD55E |. E8 89D5E2FE
call
00C0AAEC
; 注册码检查(第二次检查)
01DDD563 |. 83F8 2D
cmp
eax
, 0x2D
; ‘-’; Switch (cases 2D..E7)
01DDD566 |. 0F84 A3000000
je
01DDD60F
; 正确
01DDD56C |. 83F8 4E
cmp
eax
, 0x4E
; ‘N’
01DDD56F |. 74 78
je
short
01DDD5E9
01DDD571 |. 3D E7000000
cmp
eax
, 0xE7
01DDD576 |. 74 66
je
short
01DDD5DE
01DDD578 |. 57
push
edi
; 0x0B; Default case of switch 01DDD563
01DDD579 |. 8BCE
mov
ecx
,
esi
01DDD57B |. E8 6857E2FE
call
00C02CE8
; 试用周期检查
01DDD580 |. 83F8 17
cmp
eax
, 0x17
; 试用周期延长成功; Switch (cases 17..138)
01DDD583 |. 74 4E
je
short
01DDD5D3
01DDD585 |. 83F8 2A
cmp
eax
, 0x2A
; 在试用期内
01DDD588 |. 74 28
je
short
01DDD5B2
01DDD58A |. 3D 38010000
cmp
eax
, 0x138
; 试用期已结束
01DDD58F |. 75 4D
jnz
short
01DDD5DE
01DDD591 |. 8BCE
mov
ecx
,
esi
; Case 138 of switch 01DDD580
01DDD593 |. E8 FBCEE2FE
call
00C0A493
01DDD598 |. 3D A3010000
cmp
eax
, 0x1A3
01DDD59D |. B9 2F000000
mov
ecx
, 0x2F
01DDD5A2 |. BA F9000000
mov
edx
, 0xF9
01DDD5A7 |. 0F44CA
cmove
ecx
,
edx
01DDD5AA |. 5F
pop
edi
01DDD5AB |. 8BC1
mov
eax
,
ecx
01DDD5AD |. 5E
pop
esi
01DDD5AE |. 5D
pop
ebp
01DDD5AF |. C2 0800
retn
0x8
01DDD5B2 |> 8BCE
mov
ecx
,
esi
; Case 2A of switch 01DDD580
01DDD5B4 |. E8 DACEE2FE
call
00C0A493
01DDD5B9 |. 3D A3010000
cmp
eax
, 0x1A3
01DDD5BE |. B9 77010000
mov
ecx
, 0x177
01DDD5C3 |. BA F9000000
mov
edx
, 0xF9
01DDD5C8 |. 0F44CA
cmove
ecx
,
edx
01DDD5CB |. 5F
pop
edi
01DDD5CC |. 8BC1
mov
eax
,
ecx
01DDD5CE |. 5E
pop
esi
01DDD5CF |. 5D
pop
ebp
01DDD5D0 |. C2 0800
retn
0x8
01DDD5D3 |> 5F
pop
edi
; Case 17 of switch 01DDD580
01DDD5D4 |. B8 71000000
mov
eax
, 0x71
; 试用期扩充
01DDD5D9 |. 5E
pop
esi
01DDD5DA |. 5D
pop
ebp
01DDD5DB |. C2 0800
retn
0x8
01DDD5DE |> 5F
pop
edi
; Default case of switch 01DDD580
01DDD5DF |. B8 77010000
mov
eax
, 0x177
01DDD5E4 |. 5E
pop
esi
01DDD5E5 |. 5D
pop
ebp
01DDD5E6 |. C2 0800
retn
0x8
01DDD5E9 |> 57
push
edi
; Case 4E of switch 01DDD563
01DDD5EA |. 8BCE
mov
ecx
,
esi
01DDD5EC |. E8 F756E2FE
call
00C02CE8
; 试用周期检查
01DDD5F1 |. 83F8 17
cmp
eax
, 0x17
01DDD5F4 |. 74 0E
je
short
01DDD604
01DDD5F6 |. 5F
pop
edi
01DDD5F7 |. 83F8 2A
cmp
eax
, 0x2A
01DDD5FA |. B8 ED000000
mov
eax
, 0xED
01DDD5FF |. 5E
pop
esi
01DDD600 |. 5D
pop
ebp
01DDD601 |. C2 0800
retn
0x8
01DDD604 |> 5F
pop
edi
01DDD605 |. B8 0C020000
mov
eax
, 0x20C
01DDD60A |. 5E
pop
esi
01DDD60B |. 5D
pop
ebp
01DDD60C |. C2 0800
retn
0x8
01DDD60F |> 5F
pop
edi
; Case 2D of switch 01DDD563
01DDD610 |. B8 DB000000
mov
eax
, 0xDB
01DDD615 |. 5E
pop
esi
01DDD616 |. 5D
pop
ebp
01DDD617 \. C2 0800
retn
0x8
当试用周期过期,就会要求注册,如下图所示:
我们输入一个延长试用期的注册码,注册码类型:0xFC,如下图所示:
点 ”Check License“,如果正确,则提示如上图所示,下图的 "About" 中,会显示延长了多少天数,如下图所示,延长了 1825 天(从 2019/12/06 开始计算,所以还剩 1772 天的试用期)。
如果要换一注册类型,先要移除原有注册码,如下图所示:
在注册界面,点 ”Remove my license from this machine“,再次确认”Remove“就可以了。
下面是输入 0x9C 类型的注册码,可以看到,没有任何免费支持和升级,一开始就显示免费支持过期(1970/1/2过期)了:
下面是输入 0xAC 类型的注册码,这个是有免费支持和升级的,我设置的过期日期为:2048-10-24,即20481024,程序员喜欢的数 字!!!
提示框会显示什么时候过期:十月 24,2048。注册界面上也会显示,如下图所示:
上面都是单用户的注册授权,下面来一个多用户的:999用户。
还有一种不限用户数的:Site License。如下图所示:
在v10.0.0 x64 位的版本上,也可以通过(界面与x86版一样,不截图了)。
下面看看老版本是否可用,在 Windows 7 下的 v3.2.2 版没有问题:
在 Windows 98 下的 v2.1.4 也没有问题:
经过几个版本测试,表示其注册码算法没有改变过。0x9C 类型因为版本标识检查会过期(当然可以在SN中预设一个很大的版本号),不一定适于新版本。 但 0xAC 注册类型是可能通用各版本的。
另外,除了30天试用期,还有一种对使用次数进行限制的试用,可使用次数保存在注册表中。如下图所示,还有9次可用:
[C++] 纯文本查看 复制代码 [C++] 纯文本查看 复制代码 [Shell] 纯文本查看 复制代码
以上就是该软件的注册分析,下面放出其注册机源码,可以生成以上3种类型(0x9C, 0xAC, 0xFC)的注册码,也可以更改注册用户数(1-single user license, 2~999-multiply user license, 1000-site license)。源码通过 dev-c++ 调试通过,代码如下:#include <iostream>
#include <string.h>
#include "name_tables.h"
typedef
unsigned
char
UCHAR
;
typedef
unsigned
short
UWORD;
typedef
unsigned
long
ULONG
;
typedef
unsigned
int
UINT32
;
typedef
unsigned
long
long
UINT64
;
/*
v3.2.2:
release date: 0x3B20
version id: 0x04
*/
/*
v8.0.0:
release date: 0x4389
version id: 0x09
*/
/*
v9.0.0:
release date:
version id: 0x0A
*/
/*
v10.0.0:
release date: 0x473C
version id: 0x0B
*/
ULONG
RLSDATE = 0x473C;
//// 2019/12/06,免费升级开始日期,应该就是发布日期吧
ULONG
VERID = 0x0B;
//// 11,当前版本ID,可升级最高版本号,序列号的版本号要大于或等于此值
/////////////////////////////////////////////////
//// 免费升级天数 (可修改)
ULONG
FREE_UPGRADE_DAYS = 10549;
//// 免费升级天数:10549(截止至 2048/10/24)
/////////////////////////////////////////////////
//// SN最大可用版本,适于 0x9C 注册类型。适配免费升级天数,设置28年。
ULONG
MAX_VERSION_ID = VERID + (FREE_UPGRADE_DAYS/365);
/////////////////////////////////////////////////
int
char36ToInt(
char
ch);
int
stringToBytes(
char
* str,
char
* bytes);
int
checkName(
char
* name);
ULONG
getUncheck3(
ULONG
base,
ULONG
rlsdate);
ULONG
calcSN_Date();
ULONG
reverseSN_Date(
ULONG
sn_date);
UCHAR
getUncheck1(
char
ch);
UWORD getUncheck2(
ULONG
ul);
ULONG
getCheck3(
ULONG
sn_date,
ULONG
base);
///// 用户名处理
ULONG
getNameCheck(
char
* name,
ULONG
is_registed,
ULONG
free_days,
ULONG
user_count);
int
getDays(
int
base,
int
year2,
int
month2,
int
day2);
int
getDate(
int
days,
int
* year,
int
* month,
int
* day);
ULONG
decodeVersionID(
ULONG
encVerId);
ULONG
decodeBeginDays(
ULONG
encDays);
ULONG
decodeEndDays(
ULONG
encDays);
ULONG
decodeUsesLeft(
ULONG
encUsesLeft);
ULONG
encodeEndDays(
ULONG
days);
ULONG
encodeUsesLeft(
ULONG
usesLeft);
#define RESULT0 0x93
#define RESULT1 0xE7
//// black name list
char
* name_list[] = {
"999"
, NULL};
UCHAR
sn_int[] = {0x11, 0x11, 0x22, 0x22, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55};
//UCHAR sn_int[] = {0x11, 0x11, 0x22, 0x9C, 0x3E, 0x7A, 0xED, 0xBA, 0x55, 0x55};
int
main(
int
argc,
char
** argv) {
//// 注册用户名
char
name[] =
"solly"
;
////------------------------------------------------------------------------
//// version type
//ULONG license_type = 0x9C; //// 无免费升级,(注册时会提示过期日期为1970/1/2)
ULONG
license_type = 0xAC;
//// 有免费升级,注册码含有天数限制
//ULONG license_type = 0xFC; //// 扩展评估版,注册码含有天数限制
//// License user count
ULONG
license_user_count = 1000;
//// Site License, 0x03E8
//ULONG license_user_count = 1; //// single License, 0x01
//ULONG license_user_count = 999; //// multiply License (2~999)
////------------------------------------------------------------------------
//// 注册类型
ULONG
license_register_type = 1;
//// 正式注册版
ULONG
license_free_upgrage_end_of_date = 0;
//// 可免费升级的天数
////------------------------------------------------------------------------
int
black = checkName(name);
if
(black == RESULT1) {
printf
(
"Your name is in the black names list.\n"
);
return
0;
}
////------------------------------------------------------------------------
if
(license_type == 0xAC) {
//// full version
license_free_upgrage_end_of_date = RLSDATE + FREE_UPGRADE_DAYS;
/// 可升级天数
printf
(
"EndOf Date = 0x%08X\n"
, license_free_upgrage_end_of_date);
//// name_check = 0x8134B8E5
}
if
(license_type == 0xFC) {
license_register_type = 0;
//// 扩展评估版
license_user_count = 1;
license_free_upgrage_end_of_date = 0xFF;
////
FREE_UPGRADE_DAYS = 1825;
//// evaluation extrends days <= 0x721(1825 days, 5 years)
}
////------------------------------------------------------------------------
//// save license type
sn_int[3] = (
UCHAR
)license_type;
//// license type
//// params: "solly", 1, 0, 0x03E8
//// return: 0xBAED7A3E
//// ------------------------------
//// params: "solly', 0, 0xFF, 1
//// return: 0x4E2AC9B1
int
name_check = getNameCheck(name, license_register_type, license_free_upgrage_end_of_date, license_user_count);
printf
(
"name_check = 0x%08X\n"
, name_check);
sn_int[4] = (
UCHAR
)(name_check );
/// name check byte
sn_int[5] = (
UCHAR
)(name_check>>8 );
sn_int[6] = (
UCHAR
)(name_check>>16);
sn_int[7] = (
UCHAR
)(name_check>>24);
if
(license_type == 0xFC) {
//// evaluation license
//// 18000(0x4650) + 10549 == 0x6F85, 20000(0x4E20) + 10549 == 0x7755
int
eval_days_check = getUncheck3(name_check, RLSDATE);
printf
(
"evaluation_days_encoded = 0x%08X\n"
, eval_days_check);
sn_int[0] = (
UCHAR
)(eval_days_check );
sn_int[1] = (
UCHAR
)(eval_days_check>>8 );
sn_int[2] = (
UCHAR
)(eval_days_check>>16);
int
eval_days = getCheck3(name_check, eval_days_check);
printf
(
"evaluation extends days = 0x%08X\n"
, eval_days);
//// execute "Remove my license from this machine" first.
//// 最后注册日期 < eval_days < 最后注册成功有效日期
//// 并且 最后注册成功有效日期 < 当前日期
//// 并且 eval_days < Release_date + 0x721(1825 days,5年)
}
else
{
//// registered license
int
user_count = getUncheck2(license_user_count);
//// 0xA93B
printf
(
"user_count = 0x%08X\n"
, user_count);
sn_int[1] = (
UCHAR
)(user_count>>8) ^ sn_int[7];
//// hi
sn_int[2] = (
UCHAR
)user_count ^ sn_int[5];
//// lo
if
(license_type == 0xAC) {
ULONG
sn_date = getUncheck3(0x005B8C27, RLSDATE);
/// days
sn_date &= 0x00FFFFFF;
printf
(
"days_in_sn = 0x%08X\n"
, sn_date);
//// 0xFF9EBF52
//reverseSN_Date(sn_date); //// fill sn_int[0], sn_int[8], sn_int[9]
sn_int[0] = (
UCHAR
)(sn_date ) ^ sn_int[6];
sn_int[8] = (
UCHAR
)(sn_date>>8 ) ^ sn_int[4];
sn_int[9] = (
UCHAR
)(sn_date>>16) ^ sn_int[5];
//int check3 = getCheck3(sn_date, 0x005B8C27);
//printf("free upg days = 0x%08X\n", check3);
}
else
{
//// license_type == 0x9C
//// version_id check
sn_int[0] = getUncheck1(MAX_VERSION_ID) ^ sn_int[6];
/// version_id limited
printf
(
"version_id = 0x%08X\n"
, VERID);
}
}
////-------------------------------------------------------------------
//// test
// int a = char36ToInt('x');
// printf("x = %d\n", a);
/*
int year2, month2, day2;
int v10 = getDate(0x473C, &year2, &month2, &day2);
printf("v10.0 rls date: %d-%02d-%02d\n", year2, month2, day2);
int v10_1 = getDate(0x4770, &year2, &month2, &day2); //// 2020-01-27
printf("extends date1: %d-%02d-%02d\n", year2, month2, day2);
int v10_2 = getDate(0x7071, &year2, &month2, &day2); //// 2048-10-23
printf("extends date2: %d-%02d-%02d\n", year2, month2, day2);
int v08 = getDate(0x4389, &year2, &month2, &day2);
printf("v08.0 rls date: %d-%02d-%02d\n", year2, month2, day2);
int days1 = getDays(0, 2019, 12, 6);
printf("days1: 0x%08X\n", days1);
int days2 = getDays(0x473C+1, 2048, 10, 24);
printf("days2: %d\n", days2);
int days3 = getDays(0x0, 2020, 1, 28);
printf("days3: 0x%08X\n", days3);
/// {F73529D0-8362-B7A9-7B25-01538B3C632A}
int decodedVerId = decodeVersionID(0x64B8E106); //// 1689837830, return 0x0B
printf("Decoded Ver Id: 0x%08X\n", decodedVerId);
/// {E68B43C9-B732-8573-76A8-01AB75B3683A}
int begin_days = decodeBeginDays(0x0FE19483); /// 266441859, return 0x4770
printf("Begin Days: 0x%08X\n", begin_days);
//// {D75BD954-8B63-84BA-75B3-019654928EE7}
int end_days = decodeEndDays(0xA627AA1E); /// 2787617310, return 0x7071
printf("End Days: 0x%08X\n", end_days);
int enc_end_days = encodeEndDays(0x4770+0x1E); //// 30 days left
printf("Encoded End Days: %lu\n", enc_end_days);
//// {F175B8C0-783C-8BA0-7045-014CA847B302}
int usesLeft = decodeUsesLeft(0xA4CEFFB9); //// 2765029305, return 0x09
printf("Evaluation Uses Left: 0x%08X\n", usesLeft);
int encUsesLeft = encodeUsesLeft(100);
printf("Encoded Evaluation Uses Left: %lu\n", encUsesLeft);
*/
////-------------------------------------------------------------------
//// output sn
printf
(
"\n\nName: %s\n\n"
, name);
//// SN: 1113-3D22-3E7A-EDBA-5555
if
(license_type == 0xFC) {
//// evaluation license
printf
(
" SN: %02X%02X-%02X%02X-%02X%02X-%02X%02X\n"
,
sn_int[0], sn_int[1], sn_int[2], sn_int[3], sn_int[4],
sn_int[5], sn_int[6], sn_int[7]);
}
else
if
(license_type == 0xAC) {
//// full version
printf
(
" SN: %02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X\n"
,
sn_int[0], sn_int[1], sn_int[2], sn_int[3], sn_int[4],
sn_int[5], sn_int[6], sn_int[7], sn_int[8], sn_int[9]);
}
else
{
//// no free support, no free upgrade
printf
(
" SN: %02X%02X-%02X%02X-%02X%02X-%02X%02X\n"
,
sn_int[0], sn_int[1], sn_int[2], sn_int[3], sn_int[4],
sn_int[5], sn_int[6], sn_int[7]);
}
return
0;
}
int
checkName(
char
* name) {
for
(
int
i=0; ; i++) {
if
(name_list[i] == NULL) {
break
;
}
if
(
strcmp
(name, name_list[i])) {
continue
;
}
return
RESULT1;
/// 存在于黑名单中
}
return
0;
}
int
stringToBytes(
char
* str,
char
* bytes) {
int
idx = 0;
for
(
int
i=0; i<5; i++) {
char
ch_hi = str[idx++];
char
ch_lo = str[idx++];
bytes[i*2] = (char36ToInt(ch_hi) << 4) + char36ToInt(ch_lo);
ch_hi = str[idx++];
ch_lo = str[idx++];
bytes[i*2+1] = (char36ToInt(ch_hi) << 4) + char36ToInt(ch_lo);
idx++;
//// 跳过 '-' 分隔符
}
printf
(
"SN_INT[] = (%02X %02X - %02X %02X - %02X %02X - %02X %02X - %02X %02X)\n"
,
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4],
bytes[5], bytes[6], bytes[7], bytes[8], bytes[9]);
return
idx;
}
int
char36ToInt(
char
ch) {
//// 大写字母 O ,小写字母 o , 转换成数 字 0;
if
((ch==
'O'
) || (ch==
'o'
)) {
return
0;
}
//// 小写字母 l , 转换为数 字 1;
if
(ch ==
'l'
) {
return
1;
}
if
((ch>=
'0'
) && (ch<=
'9'
)) {
return
ch -
'0'
;
}
if
((ch>=
'A'
) && (ch<=
'Z'
)) {
return
ch -
'A'
+ 10;
}
if
((ch>=
'a'
) && (ch<=
'z'
)) {
return
ch -
'a'
+ 10;
}
return
0;
}
UCHAR
getCheck1(
char
ch) {
return
(
UCHAR
)(((ch ^ 0x18) + 0x3D) ^ 0xA7);
}
UWORD getCheck2(
ULONG
ul) {
ULONG
a = (UWORD)(((ul ^ 0x7892) + 0x4D30) ^ 0x3421);
if
(a % 11) {
return
0;
}
return
(UWORD)a;
}
/// 0x002F6BFC, 0x005B8C27
ULONG
getCheck3(
ULONG
sn_date,
ULONG
base) {
//// check1 == 0xFF
ULONG
check1 = (((sn_date ^ base ^ 0x0022C078) - 0x0002C175) ^ 0xFFE53167) & 0X00FFFFFF;
UINT64
check2 = (
UINT64
)check1 * 0xF0F0F0F1UL;
ULONG
check3 = (
UINT64
)(check2 >> (32+4));
ULONG
check4 = check3 << 4;
ULONG
check = check1 - (check4 + check3);
//printf("sn_date = 0x%08X\n", sn_date);
return
(check == 0)?check3:0;
//// check3 >= 0x473C 才正确
}
/*
ULONG getUncheck3(ULONG base, ULONG rlsdate) {
//ULONG check1 = 0x000000FF;
//return ((check1 ^ 0xFFE53167) + 0x0002C175) ^ 0x0022C078 ^ base;
ULONG check1_0 = 0;
ULONG check3_0 = 0;
ULONG check3_1 = rlsdate + FREE_UPGRADE_DAYS - 1;
do {
check3_1 ++; //// test
if(check3_1 < rlsdate) {
break;
}
//ULONG check4 = check3_1 << 4; /// check3_1 * 0x10
//check1_0 = check3_1 + check4; /// check1_0 = check3_1 * 0x11;
check1_0 = check3_1 * 0x11;
//UINT64 check2 = (UINT64)check1_0 * 0xF0F0F0F1UL;
UINT64 check2 = (UINT64)check3_1 * 0x1000000001ULL; /// 0xF0F0F0F1UL * 0x11
check3_0 = (UINT64)(check2 >> (32+4)); //// (UINT64)check3_1 * 0x01;
} while(check3_0 != check3_1);
//printf("check3_0 = 0x%08X\n", check3_0);
//printf("check3_1 = 0x%08X\n", check3_1);
return ((check1_0 ^ 0xFFE53167) + 0x0002C175) ^ 0x0022C078 ^ base;
}
*/
ULONG
getUncheck3(
ULONG
base,
ULONG
rlsdate) {
ULONG
end_of_date = rlsdate + FREE_UPGRADE_DAYS;
return
(((end_of_date * 0x11) ^ 0xFFE53167) + 0x0002C175) ^ 0x0022C078 ^ base;
}
ULONG
calcSN_Date() {
ULONG
a = sn_int[5] ^ sn_int[9];
ULONG
b = sn_int[4] ^ sn_int[8];
ULONG
c = sn_int[6] ^ sn_int[0];
ULONG
d = (((a<<8) + b)<<8) + c;
//// 0x002F6BFC
return
d;
}
ULONG
reverseSN_Date(
ULONG
sn_date) {
//// 0x002F6BFC
sn_int[0] = (
UCHAR
)(sn_date ) ^ sn_int[6];
sn_int[8] = (
UCHAR
)(sn_date>>8 ) ^ sn_int[4];
sn_int[9] = (
UCHAR
)(sn_date>>16) ^ sn_int[5];
return
0;
}
ULONG
getUncheck3(
ULONG
count) {
/// no using
return
0;
}
/// 0x3B
UCHAR
getUncheck1(
char
ch) {
return
(
UCHAR
)(((ch ^ 0xA7) - 0x3D) ^ 0x18);
}
/// license count
/// 1-single user license, 2~999-multiply user license, 1000-site license
UWORD getUncheck2(
ULONG
ul) {
ULONG
a = ul * 11;
//// ul <= 1745; /// 正常范围为 1~0x03E8 (1~1000)
return
(UWORD)(((a ^ 0x3421) - 0x4D30) ^ 0x7892);
}
//// params: "solly", 1, 0, 0x03E8 (edx, xxx, ecx, ebx)
//// return: 0xBAED7A3E
ULONG
getNameCheck(
char
* name,
ULONG
is_registed,
ULONG
free_days,
ULONG
user_count) {
ULONG
name_check = 0;
//// [ebp-04]
ULONG
idx3 = 0;
//// [ebp-10]
ULONG
idx2 = 0;
//// [ebp-0C]
int
n =
strlen
(name);
ULONG
idx4 = user_count * 15;
//// ebx
ULONG
idx1 = free_days * 17;
//// [ebp-08]
for
(
int
i=0; i<n; i++) {
ULONG
idx0 =
toupper
((
UCHAR
)name[i]);
name_check += name_tables[idx0];
if
(is_registed != 0) {
//// 正式注册版
name_check ^= name_tables[(idx0 + 0x0D) & 0xFF];
name_check *= name_tables[(idx0 + 0x2F) & 0xFF];
name_check += name_tables[(
UCHAR
)idx1];
name_check += name_tables[(
UCHAR
)idx4];
name_check += name_tables[(
UCHAR
)idx2];
}
else
{
//// 扩展评估版
name_check ^= name_tables[(idx0 + 0x3F) & 0xFF];
name_check *= name_tables[(idx0 + 0x17) & 0xFF];
name_check += name_tables[(
UCHAR
)idx1];
name_check += name_tables[(
UCHAR
)idx4];
name_check += name_tables[(
UCHAR
)idx3];
}
idx2 += 0x13;
idx1 += 0x09;
idx4 += 0x0D;
idx3 += 0x07;
}
return
name_check;
}
int
monthdays[2][12] = { { 31,28,31,30,31,30,31,31,30,31,30,31 },{ 31,29,31,30,31,30,31,31,30,31,30,31 } };
int
yeardays[2] = { 365,366 };
int
isLearYear(
int
year)
{
if
(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
return
1;
else
return
0;
}
//// from 1970-01-01
int
getDays(
int
base,
int
year2,
int
month2,
int
day2) {
int
sumdays = 0;
int
year1 = 1970;
int
month1 = 1;
int
day1 = 1;
if
(year1 == year2 && month1 == month2)
{
sumdays = day2 - day1;
}
else
{
if
(year1 == year2)
{
sumdays += monthdays[isLearYear(year1)][month1-1] - day1;
for
(
int
i = month1; i < month2-1; i++) {
sumdays += monthdays[isLearYear(year1)][i];
}
sumdays += day2;
}
else
{
sumdays += monthdays[isLearYear(year1)][month1-1] - day1;
for
(
int
i = month1; i < 12; i++) {
sumdays += monthdays[isLearYear(year1)][i];
}
for
(
int
i = year1 + 1; i < year2; i++) {
sumdays += yeardays[isLearYear(i)];
}
for
(
int
i = 0; i < month2 - 1; i++) {
sumdays += monthdays[isLearYear(year2)][i];
}
sumdays += day2;
}
}
return
sumdays - base;
}
int
getDate(
int
days,
int
* year,
int
* month,
int
* day) {
int
year1 = 1969;
int
month1= 0;
int
day1 = 0;
int
n = days;
do
{
year1 ++;
days = n;
n = days - yeardays[isLearYear(year1)];
}
while
(n>0);
n = days;
do
{
days = n;
n = days - monthdays[isLearYear(year1)][month1];
month1 ++;
//// base on 1
}
while
(n>0);
day1 = days;
day1 ++;
//// base on 1
////
*year = year1;
*month= month1;
*day = day1;
return
0;
}
//// decode versin_id from registry
ULONG
decodeVersionID(
ULONG
encVerId) {
// ULONG s = (((encVerId ^ 0xC7329CD4) - 0x2749B4FF) ^ 0x7C4280C0);
// ULONG d = (ULONG)(((UINT64)s * 0x34904333UL)>>32); //// 881869619
// int c = ((int)(((int)(s - d)>>1) + d)>>13); /// c = s / 0x3519
// ULONG a = c*0x3519;
// printf("s = 0x%08X, d = 0x%08X\n", s, d);
// return (s == a)?c:0;
return
(((encVerId ^ 0xC7329CD4) - 0x2749B4FF) ^ 0x7C4280C0) / 0x3519;
}
//// encode version_id to value of registry
ULONG
encodeVersionID(
ULONG
VerId) {
return
(((VerId * 0x3519) ^ 0x7C4280C0) + 0x2749B4FF) ^ 0xC7329CD4;
}
//// decode begin of days from registry
ULONG
decodeBeginDays(
ULONG
encDays) {
ULONG
c = (((encDays ^ 0x3F6197DA) - 0x656A0BB2) ^ 0xCB139CB7);
ULONG
d = (((
UINT64
)c * 0xB21642C9UL)>>36);
ULONG
a = d * 0x17;
return
(c == a)?d:0;
}
//// encode begin of days to value of registry
ULONG
encodeBeginDays(
ULONG
days) {
return
((((days * 0x17) ^ 0xCB139CB7) + 0x656A0BB2) ^ 0x3F6197DA);
}
//// decode end of days from registry
ULONG
decodeEndDays(
ULONG
encDays) {
// ULONG s = (((encDays ^ 0x18C5B72) - 0x329BD55E) ^ 0x7521BDEF);
// ULONG d = (ULONG)(((UINT64)s * 0x21FB7813)>>32); //// 881869619
// int c = ((int)(((int)(s - d)>>1) + d)>>6); /// c = s / 0x71
// ULONG a = c*0x71;
// printf("s = 0x%08X, d = 0x%08X\n", s, d);
// return (s == a)?c:0;
return
((((encDays ^ 0x18C5B72) - 0x329BD55E) ^ 0x7521BDEF) / 0x71);
}
//// encode end of days to value of registry
ULONG
encodeEndDays(
ULONG
days) {
return
((((days * 0x71) ^ 0x7521BDEF) + 0x329BD55E) ^ 0x18C5B72);
}
//// {F175B8C0-783C-8BA0-7045-014CA847B302}
ULONG
decodeUsesLeft(
ULONG
encUsesLeft) {
// ULONG c = (((encUsesLeft ^ 0x7328B47A) - 0x18B3C906) ^ 0xBF32ABCE);
// UINT64 a = (UINT64)c * 0x379609FD;
// ULONG d = (ULONG)(a>>(32+8)); /// d = c / 0x049B
// ULONG A = d * 0x049B;
// return (c == A)?d:0;
return
(((encUsesLeft ^ 0x7328B47A) - 0x18B3C906) ^ 0xBF32ABCE) / 0x049B;
}
//// {F175B8C0-783C-8BA0-7045-014CA847B302}
//// 试用使用次数
ULONG
encodeUsesLeft(
ULONG
usesLeft) {
return
((((usesLeft * 0x049B) ^ 0xBF32ABCE) + 0x18B3C906) ^ 0x7328B47A);
}
代码内还有一些多余函数和代码,是在跟踪分析过程中进行测试或解码用的,没有删除。
另外,还有一个文件是用户名转换时的查表数据,文件名为:name_tables.h,代码如下:#ifndef __name_tables__
#define __name_tables__
long
name_tables[] = {
0x39CB44B8, 0x23754F67, 0x5F017211, 0x3EBB24DA, 0x351707C6, 0x63F9774B, 0x17827288, 0x0FE74821,
0x5B5F670F, 0x48315AE8, 0x785B7769, 0x2B7A1547, 0x38D11292, 0x42A11B32, 0x35332244, 0x77437B60,
0x1EAB3B10, 0x53810000, 0x1D0212AE, 0x6F0377A8, 0x43C03092, 0x2D3C0A8E, 0x62950CBF, 0x30F06FFA,
0x34F710E0, 0x28F417FB, 0x350D2F95, 0x5A361D5A, 0x15CC060B, 0x0AFD13CC, 0x28603BCF, 0x3371066B,
0x30CD14E4, 0x175D3A67, 0x6DD66A13, 0x2D3409F9, 0x581E7B82, 0x76526B99, 0x5C8D5188, 0x2C857971,
0x15F51FC0, 0x68CC0D11, 0x49F55E5C, 0x275E4364, 0x2D1E0DBC, 0x4CEE7CE3, 0x32555840, 0x112E2E08,
0x6978065A, 0x72921406, 0x314578E7, 0x175621B7, 0x40771DBF, 0x3FC238D6, 0x4A31128A, 0x2DAD036E,
0x41A069D6, 0x25400192, 0x00DD4667, 0x6AFC1F4F, 0x571040CE, 0x62FE66DF, 0x41DB4B3E, 0x3582231F,
0x55F6079A, 0x1CA70644, 0x1B1643D2, 0x3F7228C9, 0x5F141070, 0x3E1474AB, 0x444B256E, 0x537050D9,
0x0F42094B, 0x2FD820E6, 0x778B2E5E, 0x71176D02, 0x7FEA7A69, 0x5BB54628, 0x19BA6C71, 0x39763A99,
0x178D54CD, 0x01246E88, 0x3313537E, 0x2B8E2D17, 0x2A3D10BE, 0x59D10582, 0x37A163DB, 0x30D6489A,
0x6A215C46, 0x0E1C7A76, 0x1FC760E7, 0x79B80C65, 0x27F459B4, 0x799A7326, 0x50BA1782, 0x2A116D5C,
0x63866E1B, 0x3F920E3C, 0x55023490, 0x55B56089, 0x2C391FD1, 0x2F8035C2, 0x64FD2B7A, 0x4CE8759A,
0x518504F0, 0x799501A8, 0x3F5B2CAD, 0x38E60160, 0x637641D8, 0x33352A42, 0x51A22C19, 0x085C5851,
0x032917AB, 0x2B770AC7, 0x30AC77B3, 0x2BEC1907, 0x035202D0, 0x0FA933D3, 0x61255DF3, 0x22AD06BF,
0x58B86971, 0x5FCA0DE5, 0x700D6456, 0x56A973DB, 0x5AB759FD, 0x330E0BE2, 0x5B3C0DDD, 0x495D3C60,
0x53BD59A6, 0x4C5E6D91, 0x49D9318D, 0x103D5079, 0x61CE42E3, 0x7ED5121D, 0x14E160ED, 0x212D4EF2,
0x270133F0, 0x62435A96, 0x1FA75E8B, 0x6F092FBE, 0x4A000D49, 0x57AE1C70, 0x004E2477, 0x561E7E72,
0x468C0033, 0x5DCC2402, 0x78507AC6, 0x58AF24C7, 0x0DF62D34, 0x358A4708, 0x3CFB1E11, 0x2B71451C,
0x77A75295, 0x56890721, 0x0FEF75F3, 0x120F24F1, 0x01990AE7, 0x339C4452, 0x27A15B8E, 0x0BA7276D,
0x60DC1B7B, 0x4F4B7F82, 0x67DB7007, 0x4F4A57D9, 0x621252E8, 0x20532CFC, 0x6A390306, 0x18800423,
0x19F3778A, 0x462316F0, 0x56AE0937, 0x43C2675C, 0x65CA45FD, 0x0D604FF2, 0x0BFD22CB, 0x3AFE643B,
0x3BF67FA6, 0x44623579, 0x184031F8, 0x32174F97, 0x4C6A092A, 0x5FB50261, 0x01650174, 0x33634AF1,
0x712D18F4, 0x6E997169, 0x5DAB7AFE, 0x7C2B2EE8, 0x6EDB75B4, 0x5F836FB6, 0x3C2A6DD6, 0x292D05C2,
0x052244DB, 0x149A5F4F, 0x5D486540, 0x331D15EA, 0x4F456920, 0x483A699F, 0x3B450F05, 0x3B207C6C,
0x749D70FE, 0x417461F6, 0x62B031F1, 0x2750577B, 0x29131533, 0x588C3808, 0x1AEF3456, 0x0F3C00EC,
0x7DA74742, 0x4B797A6C, 0x5EBB3287, 0x786558B8, 0x00ED4FF2, 0x6269691E, 0x24A2255F, 0x62C11F7E,
0x2F8A7DCD, 0x643B17FE, 0x778318B8, 0x253B60FE, 0x34BB63A3, 0x5B03214F, 0x5F1571F4, 0x1A316E9F,
0x7ACF2704, 0x28896838, 0x18614677, 0x1BF569EB, 0x0BA85EC9, 0x6ACA6B46, 0x1E43422A, 0x514D5F0E,
0x413E018C, 0x307626E9, 0x01ED1DFA, 0x49F46F5A, 0x461B642B, 0x7D7007F2, 0x13652657, 0x6B160BC5,
0x65E04849, 0x1F526E1C, 0x5A0251B6, 0x2BD73F69, 0x2DBF7ACD, 0x51E63E80, 0x5CF2670F, 0x21CD0A03,
0x5CFF0261, 0x33AE061E, 0x3BB6345F, 0x5D814A75, 0x257B5DF4, 0x0A5C2C5B, 0x16A45527, 0x16F23945
};
#endif
该软件在注册码验证时,还会进行网络验证,因此,还需要进行网络验证的处理,软件会向其服务器发送一个如下的http字符串进行验证:#网络验证字符串
[url=http:
//www
.sweetscape.com
/cgibin/010editor_check_license_9b
.php?t=2E65DB0868&
sum
=1F4BF35D3CFC5A59865F2CF44167FEC268EDB719D6CD0ADE&
id
=0&chk=14611&typ=0]http:
//www
.sweetscape.com
/cgibin
... d=0&chk=14611&typ=0[
/url
]
其中有几个参数説明如下:
1、t=2E65DB0868,这个是由用户名字符串编码加密而来的。
2、sum=1F4BF35D3CFC5A59865F2CF44167FEC268EDB719D6CD0ADE,这个是由上面的 t 参数与注册码一起加密而来的。
3、id=0,这个 id 是每次验证返回的验证成功值,会保存到注册表,不过第一次验证时是 0,后面验证时取的注册表键值。如果验证成功,id大于0了。
4、chk=14611,这个值也是由用户名字符串编码来的,16bits整数。
5、typ=0,这个是一个bool数据,当 typ=0,则进行网验证,typ=1,则发送验证已完成给服务器(发送字符串"1On_HttpCheckLicenseFinished(char*,int)"),并且也不再验证返回的数据。
以上是在线网络验证参数分析,下面进行具体的验证过程分析,另外,不对以上几个用户名和注册码的加密进行分析,因为那个不是客户端的事,服务端完成的工作我们不必去多费心分析。我们要做就是不管其发送什么内容,我们都给他回复一个“验证成功”的标志!
如下图,先定位网络验证的入口:
[Asm] 纯文本查看 复制代码
其实就在我们前面注册算法函数的后面一点点,如上图所示,传入网络验证函数参数只有一个:typ=0,表示需要进行验证。代码如下:016FB7D3 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第三次引用[0371805C]
016FB7D9 . 68 3C470000
push
0x473C
; 18236
016FB7DE . 6A 0B
push
0xB
; 11
016FB7E0 . E8 07F350FF
call
00C0AAEC
; 注册码检查
016FB7E5 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第四次引用[0371805C]
016FB7EB . 8BD8
mov
ebx
,
eax
; 注册标志 ebx == eax
016FB7ED . 68 3C470000
push
0x473C
; 18236
016FB7F2 . 6A 0B
push
0xB
; 11
016FB7F4 . E8 5FE650FF
call
00C09E58
; 注册码检查 及 试用期扩充的检查
016FB7F9 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
; 第五次引用[0371805C]
016FB7FF . 8BF8
mov
edi
,
eax
; 版本标识 edi == eax
016FB801 . 81FB E7000000
cmp
ebx
, 0xE7
; 成功标志3
016FB807 . 0F84 F3000000
je
016FB900
016FB80D . 8379 2C 00
cmp
dword
ptr
[
ecx
+0x2C], 0x0
; 是否进行网络验证,0-不验证,1-验证
016FB811 . 0F84 E9000000
je
016FB900
016FB817 . 6A 00
push
0x0
016FB819 . 8BCE
mov
ecx
,
esi
016FB81B . E8 BB6D50FF
call
00C025DB
; 网络验证
016FB820 . 85C0
test
eax
,
eax
; eax 为返回验证id
016FB822 . 0F89 8C000000
jns
016FB8B4
; eax非负,跳转再次判断
进入该函数,如下图所示:
首先取得用户名,如下图所示,取得界面上的用户名字符串:
对用户名字符串进行类型转换并转成字符数组,如下图所示:
接下来取注册码,一样也进行转换成字符数组之类的操作,如下图所示:
接下来,对用户名进行编码,得到一个整数,如下图所示,编码方法不管,这个编码是 chk 参数:
接下一大段代码就是将分而在软件中不同位置的一些常量字符串进行拼接,拼接出一个 http 连接串,如下图所示:
上图中,OD 数据区显示的就是拼接好的字符串的前面一部分。
接下来,又是对用户名编码,这次是编码成一个字符串,作为 t 参数,如下图所示:
上面完成后,然后将 t 参数与注册码进行一起编码为一个更长的字符串,作为 sum 参数,如下图所示:
如下图所示,在 OD 的数据区显示,已经将上面的编码,添加到 http 连接串后面了:
接下来就是ID参数的处理,第一次验证时,注册表中没有数据,默认就是 t=0,如下图所示:
接下来,就是将最前面的用户名编码后的整数也添加上来,作为 chk 参数,如下图所示:
这个操作完成,只有最后一个参数了,typ 参数是函数传入的,当前传入是的 0,如下图所示:
这样,整个 http 验证字符串就拼接完成了,下面再转换成 QString 类型 Unicode 字符串,如下图所示:
我们先看看,手动发送一下,是什么效果,将这一串(没有转换成 unicode前的)字符串复制出来,在 IE 中直接连接,如下图所示:
[C++] 纯文本查看 复制代码
直接返回是的 <ss>invalid</ss>,表示验证失败,注册码无效。
下面我们进行破*解,先在 hosts 文件中加上如下数据:
127.0.0.1 www.sweetscape.com
然后在管理员权限下,在命令行运行如下命令,刷新 DNS:
ipconfig /flushdns
切记不要录错了。
下面,从网上抓一段 http server 代码,稍加改造,自己做一个验证服务器,代码如下:#include <iostream>
#include <stdio.h>
#include <winsock.h>
#pragma comment(lib,"WSock32.Lib") //GCC 需要指定库 "libwsock32.a"
int
main(
int
argc,
char
** argv)
{
WSADATA wsaData;
if
(WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
fprintf
(stderr,
"WSAStartup failed.\n"
);
exit
(1);
}
printf
(
"WebServer started...\r\nvisit [url=http://127.0.0.1:80]http://127.0.0.1:80[/url]\r\n\r\n"
);
printf
(
"Exit the http server, visit [url=http://127.0.0.1:80/?quit=1]http://127.0.0.1:80/?quit=1[/url]\r\n"
);
SOCKET server_socket;
//服务器的socket
SOCKET acc_socket;
//接收到的用户连接的socket
int
sock_size =
sizeof
(
struct
sockaddr_in);
struct
sockaddr_in client_addr;
//客户连接信息
struct
sockaddr_in server_addr;
//客户连接信息
server_socket = socket(PF_INET, SOCK_STREAM, 0);
if
(server_socket == -1) {
//如果返回值为-1 则出错
return
-1;
}
/*
* 填充服务器连接信息
*/
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(80);
server_addr.sin_addr.s_addr = inet_addr(
"127.0.0.1"
);
//本地地址
memset
(&(server_addr.sin_zero),
'\0'
, 8);
if
(bind(server_socket, (
struct
sockaddr *)&server_addr,
sizeof
(server_addr)) == -1) {
//绑定服务 如果出错 则返回-1
printf
(
"bind error!\n"
);
return
-1;
}
if
(listen(server_socket, 10) == -1 ) {
//开始监听
printf
(
"listen error!\n"
);
return
-1;
}
if
(server_socket == -1) {
//创建socket出错
printf
(
"Server exception!\n"
);
// exit(2);
}
while
(
true
) {
acc_socket = accept(server_socket, (
struct
sockaddr *)&client_addr, &sock_size);
//接收连接
int
numbytes;
char
recv_buff[1000];
if
((numbytes=recv(acc_socket, recv_buff, 999, 0)) == -1) {
perror
(
"recv error!\n"
);
// exit(1);
numbytes = 0;
}
recv_buff[numbytes] =
'\0'
;
printf
(
"recv data:\n %s\n"
, recv_buff);
//show data
////////// send data
char
reponse[] =
"HTTP/1.0 200 OK\r\n"
\
"Content-type: text/html\r\n"
\
"Content-length: 26\r\n\r\n"
\
"<ss><id>78787878</id></ss>"
;
//// length == 26, content == "<ss><id>78787878</id></ss>"
printf
(
"send data:\n %s\n"
, reponse);
//show data
send(acc_socket, reponse,
strlen
(reponse), 0);
//// close
shutdown(acc_socket, 2);
/// 2-SD_BOTH
closesocket(acc_socket);
/////
if
(
strstr
(recv_buff,
"?quit=1"
)) {
//closesocket(acc_socket);
break
;
}
printf
(
"\r\n\r\n"
);
}
closesocket(server_socket);
WSACleanup();
return
0;
}
注意,这只是一个简单的验证,不过很有效。另外,GCC 编译时,要加上 libwsock32.a 库。
启动我们自己的验证服务器后,就可以进入下一步了。
[Asm] 纯文本查看 复制代码
如上图所示,发送了网络连接字符串。如果 bl ==1,表示成功发送和接收了。这是成功的第一步。这一段关键代码如下:016FD601 . 8B0D 6C80BD03
mov
ecx
,
dword
ptr
[0x3BD806C]
016FD607 . 8D85 D8EFFFFF
lea
eax
,
dword
ptr
[
ebp
-0x1028]
016FD60D . 53
push
ebx
016FD60E . 6A 01
push
0x1
016FD610 . 50
push
eax
016FD611 . 8D85 E4EFFFFF
lea
eax
,
dword
ptr
[
ebp
-0x101C]
016FD617 . C745 FC 05000000
mov
dword
ptr
[
ebp
-0x4], 0x5
016FD61E . 50
push
eax
016FD61F . 8D85 ECEFFFFF
lea
eax
,
dword
ptr
[
ebp
-0x1014]
016FD625 . 50
push
eax
016FD626 . E8 1F7C50FF
call
00C0524A
; 发送http数据
016FD62B . 85C0
test
eax
,
eax
; eax==0 通讯正常
016FD62D . C745 FC FFFFFFFF
mov
dword
ptr
[
ebp
-0x4], -0x1
016FD634 . 8D8D ECEFFFFF
lea
ecx
,
dword
ptr
[
ebp
-0x1014]
016FD63A . 0F94C3
sete
bl
; bl==1,发送接收正常
016FD63D . FF15 B094BD03
call
dword
ptr
[<&Qt5Core.QString::~QString>]
; Qt5Core.QXmlStreamStringRef::~QXmlStreamStringRef
016FD643 . 84DB
test
bl
,
bl
016FD645 .^ 0F84 ABF9FFFF
je
016FCFF6
; 失败则跳转
016FD64B . FFB5 D8EFFFFF
push
dword
ptr
[
ebp
-0x1028]
; length
016FD651 . 8B0D 5C80BD03
mov
ecx
,
dword
ptr
[0x3BD805C]
016FD657 . FFB5 E4EFFFFF
push
dword
ptr
[
ebp
-0x101C]
; 接收到的数据
016FD65D . E8 046F50FF
call
00C04566
; 解析收到的数据
016FD662 . FFB5 E4EFFFFF
push
dword
ptr
[
ebp
-0x101C]
016FD668 . 8BF0
mov
esi
,
eax
016FD66A . E8 6106A200
call
<
jmp
.&MSVCR120.operator delete[]>
016FD66F . 83C4 04
add
esp
, 0x4
016FD672 . 8BC6
mov
eax
,
esi
016FD674 . EB 47
jmp
short
016FD6BD
; 结束了
如果验证失败,则收到如下数据(这是在改 hosts 文件 之前从官网接收的):
如果验证正确,则接收到如下图所示数据(这是从我的验证服务器接收的):
验证服务器显示如下:
可以清楚的看到接收和发送的数据。
接收到数据后,就会对数据进行解析,调用如下函数:
如上图所示,先解析<ss></ss>标签。检查接收到的字符串是不是 <ss>error</ss>和<ss>invalid</ss>,如果不是,则保存一个标志到注册表,表示验证成功:
[Asm] 纯文本查看 复制代码
这一段关键代码如下:01DDE870 |> \1BC0
sbb
eax
,
eax
01DDE872 |. 83C8 01
or
eax
, 0x1
01DDE875 |> 85C0
test
eax
,
eax
01DDE877 |. 75 0B
jnz
short
01DDE884
01DDE879 |. C746 2C 01000000
mov
dword
ptr
[
esi
+0x2C], 0x1
01DDE880 |. 6A 01
push
0x1
; verId = 1,错误
01DDE882 |. EB 09
jmp
short
01DDE88D
01DDE884 |> C746 2C 00000000
mov
dword
ptr
[
esi
+0x2C], 0x0
01DDE88B |. 6A 00
push
0x0
; VerId = 0, 正确
01DDE88D |> E8 85A1E2FE
call
00C08A17
; get registry object
01DDE892 |. 8BC8
mov
ecx
,
eax
01DDE894 |. E8 E35FE2FE
call
00C0487C
; 写注册表数据(verId),保存在线验证的结果
01DDE899 |. 837E 2C 01
cmp
dword
ptr
[
esi
+0x2C], 0x1
01DDE89D |. 75 14
jnz
short
01DDE8B3
; 不等于1正确, 跳转
具体的注册表标志保存位置如下:
如果是 1690216811,表示成功,这个数 字解码后是 0,如果是 1690236428,则表示失败,这个数 字解码后是 1。
保存完注册表后,就对“<id>78787878</id>”解析,如下图所示:
如果没有发现问题,则将“<id>78787878</id>”中的数据“78787878”解析出来,如上图所示。
如果解析成功,则将解析出来的 id 转换成一个整数,并保存到注册表中,并且是明文件保存的,没有加密,如下图所示:
解析函数完成后,返回我们前面的拼接函数,如果到这里还没有错误,网络就验证就完成了,如下图所示,要退出函数了:
至此,网络验证成功了。
注册表中保存的ID数据如下图所示:
[Shell] 纯文本查看 复制代码
因此,在注册表中手动添加好这两条注册信息,就可以完成离线网络验证:Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\SweetScape\010 Editor\CLASSES\CLSID]
[HKEY_CURRENT_USER\Software\SweetScape\010 Editor\CLASSES\CLSID\{F531AB73-8493-A83D-7C94-016296B3CF17}]
[HKEY_CURRENT_USER\Software\SweetScape\010 Editor\CLASSES\CLSID\{F531AB73-8493-A83D-7C94-016296B3CF17}\InProcServer32A]
"ThreadingModel"
=
"78787878"
[HKEY_CURRENT_USER\Software\SweetScape\010 Editor\CLASSES\CLSID\{FA1395FC-83C3-0732-7D11-0134937462A0}]
[HKEY_CURRENT_USER\Software\SweetScape\010 Editor\CLASSES\CLSID\{FA1395FC-83C3-0732-7D11-0134937462A0}\InProcServer32A]
"ThreadingModel"
=
"1690216811"
前面説了,网络验证函数的参数为1时不会验证,只会 post 一条字符串 "1On_HttpCheckLicenseFinished(char*,int)" 给服务器。typ=1的网络验证函数调用在如下位置:
好象这个验证是界面显示刷新或定时器里用到了,没有深究,不一定准确。
另外,验证完成后,就可以恢复 hosts 文件了。
全部分析完毕!!!!
发表吐槽
你肿么看?
既然没有吐槽,那就赶紧抢沙发吧!