【软件名称】 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();
}
----------------------------------------------------------------
【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢!