【软件名称】 xx外挂浮点注册算法分析
【文章作者】 仙剑太郎
【网站地址】 中国X黑客小组 www.CnXHacker.com
【破解工具】 Ollydbg
【软件语言】 Microsoft Visual C++
【破解声明】 我是一只小菜鸟,偶的一点心得,愿与大家分享:)
-------------------------------------------------------------
【破解过程】
一个冒险岛的小外挂,无壳,VC写的,偶们菜鸟拿来做练习最好不过了~
软件使用了浮点运算,正好复习一下浮点指令:)好,下面开工了..
我这里的机器码是DFMPHHCCF4,随便输入注册码,根据出错提示很容易找到关键地方,向上翻,会找到算法开始的地方,如下
004028B9 . 33C0
xor eax,
eax
004028BB . 8B35 >
mov esi,
dword ptr ds:[41F54C]
; 038版本?0041F560
004028C1 . 89442>
mov dword ptr ss:[
esp+2D],
eax
004028C5 . 33D2
xor edx,
edx
004028C7 . 89442>
mov dword ptr ss:[
esp+31],
eax
004028CB . 89542>
mov dword ptr ss:[
esp+44],
edx
004028CF . 89442>
mov dword ptr ss:[
esp+35],
eax
004028D3 . 88542>
mov byte ptr ss:[
esp+2C],
dl
004028D7 . 66:89>
mov word ptr ss:[
esp+39],
ax
004028DC . 89742>
mov dword ptr ss:[
esp+10],
esi
004028E0 . 88442>
mov byte ptr ss:[
esp+3B],
al
004028E4 . 8B7C2>
mov edi,
dword ptr ss:[
esp+18]
; 机器码指针放进edi
004028E8 . 83C9 >
or ecx,FFFFFFFF
004028EB . 33ED
xor ebp,
ebp
004028ED . C6442>
mov byte ptr ss:[
esp+44],1
004028F2 . F2:AE
repne scas byte ptr es:[
edi]
; 计算机器码长度
004028F4 . F7D1
not ecx
004028F6 . 49
dec ecx ; 长度为0xA
004028F7 . 89542>
mov dword ptr ss:[
esp+14],
edx
004028FB . 8BF9
mov edi,
ecx ; 保存机器码长度
004028FD . 0F84 >
je 038版本?00402A9E
00402903 . BE 01>
mov esi,1
00402908 . 3BFE
cmp edi,
esi ; 机器码长度是否小于1?
0040290A . 89742>
mov dword ptr ss:[
esp+1C],
esi
0040290E . 0F8C >
jl 038版本?004029C3
; 小于则over
00402914 . DD05 >
fld qword ptr ds:[41A768]
; 将0.0放入st0
0040291A > 8B4C2>
mov ecx,
dword ptr ss:[
esp+18]
; 循环的开始,机器码指针保存到ecx中
0040291E . DB442>
fild dword ptr ss:[
esp+1C]
; 第N位放到st0
00402922 . 8A5C3>
mov bl,
byte ptr ds:[
ecx+
esi-1]
; 机器码逐字符ASCII放到bl
00402926 . 0FBED>
movsx edx,
bl ; 保存到edx中
00402929 . DD5C2>
fstp qword ptr ss:[
esp+24]
; 取st0值到esp+0x24中,出栈
0040292D . 89542>
mov dword ptr ss:[
esp+1C],
edx ; 将第N个ASCII放到esp+0x1c
00402931 . DB442>
fild dword ptr ss:[
esp+1C]
; 第N个ASCII整数值到st0中
00402935 . 0FBEC>
movsx eax,
bl ; 第N个ASCII数值保存到eax
00402938 . D9FA
fsqrt ; 取st0第N个ASCII平方根
0040293A . 0FAFC>
imul eax,
esi ; 第N个ASCII*N
0040293D . DC4C2>
fmul qword ptr ss:[
esp+24]
; st0*N=st0
00402941 . DC05 >
fadd qword ptr ds:[41A760]
; 平方根的值+1
00402947 . 0FAFC>
imul eax,
esi ; 已经*N的ASCII再*N
0040294A . 89442>
mov dword ptr ss:[
esp+1C],
eax ; *完的结果放到esp+0x1c里
0040294E . DB442>
fild dword ptr ss:[
esp+1C]
; *完的结果到st0中,以十进制表示
00402952 . DEC9
fmulp st(1),
st ; *完的结果再*st1,即st0=st0*st1
00402954 . D8C1
fadd st,
st(1)
; st0+st1
00402956 . E8 75>
call 038版本?00403CD0
; 将st0取整,st0出栈
0040295B . 99
cdq
0040295C . DDD8
fstp st ; 出栈一次
0040295E . B9 A0>
mov ecx,186A0
; 常数100000
00402963 . F7F9
idiv ecx ; 取整后的数余100000
00402965 . 89542>
mov dword ptr ss:[
esp+14],
edx ; 余数为密钥1,Key1★
00402969 . 0FBED>
movsx edx,
bl ; 第N位ASCII放到ebx
0040296C . 89542>
mov dword ptr ss:[
esp+1C],
edx ; 第N位ASCII放到esp+1c
00402970 . DB442>
fild dword ptr ss:[
esp+1C]
; 第N位ASCII放到存进st0,十进制表示
00402974 . DD05 >
fld qword ptr ds:[41A758]
; 将2.00放到st0,作为底数
0040297A . E8 31>
call 038版本?00403AB0
; 求对数LOG等运算的CALL,跟进
================================
0040297A此
CALL用来求对数等其它运算,F7跟进看看,来到下面:
================================
00403AB0 /$ 83EC >
sub esp,10
00403AB3 |. D9C9
fxch st(1)
; st0和st1互换
00403AB5 |. DD1C2>
fstp qword ptr ss:[
esp]
; st0出栈
00403AB8 |. DD542>
fst qword ptr ss:[
esp+8]
; 将浮点2.00以实数形式放到esp+8,值为0
00403ABC |. 8B442>
mov eax,
dword ptr ss:[
esp+C]
; 将40000000放eax
00403AC0 |. E8 0D>
call 038版本?00403AD2
; 求LOG的CALL,跟进
00403AC5 |. 83C4 >
add esp,10
00403AC8 \. C3
retn
================================
再跟进一下00403AC0,来到下面:
================================
00403AD2 $ 8BC8
mov ecx,
eax
00403AD4 . 50
push eax
00403AD5 . 9B
wait
00403AD6 . D93C2>
fstcw word ptr ss:[
esp]
00403AD9 . 66:81>
cmp word ptr ss:[
esp],27F
00403ADF . 74 05
je short 038版本?00403AE6
00403AE1 . E8 4F>
call 038版本?00406935
00403AE6 > 81E1 >
and ecx,7FF00000
; 40000000 and 7FF00000
00403AEC . 8D542>
lea edx,
dword ptr ss:[
esp+8]
00403AF0 . 81F9 >
cmp ecx,7FF00000
00403AF6 . 0F84 >
je 038版本?00403B99
00403AFC . E8 64>
call 038版本?00406965
00403B01 . 0F84 >
je 038版本?00403B95
00403B07 . A9 00>
test eax,7FF00000
00403B0C . 0F84 >
je 038版本?00403C08
00403B12 > 8A4C2>
mov cl,
byte ptr ss:[
esp+F]
; 40?
00403B16 . 80E1 >
and cl,80
; 40 and 80=0
00403B19 . 0F85 >
jnz 038版本?00403C80
00403B1F > D9F1
fyl2x ; st1 * log2(st0)
00403B21 . E8 FA>
call 038版本?00406920
; 四舍五入等运算,跟进
================================
00403B21的
CALL也是关键的计算,F7跟进,来到下面:
================================
00406920 /$ D9C0
fld st
00406922 |. D9FC
frndint ; 四舍五入
00406924 |. DCE1
fsubr st(1),
st ; st1-st0,负值
00406926 |. D9C9
fxch st(1)
; 结果交换
00406928 |. D9E0
fchs ; 求负数,前面加-
0040692A |. D9F0
f2xm1 ; st(0) <- (2 ^ st(0)) - 1
0040692C |. D9E8
fld1 ; 1放入st0
0040692E |. DEC1
faddp st(1),
st ; 相加然后出栈
00406930 |. D9FD
fscale ; 2 ^ st1*st(0)
00406932 |. DDD9
fstp st(1)
; 出栈
00406934 \. C3
retn
================================
上面
CALL的运算流程一目了然,我们
retn回去,中途的很多东西都是编译器生成的容错代码,与注册码算法无关,所以中间有些东西不用理会
继续返回到这里:
================================
0040297F . DC4C2>
fmul qword ptr ss:[
esp+24]
; 求对数的结果*N
00402983 . E8 48>
call 038版本?00403CD0
; 将st0取整
00402988 . DB442>
fild dword ptr ss:[
esp+14]
; 将余数放st0
0040298C . 8BCE
mov ecx,
esi ; esi=N
0040298E . 0FAFC>
imul ecx,
ebp ; ebp为新变量*N
00402991 . D9C0
fld st ; 入栈st1
00402993 . D9FA
fsqrt ; *完的结果开平方
00402995 . 03C1
add eax,
ecx ; 开平方的结果与*的结果相加
00402997 . B9 A0>
mov ecx,186A0
; 常数10000
0040299C . 99
cdq
0040299D . F7F9
idiv ecx ; 相加后的数余100000
0040299F . 8BEA
mov ebp,
edx ; 保存结果为密钥2,Key2★
004029A1 . E8 2A>
call 038版本?00403CD0
; 取整
004029A6 . 03C5
add eax,
ebp ; 取整结果与相加结果再加
004029A8 . B9 A0>
mov ecx,186A0
; 常数10000
004029AD . 99
cdq
004029AE . F7F9
idiv ecx ; 余数为密钥3,Key3★
004029B0 . 46
inc esi ; esi+1
004029B1 . 3BF7
cmp esi,
edi ; 完了没?
004029B3 . 89742>
mov dword ptr ss:[
esp+1C],
esi ; esi放到esp+1c保存
004029B7 .^ 0F8E >
jle 038版本?0040291A
; 未完跳回去,完了则向下
================================
这里根据机器码算出来的三个注册密钥已经生成完毕,我的机器码对应的三个密钥是:
Key1=0x11F73 , Key2=0x18088 , Key3=18197
下面还会跟据三个密钥生成一个15
byte的表:
================================
004029BD . 8B5C2>
mov ebx,
dword ptr ss:[
esp+20]
; 密钥生成完毕,到表的生成
004029C1 . DDD8
fstp st ; st0出栈
004029C3 > 33C0
xor eax,
eax ; 清零,第一段表开始
004029C5 > 8BC8
mov ecx,
eax ; 前5位表,累加值存入ecx
004029C7 . 8B742>
mov esi,
dword ptr ss:[
esp+14]
; 取浮点运算结果HEX放到esi中
004029CB . 0FAFC>
imul ecx,
eax ; 循环相乘
004029CE . 0FAFC>
imul ecx,
eax ; 再乘一次
004029D1 . 8D4C3>
lea ecx,
dword ptr ds:[
ecx+
esi+1F]
; esi=Key1,累加+0x1F
004029D5 . 81E1 >
and ecx,8000007F
; 与运算and 0x800007F
004029DB . 79 05
jns short 038版本?004029E2
; 跳了
004029DD . 49
dec ecx
004029DE . 83C9 >
or ecx,FFFFFF80
004029E1 . 41
inc ecx
004029E2 > 884C0>
mov byte ptr ss:[
esp+
eax+2C],
cl ; 保存到表的第一段中★
004029E6 . 40
inc eax ; eax递增
004029E7 . 83F8 >
cmp eax,5
; 少于5?
004029EA .^ 7C D9
jl short 038版本?004029C5
; 小于5则继续循环
004029EC . B8 05>
mov eax,5
; 第二段表开始
004029F1 > 8BC8
mov ecx,
eax ; 以下同上
004029F3 . 0FAFC>
imul ecx,
eax
004029F6 . 0FAFC>
imul ecx,
eax
004029F9 . 8D4C2>
lea ecx,
dword ptr ds:[
ecx+
ebp+1F]
; ebp=Key2,其余同上
004029FD . 81E1 >
and ecx,8000007F
00402A03 . 79 05
jns short 038版本?00402A0A
; 跳了
00402A05 . 49
dec ecx
00402A06 . 83C9 >
or ecx,FFFFFF80
00402A09 . 41
inc ecx
00402A0A > 884C0>
mov byte ptr ss:[
esp+
eax+2C],
cl ; 保存到表的第二段中★
00402A0E . 40
inc eax
00402A0F . 83F8 >
cmp eax,0A
; 到10了没?
00402A12 .^ 7C
DD jl short 038版本?004029F1
; 未完继续
00402A14 . B8 0A>
mov eax,0A
; 第三段表开始
00402A19 > 8BC8
mov ecx,
eax ; 以下同上
00402A1B . 0FAFC>
imul ecx,
eax
00402A1E . 0FAFC>
imul ecx,
eax
00402A21 . 8D4C1>
lea ecx,
dword ptr ds:[
ecx+
edx+1F]
; edx=Key3,其余同上
00402A25 . 81E1 >
and ecx,8000007F
00402A2B . 79 05
jns short 038版本?00402A32
; 跳了
00402A2D . 49
dec ecx
00402A2E . 83C9 >
or ecx,FFFFFF80
00402A31 . 41
inc ecx
00402A32 > 884C0>
mov byte ptr ss:[
esp+
eax+2C],
cl ; 保存到表的第三段中★
00402A36 . 40
inc eax
00402A37 . 83F8 >
cmp eax,0F
; 到15了没?
00402A3A .^ 7C
DD jl short 038版本?00402A19
; 未完继续
================================
上面分三次生成了一个15
byte的表,我这里表的数据如下
0012F56C 12 13 1A 2D 52 24 7F 7E 27 00 1E 69 76 4B 6E -R$~'.ivKn
即:
表1
0x12 0x13 0x1A 0x2D 0x52
表2
0x24 0x7F 0x7E 0x27 0x00
表3
0x1E 0x69 0x76 0x4B 0x6E
================================
00402A3C . 33F6
xor esi,
esi ; 表已生成完毕,开始计算注册码
00402A3E . C6442>
mov byte ptr ss:[
esp+3B],0
00402A43 . 33C9
xor ecx,
ecx
00402A45 > 8A443>
mov al,
byte ptr ss:[
esp+
esi+2C]
; 取表中第N位的低8位到al
00402A49 . 3C 30
cmp al,30
; '0'
00402A4B . 7C 04
jl short 038版本?00402A51
00402A4D . 3C 39
cmp al,39
; '9'
00402A4F . 7E 29
jle short 038版本?00402A7A
; 非可显字符则跳
00402A51 > 3C 41
cmp al,41
; 'a'
00402A53 . 7C 04
jl short 038版本?00402A59
00402A55 . 3C 5A
cmp al,5A
; 'z'
00402A57 . 7E 21
jle short 038版本?00402A7A
; 非可显字符则跳
00402A59 > 3C 61
cmp al,61
; 'A'
00402A5B . 7C 04
jl short 038版本?00402A61
00402A5D . 3C 7A
cmp al,7A
; 'Z'
00402A5F . 7E 19
jle short 038版本?00402A7A
; 非可显字符则跳
00402A61 > 0FBED>
movsx edx,
al ; 表中第N位放到edx
00402A64 . 8D440>
lea eax,
dword ptr ds:[
edx+
ecx+1F]
; edx=表第N位ASCII,相加结果放到eax中
00402A68 . 25 7F>
and eax,8000007F
; 与运算and 0x800007F
00402A6D . 79 05
jns short 038版本?00402A74
; 跳了
00402A6F . 48
dec eax
00402A70 . 83C8 >
or eax,FFFFFF80
00402A73 . 40
inc eax
00402A74 > 88443>
mov byte ptr ss:[
esp+
esi+2C],
al ; 保存注册码第N位
00402A78 .^ EB CB
jmp short 038版本?00402A45
; 继续下一个
00402A7A > 83C1 >
add ecx,7
; 非可显字符ASCII+0x7
00402A7D . 46
inc esi ; 记数器加1
00402A7E . 83F9 >
cmp ecx,69
; 0x7*0x0F=0x69,是否循环完了15次?
00402A81 .^ 7C C2
jl short 038版本?00402A45
; 未完继续,完了向下
================================
上面这段是根据之前的表生成0-9,a-z,A-Z段的注册码,以便显示出来
到这里已经可以看到注册码的原始状态,我这里显示的注册码原始状态是
0012F56C 31 39 47 61 52 66 48 4E 55 78 68 69 76 4B 6E 19GaRfHNUxhivKn
下面继续看看还做了些什么
================================
00402A83 . 8D4C2>
lea ecx,
dword ptr ss:[
esp+2C]
; 保存得到的15位注册码指针到ecx
00402A87 . 8D542>
lea edx,
dword ptr ss:[
esp+10]
; 空指针
00402A8B . 51
push ecx ; 注册码入栈
00402A8C . 68 C8>
push 038版本?0041F0C8
; ASCII "%s"
00402A91 . 52
push edx
00402A92 . E8 66>
call 038版本?00410CFD
; 计算注册码长度=0x0F
00402A97 . 8B742>
mov esi,
dword ptr ss:[
esp+1C]
; 注册码指针保存到esi
00402A9B . 83C4 >
add esp,0C
00402A9E > 8B46 >
mov eax,
dword ptr ds:[
esi-8]
; 保存长度
00402AA1 . 33C9
xor ecx,
ecx
00402AA3 . 85C0
test eax,
eax
00402AA5 . 7E 2D
jle short 038版本?00402AD4
; 小于等于0x0F则over
00402AA7 > 8D79 >
lea edi,
dword ptr ds:[
ecx+1]
; 记数器
00402AAA . BD 06>
mov ebp,6
00402AAF . 8BC7
mov eax,
edi
00402AB1 . 99
cdq
00402AB2 . F7FD
idiv ebp ; 记数器余0x6
00402AB4 . 85D2
test edx,
edx ; 是否0,其实这里是判断注册码第6和第12位
00402AB6 . 75 13
jnz short 038版本?00402ACB
; 不是则跳
00402AB8 . 68 7C>
push 038版本?0041F37C
; 是第6位或第12位了
00402ABD . 51
push ecx
00402ABE . 8D4C2>
lea ecx,
dword ptr ss:[
esp+18]
00402AC2 . E8 E3>
call 038版本?004108AA
; 此CALL是往第6和第12位添加"-"
00402AC7 . 8B742>
mov esi,
dword ptr ss:[
esp+10]
00402ACB > 8B46 >
mov eax,
dword ptr ds:[
esi-8]
; 注册码长度
00402ACE . 8BCF
mov ecx,
edi ; 记数器保存到ecx
00402AD0 . 3BC8
cmp ecx,
eax ; 是否完了15次?
00402AD2 .^ 7C D3
jl short 038版本?00402AA7
; 未完继续
================================
上面这部分是往原始注册码的第6和第12位添加
"-"
添加
"-"完成后,注册码就出来了,我这里的注册码是
ESP 0012F540
EBP 00000006
ESI 003E6EE8 ASCII
"19GaR-fHNUx-hivKn"
EDI 00000011
完整注册码生成完毕,下面开始比较注册码了
================================
00402AD4 > 8B83 >
mov eax,
dword ptr ds:[
ebx+18C]
00402ADA . 8B48 >
mov ecx,
dword ptr ds:[
eax-8]
00402ADD . 85C9
test ecx,
ecx ; 判断注册码长度
00402ADF . 75 18
jnz short 038版本?00402AF9
; 非空则继续
00402AE1 . 6A 10
push 10
00402AE3 . 68 70>
push 038版本?0041F370
00402AE8 . 68 5C>
push 038版本?0041F35C
00402AED . 8BCB
mov ecx,
ebx
00402AEF . E8 EB>
call 038版本?004145DF
; 弹出"注册码不能为空"出错提示
00402AF4 . E9 20>
jmp 038版本?00402D19
; over
00402AF9 > 56
push esi ; 注册码指针,这里可以作内存注册机
00402AFA . 50
push eax
00402AFB . E8 A3>
call 038版本?004039A3
; 判断注册码是否合法CALL
00402B00 . 83C4 >
add esp,8
00402B03 . 85C0
test eax,
eax
00402B05 . 0F85 >
jnz 038版本?00402D11
; 暴破点,跳则over
00402B0B . 8D83 >
lea eax,
dword ptr ds:[
ebx+188]
00402B11 . 68 50>
push 038版本?0041F350
00402B16 . 50
push eax
00402B17 . 8D442>
lea eax,
dword ptr ss:[
esp+24]
【最后小结】
到此,整个注册码生成过程已经了解清楚了,可以开始写注册机了.这里用C写了个注册机,根据上面分析的结果来写的,没有经过优化,写得比较乱,见笑了:)
注册机源码如下(TC 2.0调试通过):
【注册机】
#include <stdio.h>
#include <math.h>
#include <string.h>
double Call1(
double inDbl)
{
/* 求对数子函数 */
double tmpCallDbl;
unsigned long tmpCallLng;
tmpCallDbl=log(inDbl)/log(2)*2;
/* 以2为底求对数 */
tmpCallLng=tmpCallDbl+0.5;
/* 四舍五入 */
tmpCallDbl-=tmpCallLng;
tmpCallDbl=pow(2,tmpCallDbl-1+1);
tmpCallDbl=pow(2,tmpCallLng)*tmpCallDbl;
return(tmpCallDbl);
}
void main()
{
/**************************************/
/* KeyGen By XJTL www.CnXHacker.com */
/**************************************/
double tmpDbl;
unsigned long tmpLng1,tmpLng2,OldLng1=0,OldLng2=0,Key1,Key2,Key3,
ecx;
int j,k,l,
eax,
esi=0;
char n[15],sn[15];
clrscr();
printf(
"\n********** KeyGen For BB_038wg **********\n");
printf(
"\n******Powered By XJTL CnXHacker.com******\n\n");
printf(
"Pls Input Your Machine Code:");
gets(n);
l=strlen(n);
printf(
"\nYour Serial Num is : ");
for(k=1;k<=l;k++)
{
tmpDbl=sqrt(n[k-1])*k+1;
tmpLng1=n[k-1]*k*k;
tmpDbl*=tmpLng1;
OldLng1+=tmpDbl;
Key1=OldLng1%0x186A0;
/* 密钥1 */
tmpDbl=Call1(n[k-1]);
/* 调用求对数函数 */
tmpLng1=tmpDbl*k+0.5;
/* 四舍五入 */
OldLng2*=k;
tmpDbl=sqrt(Key1);
tmpLng1+=OldLng2;
tmpLng2=tmpLng1;
/* 保存临时变量 */
OldLng2=tmpLng1%0x186A0;
Key2=OldLng2;
/* 密钥2 */
tmpLng1=tmpDbl;
/* 保存临时变量 */
tmpLng2+=tmpLng1;
tmpLng2=tmpLng2%0x186A0;
Key3=tmpLng2;
/* 密钥3 */
}
/* 根据密钥生成三个表,共15 byte */
for(
eax=0;
eax<=4;
eax++)
{
ecx=
eax;
ecx*=
eax;
ecx*=
eax;
ecx+=Key1+0x1F;
ecx&=0x8000007F;
sn[
eax]=
ecx;
/* 生成表第一部分,5 byte */
}
for(
eax=5;
eax<=9;
eax++)
{
ecx=
eax;
ecx*=
eax;
ecx*=
eax;
ecx+=Key2+0x1F;
ecx&=0x8000007F;
sn[
eax]=
ecx;
/* 生成表第二部分,5 byte */
}
for(
eax=10;
eax<=14;
eax++)
{
ecx=
eax;
ecx*=
eax;
ecx*=
eax;
ecx+=Key3+0x1F;
ecx&=0x8000007F;
sn[
eax]=
ecx;
/* 生成表第三部分,5 byte */
}
ecx=0;
while(
esi<=14)
{
/* 遍历表输出可显字符 */
if(sn[
esi]>='0'&&sn[
esi]<='9'||sn[
esi]>='a'&&sn[
esi]<='z'||sn[
esi]>='A'&&sn[
esi]<='Z')
{
ecx+=7;
esi+=1;
}
else
{
eax=sn[
esi]+
ecx+0x1F;
eax&=0x8000007F;
sn[
esi]=
eax;
/* 生成注册码,15 byte */
}
}
/* 注册码添加"-"然后输出 */
for(j=0;j<=4;j++)
printf(
"%c",sn[j]);
printf(
"-");
for(j=5;j<=9;j++)
printf(
"%c",sn[j]);
printf(
"-");
for(j=10;j<=14;j++)
printf(
"%c",sn[j]);
printf(
"\n\n");
getch();
}
----------------------------------------------------------------
【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!
[课程]Android-CTF解题方法汇总!