-
-
[旧帖] [原创]某网络设备管理软件算法分析 0.00雪花
-
发表于: 2014-11-12 19:58 2133
-
安装程序下载地址
http://pan.baidu.com/s/1c08JWVQ
软件有服务器端和客户端,服务器端是一些网络设备,
临时会员版块不注册好像看不到,所以我就说一下,这是某国企的内部软件,详情见百度
http://wenku.baidu.com/view/0ed456f2dd3383c4ba4cd216.html
客户端不注册也能用,但是连接服务器的时候会失败,软件没有输入注册码的地方,登陆的时候会获取服务器上的一个文件,ip/vb.htm
一个正确的key如下:
OK gethwinfo=00:80:c8:27:e4:31^1000c879-9ef1f899^01^.^00.00.00.48.58.30.30 ^01.00.00.01^00000001
其中1000c879-9ef1f899为序列号。
没有序列号也能运行程序,但是连接设备的时候就连不上了,一直显示登录中
注册码验证是在一个dll里面,完整程序太大上传不了,我就只上传了DLL。
IDA打开这个dll,可以看到其中有一个XXC_Login函数比较可疑,call sub_1000E510后面有一个条件跳转,明显就是关键跳了
直接从这里爆破程序没有功能,同样是一直显示连接中,
先看一下有没有什么已知的算法,IDA的插件扫出来的有MD4,MD5,CRC32,
看了一下,CRC32是在XXC_GetUpdateFileVersion里面调用的,MD5也不在刚才那个关键CALL里面,这个先不管它。
直接去看那个call,IDA F5:
可以看出返回值是1000E490决定的,分析一下输入的参数
跟进去,首先是ntohl转化了字节顺序,然后序列号运算后输出了16位字符串,但是只是取了其中2位进行验证了。
再看加密算法,初始化了几个常数,然后call sub_10034530
最终算法我就F5看了一下
已经是C代码了,关键分析一下每个常量,改好就能用了。用IIS架了个服务器,放上了那个vb.htm,用OD调了一下,把常量都分析出来了。
结果如下:
算法是还原出来了,可是怎么样才能验证成功呢?要么得把算法倒推回去,从最后2个字节做逆运算,要么就穷举。厂家提供序列号的时候肯定要用这个算法的逆算法,所以这个算法肯定是可逆的,这时我忽然想到一个这么复杂还要可逆,肯定不是自己发明的,这个算法是不是什么知名算法,我搜了一下算法里面的常数0x33333333,在论坛里面找到了2个类似的:http://bbs.pediy.com/showthread.php?t=136534和http://bbs.pediy.com/showthread.php?t=26409
从这里可以断定这个是DES算法,不过我看我这个的SPBOX跟他们的不一样。
DES逆运算要知道KEY,而要知道KEY就得找到DESSetkey的函数,那还得回头去分析算法,要找发现是DES可能还会去分析,现在我都还原到C了,花了整整一下午时间,也就没兴趣再弄了,还是先试试穷举看速度能不能接受。
结果基本上不到一秒钟就能穷举出来一个。然后用这个key去测试,关键跳正确跳转了。
这就算成功了吧,但是我觉得这个有点奇怪,加密的算法是:
8字节-8字节 des加密-----> 8字节-8字节,
程序只验证了2个字节,剩下14字节都没用上。根据高中的排列组合知识不难知道穷举能碰对的几率:
一共0x10的16次方个数,符合条件的密文有0x10的14次方个,也就是0x10000分之一的几率,des是可逆的,反推回去同样明文也就是0x10000分之一的几率能通过验证,也就是6万分之一,即使不知道算法,直接穷举碰对的概率也是相当大的,这样的条件作为序列号验证有点不合理。
/////////////////////////////////////////////////////////////////////////////////////////
过了2天后朋友跟我说算出来的序列号不行,虽然能连上,但是再执行一段时间后就断开了。
他经过测试得到的结论是程序连上去之后还有一处验证,算法他已经分析好了(写这篇文章时我去找了这个算法,找了半天也找不到,IDA里面就没有2040f这个常数,因为没有设备,本地架的服务器不能完全连上,所以不能继续后面的测试,大家要不信的话我也没办法了)。
比如序列号9b393966-8a2a99b2,那么验证过程是:
先把9b393966-8a2a99b2 分别转成ASIIC码,如下(都是16进制数据,我就省略0x了),
39 62 33 39 33 39 36 36 -- 38 61 32 61 39 39 62 32
把前8位相加 +3962 +3339+ 3339 +3636 = D60A
然后把后8位,先调一下位置, 把最后一位调到最前面,得到如下 3238 61 32 61 39 39 62
最后,把3238+6132+6139+3962 = 12E05
d60a+12e05=2040f,就为总和。
总和为2040ff时,验证通过。
接下来我们把这个算法写成C,首先做一下调整
每一位拆开
(39+33+33+36)*100+(62+39+39+36)= D60A
(32+61+61+39)*100+(38+32+39+62)= 12E05
2个算式合并
(39+33+33+36+32+61+61+39)*100+(62+39+39+36+38+32+39+62)=2040f
最后代码这样的
//第二个验证算法
2个条件,前面的线程函数对应改成
if(pDlg->Check2(str1,str2)&&pDlg->Check(a1,a2))
这下子速度慢了,我改成了批量生成,把结果写入文件,2个月之后朋友又来找我,说挂了那么多天才跑出来几十个号,刚开始几天还行,到后面一天可能都出不来一个号。
那么我们接着分析一下第2个算法,
第二个算法是求和,和是常数,我们没有必要每一位都穷举,穷举其中几位,其它几位就唯一确定了,
比如说2个2位数相加等于100,
如果把2个数都从0-99穷举,就要10000次
如果穷举一个,另外一个用100去减,就是穷举100次。
接着原算式:
(39+33+33+36+32+61+61+39)*100+(62+39+39+36+38+32+39+62)=2040f
换成符号来表示
(a1+a2+a3+a4+a5+a6+a7+a8)*100+(a9+a10+a11+a12+a13+a14+a15+a16)=2040f
记为m*100+n=s
其中ai为0-9的数字或者A-F的字母对应的ASCII码,
其中30<=ai<=39 或者61<=ai<=66。
根据ai的范围,8个ai相加最小值为30*8=180,最大值为66*8=330,
但是ai不能取39-61之间的值,也就是不一定180-330之间的每个值都能取得。
我这就不做数学推导了,直接让电脑算最精确了。
//计算8个1-f的数字对应ascii码和的范围
运行的结果303 >>><<<308,说明304-307的和是取不到的。
也就是说ai和的范围为 180~303 和 308~330。
再回头看m*100+n=s
其中s=2040f为常数,
因为m*100以00结尾,又s结尾为0f
所以n结尾为0f。
又n在180~303 和 308~330这个范围内,
n的取值只能为:20f或者30f。
s减去n得到m的取值为202或者201。
原问题归结为2个方程组
方程组1:
a1+a2+a3+a4+a5+a6+a7+a8=201
a9+a10+a11+a12+a13+a14+a15+a16=30f
方程组2:
a1+a2+a3+a4+a5+a6+a7+a8=202
a9+a10+a11+a12+a13+a14+a15+a16=20f
其中30<=ai<=39 或者61<=ai<=66。
这样好像缩小到了一个很小的范围了,这几个方程组到底有多少组解呢,毕竟解完方程后还要通过第一个验证,而第一个验证只有65536分之一的几率能过,我们现在需要1千个序列号,大约要这个方程产生6千万组解,有可能一共都有效key都没这么多。
到底能不能到这么多号,随手写了个程序统计一下方程组的解。
//求4个8元不定方程解的数量
运行结果:
0x201:52456096个解
0x202:52892616个解
0x20f:12291776个解
0x30f:3368个解
很多组解,看来之前的担忧是多余的,很多解。
方程组有2个方程,都穷举的话比较慢,我们先解其中一个方程求得500个解,存入一个文件,然后再解另外一个方程。
//计算和为20f的500组解
i
解出来
008999ff
00899f9f
00899ff9
0089f99f
0089f9f9
0089ff99
008f999f
008f99f9
008f9f99
008ff999
009899ff
00989f9f
00989ff9
0098f99f
0098f9f9
0098ff99
009989ff
00998f9f
00998ff9
009998ff
009999ef
......
......
然后收尾工作:
运行一下,速度还行,平均一秒钟可能算出来几个号,说明算法优化还是很重要的,原先一天一个号,现在一秒钟几个。
start time:11-12 10:49:15:244
00a07859----99f1f99b
00b09889----95fcf295
00c02879----9df4f698
00c028f9----96f2f897
00c088e9----92f2f797
00d09899----91f5ff90
00e03899----93f5fd95
00e09809----99faf694
00f018e9----99f9f094
00f03889----90f8fa98
00f088a9----95f7f295
1000c879----9ef1f899
1000f889----9cf7f396
10204869----98f8fa9d
102078a9----94f6f99d
10307819----94f8ff9d
103088e9----9cf0f698
1030f8a9----93f9f398
10401849----9df9fd97
104018d9----95f7ff96
xxsdk.rar
http://pan.baidu.com/s/1c08JWVQ
软件有服务器端和客户端,服务器端是一些网络设备,
临时会员版块不注册好像看不到,所以我就说一下,这是某国企的内部软件,详情见百度
http://wenku.baidu.com/view/0ed456f2dd3383c4ba4cd216.html
客户端不注册也能用,但是连接服务器的时候会失败,软件没有输入注册码的地方,登陆的时候会获取服务器上的一个文件,ip/vb.htm
一个正确的key如下:
OK gethwinfo=00:80:c8:27:e4:31^1000c879-9ef1f899^01^.^00.00.00.48.58.30.30 ^01.00.00.01^00000001
其中1000c879-9ef1f899为序列号。
没有序列号也能运行程序,但是连接设备的时候就连不上了,一直显示登录中
注册码验证是在一个dll里面,完整程序太大上传不了,我就只上传了DLL。
IDA打开这个dll,可以看到其中有一个XXC_Login函数比较可疑,call sub_1000E510后面有一个条件跳转,明显就是关键跳了
直接从这里爆破程序没有功能,同样是一直显示连接中,
先看一下有没有什么已知的算法,IDA的插件扫出来的有MD4,MD5,CRC32,
看了一下,CRC32是在XXC_GetUpdateFileVersion里面调用的,MD5也不在刚才那个关键CALL里面,这个先不管它。
直接去看那个call,IDA F5:
int __userpurge sub_1000E510<eax>(int a1<ecx>, int a2<ebx>, int a3) { int v3; // edi@1 char *v4; // eax@3 int *v5; // esi@3 int v6; // ST24_4@3 u_long *v7; // edi@3 char v9; // [sp+0h] [bp-F24h]@0 char v10; // [sp+Ch] [bp-F18h]@1 char v11; // [sp+10h] [bp-F14h]@3 char v12; // [sp+14h] [bp-F10h]@3 char v13; // [sp+18h] [bp-F0Ch]@3 char v14; // [sp+1Ch] [bp-F08h]@3 char v15; // [sp+20h] [bp-F04h]@3 char v16; // [sp+24h] [bp-F00h]@1 char v17; // [sp+314h] [bp-C10h]@2 unsigned int v18; // [sp+F20h] [bp-4h]@1 v18 = (unsigned int)&v10 ^ __security_cookie; v3 = a1; sub_10001000(555); sub_10001060(0, v9); sub_1000F170(&v16); if ( sub_100010C0(&v16) == -1 || strstr(&v17, "notwrite") ) return -1; v4 = strchr(&v17, 61); v5 = (int *)(v3 + 708); v6 = v3 + 708; v7 = (u_long *)(v3 + 704); if ( sscanf(v4 + 1, "%02x:%02x:%02x:%02x:%02x:%02x^%x-%x", &v10, &v11, &v12, &v13, &v14, &v15, v7, v6) != 8 ) { *v5 = -1; *v7 = -1; return -1; } *(_DWORD *)a2 = *v7; *(_DWORD *)a3 = *v5; return ((unsigned __int8)sub_1000E490(*v7, *v5) != 0) - 1; }
可以看出返回值是1000E490决定的,分析一下输入的参数
mov eax, [edi] .text:1000E5E3 mov [ebx], eax .text:1000E5E5 mov ecx, [esi] .text:1000E5E7 mov [ebp+0], ecx .text:1000E5EA mov edx, [esi] .text:1000E5EC mov eax, [edi] .text:1000E5EE push edx ; int .text:1000E5EF push eax ; netlong .text:1000E5F0 call sub_1000E490 ; eax为序列号前8位,edx为后8位 .text:1000E5F5 movzx eax, al
跟进去,首先是ntohl转化了字节顺序,然后序列号运算后输出了16位字符串,但是只是取了其中2位进行验证了。
.text:1000E490 sub esp, 0Ch .text:1000E493 mov eax, ___security_cookie .text:1000E498 xor eax, esp .text:1000E49A mov [esp+0Ch+var_4], eax .text:1000E49E xor eax, eax .text:1000E4A0 push esi .text:1000E4A1 mov esi, ds:ntohl .text:1000E4A7 mov dword ptr [esp+10h+var_C+1], eax .text:1000E4AB mov word ptr [esp+10h+var_C+5], ax .text:1000E4B0 mov byte ptr [esp+10h+var_C+7], al .text:1000E4B4 mov eax, [esp+10h+netlong] .text:1000E4B8 push eax ; netlong .text:1000E4B9 call esi ; ntohl .text:1000E4BB mov ecx, [esp+10h+arg_4] .text:1000E4BF push ecx ; netlong .text:1000E4C0 mov [esp+8], eax .text:1000E4C4 call esi ; ntohl .text:1000E4C6 mov dword ptr [esp+10h+var_C+4], eax .text:1000E4CA lea eax, [esp+10h+var_C] .text:1000E4CE call sub_100344D0 ; //加密 .text:1000E4D3 cmp byte ptr [esp+10h+var_C+6], 56h//第一处验证 .text:1000E4D8 pop esi .text:1000E4D9 jnz short loc_1000E4F3 .text:1000E4DB cmp byte ptr [esp+0Ch+var_C+7], 5Ah//第二处验证 .text:1000E4E0 jnz short loc_1000E4F3 .text:1000E4E2 mov al, 1 .text:1000E4E4 mov ecx, [esp+0Ch+var_4]
再看加密算法,初始化了几个常数,然后call sub_10034530
.text:100344D0 sub_100344D0 proc near ; CODE XREF: sub_1000E490+3Ep .text:100344D0 cmp dword_108E340C, 0 .text:100344D7 jnz short loc_1003451F .text:100344D9 mov dword_108E3410, 0A91h .text:100344E3 mov dword_108E3414, 6Ch .text:100344ED mov dword_108E3418, 2C6h .text:100344F7 mov dword_108E3420, 10Dh .text:10034501 mov dword_108E3428, 0DEh .text:1003450B mov dword_108E3430, 1BAh .text:10034515 mov dword_108E340C, 1 .text:1003451F .text:1003451F loc_1003451F: ; CODE XREF: sub_100344D0+7j .text:1003451F push eax .text:10034520 call sub_10034530 .text:10034525 pop ecx .text:10034526 retn .text:10034526 sub_100344D0 endp
最终算法我就F5看了一下
int __cdecl sub_10034530(int a1) { int v1; // edx@1 int v2; // eax@1 int v3; // ecx@1 int v4; // edx@1 int v5; // ecx@1 int v6; // eax@1 int v7; // edx@1 int v8; // eax@1 int v9; // ecx@1 int v10; // edx@1 int v11; // ecx@1 int v12; // eax@1 int v13; // edx@1 unsigned int v14; // ebx@1 unsigned int v15; // edi@1 signed int v16; // esi@1 int v17; // edx@2 int v18; // eax@3 unsigned int v19; // edx@3 int v20; // eax@3 int v21; // ecx@3 int v22; // edx@3 int v23; // ecx@3 int v24; // eax@3 int v25; // edx@3 int v26; // eax@3 int v27; // ecx@3 int v28; // edx@3 int v29; // ecx@3 int v30; // eax@3 int v31; // edx@3 int v32; // esi@3 int result; // eax@3 v1 = (*(_DWORD *)a1 ^ (*(_DWORD *)(a1 + 4) >> 4)) & 0xF0F0F0F; v2 = v1 ^ *(_DWORD *)a1; v3 = 16 * v1 ^ *(_DWORD *)(a1 + 4); v4 = (unsigned __int16)(v3 ^ (((unsigned int)v1 ^ *(_DWORD *)a1) >> 16)); v5 = v4 ^ v3; v6 = (v4 << 16) ^ v2; v7 = (v6 ^ ((unsigned int)v5 >> 2)) & 0x33333333; v8 = v7 ^ v6; v9 = 4 * v7 ^ v5; v10 = v9 ^ ((unsigned int)v8 >> 8); v10 &= 0xFF00FFu; v11 = v10 ^ v9; v12 = (v10 << 8) ^ v8; v13 = (v12 ^ ((unsigned int)v11 >> 1)) & 0x55555555; v14 = ((v13 ^ (unsigned int)v12) >> 29) + 8 * (v13 ^ v12); v15 = ((2 * v13 ^ (unsigned int)v11) >> 29) + 8 * (2 * v13 ^ v11); v16 = 28; do { v15 ^= dword_100875A8[(v14 ^ dword_108E3418[v16]) >> 26] ^ dword_100876A8[(((v14 ^ dword_108E341C[v16]) >> 4) + ((v14 ^ dword_108E341C[v16]) << 28)) >> 26] ^ dword_10086FA8[((v14 ^ dword_108E3418[v16]) >> 2) & 0x3F] ^ dword_100871A8[(unsigned __int8)((unsigned __int16)(v14 ^ LOWORD(dword_108E3418[v16])) >> 8) >> 2] ^ dword_100873A8[((v14 ^ dword_108E3418[v16]) >> 18) & 0x3F] ^ dword_100870A8[((((v14 ^ dword_108E341C[v16]) >> 4) + ((v14 ^ dword_108E341C[v16]) << 28)) >> 2) & 0x3F] ^ dword_100872A8[((((v14 ^ dword_108E341C[v16]) >> 4) + ((v14 ^ dword_108E341C[v16]) << 28)) >> 10) & 0x3F] ^ dword_100874A8[((((v14 ^ dword_108E341C[v16]) >> 4) + ((v14 ^ dword_108E341C[v16]) << 28)) >> 18) & 0x3F]; v17 = dword_100875A8[(v15 ^ dword_108E3410[v16]) >> 26] ^ dword_100876A8[(((v15 ^ dword_108E3414[v16]) >> 4) + ((v15 ^ dword_108E3414[v16]) << 28)) >> 26] ^ dword_10086FA8[((v15 ^ dword_108E3410[v16]) >> 2) & 0x3F] ^ dword_100871A8[(unsigned __int8)((unsigned __int16)(v15 ^ LOWORD(dword_108E3410[v16])) >> 8) >> 2] ^ dword_100873A8[((v15 ^ dword_108E3410[v16]) >> 18) & 0x3F] ^ dword_100870A8[((((v15 ^ dword_108E3414[v16]) >> 4) + ((v15 ^ dword_108E3414[v16]) << 28)) >> 2) & 0x3F] ^ dword_100872A8[((((v15 ^ dword_108E3414[v16]) >> 4) + ((v15 ^ dword_108E3414[v16]) << 28)) >> 10) & 0x3F] ^ dword_100874A8[((((v15 ^ dword_108E3414[v16]) >> 4) + ((v15 ^ dword_108E3414[v16]) << 28)) >> 18) & 0x3F]; v16 -= 4; v14 ^= v17; } while ( v16 > -2 ); v18 = (v15 >> 3) + (v15 << 29); v19 = (v18 ^ (((v14 >> 3) + (v14 << 29)) >> 1)) & 0x55555555; v20 = v19 ^ v18; v21 = 2 * v19 ^ ((v14 >> 3) + (v14 << 29)); v22 = v21 ^ ((unsigned int)v20 >> 8); v22 &= 0xFF00FFu; v23 = v22 ^ v21; v24 = (v22 << 8) ^ v20; v25 = (v24 ^ ((unsigned int)v23 >> 2)) & 0x33333333; v26 = v25 ^ v24; v27 = 4 * v25 ^ v23; v28 = (unsigned __int16)(v27 ^ HIWORD(v26)); v29 = v28 ^ v27; v30 = (v28 << 16) ^ v26; v31 = (v30 ^ ((unsigned int)v29 >> 4)) & 0xF0F0F0F; v32 = v30 ^ (v30 ^ ((unsigned int)v29 >> 4)) & 0xF0F0F0F; result = a1; *(_DWORD *)a1 = v32; *(_DWORD *)(a1 + 4) = v29 ^ 16 * v31; return result; }
已经是C代码了,关键分析一下每个常量,改好就能用了。用IIS架了个服务器,放上了那个vb.htm,用OD调了一下,把常量都分析出来了。
结果如下:
bool CSearchDlg::Check(unsigned int a1, unsigned int a2) { bool bOK=false; if(a1<10000000 || a2<10000000) return false; unsigned int b1,b2,result; int v16; unsigned int v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13,v14,v15, v17,v18,v19,v20,v21,v22,v23,v24,v25,v26,v27,v28,v29,v30,v31,v32; unsigned int dword_10086FA8[100]={0x002080800,0x000080000,0x002000002,0x002080802,0x002000000,0x000080802,0x000080002,0x002000002,0x000080802,0x002080800,0x002080000,0x000000802,0x002000802,0x002000000,0x000000000,0x000080002, 0x000080000,0x000000002,0x002000800,0x000080800,0x002080802,0x002080000,0x000000802,0x002000800,0x000000002,0x000000800,0x000080800,0x002080002,0x000000800,0x002000802,0x002080002,0x000000000, 0x000000000,0x002080802,0x002000800,0x000080002,0x002080800,0x000080000,0x000000802,0x002000800,0x002080002,0x000000800,0x000080800,0x002000002,0x000080802,0x000000002,0x002000002,0x002080000, 0x002080802,0x000080800,0x002080000,0x002000802,0x002000000,0x000000802,0x000080002,0x000000000,0x000080000,0x002000000,0x002000802,0x002080800,0x000000002,0x002080002,0x000000800,0x000080802}, dword_100870A8[100]={0x040108010,0x000000000,0x000108000,0x040100000,0x040000010,0x000008010,0x040008000,0x000108000,0x000008000,0x040100010,0x000000010,0x040008000,0x000100010,0x040108000,0x040100000,0x000000010, 0x000100000,0x040008010,0x040100010,0x000008000,0x000108010,0x040000000,0x000000000,0x000100010,0x040008010,0x000108010,0x040108000,0x040000010,0x040000000,0x000100000,0x000008010,0x040108010, 0x000100010,0x040108000,0x040008000,0x000108010,0x040108010,0x000100010,0x040000010,0x000000000,0x040000000,0x000008010,0x000100000,0x040100010,0x000008000,0x040000000,0x000108010,0x040008010, 0x040108000,0x000008000,0x000000000,0x040000010,0x000000010,0x040108010,0x000108000,0x040100000,0x040100010,0x000100000,0x000008010,0x040008000,0x040008010,0x000000010,0x040100000,0x000108000}, dword_100871A8[100]={0x004000001,0x004040100,0x000000100,0x004000101,0x000040001,0x004000000,0x004000101,0x000040100,0x004000100,0x000040000,0x004040000,0x000000001,0x004040101,0x000000101,0x000000001,0x004040001, 0x000000000,0x000040001,0x004040100,0x000000100,0x000000101,0x004040101,0x000040000,0x004000001,0x004040001,0x004000100,0x000040101,0x004040000,0x000040100,0x000000000,0x004000000,0x000040101, 0x004040100,0x000000100,0x000000001,0x000040000,0x000000101,0x000040001,0x004040000,0x004000101,0x000000000,0x004040100,0x000040100,0x004040001,0x000040001,0x004000000,0x004040101,0x000000001, 0x000040101,0x004000001,0x004000000,0x004040101,0x000040000,0x004000100,0x004000101,0x000040100,0x004000100,0x000000000,0x004040001,0x000000101,0x004000001,0x000040101,0x000000100,0x004040000}, dword_100872A8[100]={0x000401008,0x010001000,0x000000008,0x010401008,0x000000000,0x010400000,0x010001008,0x000400008,0x010401000,0x010000008,0x010000000,0x000001008,0x010000008,0x000401008,0x000400000,0x010000000, 0x010400008,0x000401000,0x000001000,0x000000008,0x000401000,0x010001008,0x010400000,0x000001000,0x000001008,0x000000000,0x000400008,0x010401000,0x010001000,0x010400008,0x010401008,0x000400000, 0x010400008,0x000001008,0x000400000,0x010000008,0x000401000,0x010001000,0x000000008,0x010400000,0x010001008,0x000000000,0x000001000,0x000400008,0x000000000,0x010400008,0x010401000,0x000001000, 0x010000000,0x010401008,0x000401008,0x000400000,0x010401008,0x000000008,0x010001000,0x000401008,0x000400008,0x000401000,0x010400000,0x010001008,0x000001008,0x010000000,0x010000008,0x010401000}, dword_100873A8[100]={0x008000000,0x000010000,0x000000400,0x008010420,0x008010020,0x008000400,0x000010420,0x008010000,0x000010000,0x000000020,0x008000020,0x000010400,0x008000420,0x008010020,0x008010400,0x000000000, 0x000010400,0x008000000,0x000010020,0x000000420,0x008000400,0x000010420,0x000000000,0x008000020,0x000000020,0x008000420,0x008010420,0x000010020,0x008010000,0x000000400,0x000000420,0x008010400, 0x008010400,0x008000420,0x000010020,0x008010000,0x000010000,0x000000020,0x008000020,0x008000400,0x008000000,0x000010400,0x008010420,0x000000000,0x000010420,0x008000000,0x000000400,0x000010020, 0x008000420,0x000000400,0x000000000,0x008010420,0x008010020,0x008010400,0x000000420,0x000010000,0x000010400,0x008010020,0x008000400,0x000000420,0x000000020,0x000010420,0x008010000,0x008000020}, dword_100874A8[100]={0x080000040,0x000200040,0x000000000,0x080202000,0x000200040,0x000002000,0x080002040,0x000200000,0x000002040,0x080202040,0x000202000,0x080000000,0x080002000,0x080000040,0x080200000,0x000202040, 0x000200000,0x080002040,0x080200040,0x000000000,0x000002000,0x000000040,0x080202000,0x080200040,0x080202040,0x080200000,0x080000000,0x000002040,0x000000040,0x000202000,0x000202040,0x080002000, 0x000002040,0x080000000,0x080002000,0x000202040,0x080202000,0x000200040,0x000000000,0x080002000,0x080000000,0x000002000,0x080200040,0x000200000,0x000200040,0x080202040,0x000202000,0x000000040, 0x080202040,0x000202000,0x000200000,0x080002040,0x080000040,0x080200000,0x000202040,0x000000000,0x000002000,0x080000040,0x080002040,0x080202000,0x080200000,0x000002040,0x000000040,0x080200040}, dword_100875A8[100]={0x000004000,0x000000200,0x001000200,0x001000004,0x001004204,0x000004004,0x000004200,0x000000000,0x001000000,0x001000204,0x000000204,0x001004000,0x000000004,0x001004200,0x001004000,0x000000204, 0x001000204,0x000004000,0x000004004,0x001004204,0x000000000,0x001000200,0x001000004,0x000004200,0x001004004,0x000004204,0x001004200,0x000000004,0x000004204,0x001004004,0x000000200,0x001000000, 0x000004204,0x001004000,0x001004004,0x000000204,0x000004000,0x000000200,0x001000000,0x001004004,0x001000204,0x000004204,0x000004200,0x000000000,0x000000200,0x001000004,0x000000004,0x001000200, 0x000000000,0x001000204,0x001000200,0x000004200,0x000000204,0x000004000,0x001004204,0x001000000,0x001004200,0x000000004,0x000004004,0x001004204,0x001000004,0x001004200,0x001004000,0x000004004}, dword_100876A8[100]={0x020800080,0x020820000,0x000020080,0x000000000,0x020020000,0x000800080,0x020800000,0x020820080,0x000000080,0x020000000,0x000820000,0x000020080,0x000820080,0x020020080,0x020000080,0x020800000, 0x000020000,0x000820080,0x000800080,0x020020000,0x020820080,0x020000080,0x000000000,0x000820000,0x020000000,0x000800000,0x020020080,0x020800080,0x000800000,0x000020000,0x020820000,0x000000080, 0x000800000,0x000020000,0x020000080,0x020820080,0x000020080,0x020000000,0x000000000,0x000820000,0x020800080,0x020020080,0x020020000,0x000800080,0x020820000,0x000000080,0x000800080,0x020020000, 0x020820080,0x000800000,0x020800000,0x020000080,0x000820000,0x000020080,0x020020080,0x020800000,0x000000080,0x020820000,0x000820080,0x000000000,0x020000000,0x020800080,0x000020000,0x000820080}, dword_108E3410[100]={1,0x000000a91,0x00000006c,0x0000002c6,0x000000000,0x00000010d,0x000000000,0x0000000de,0x000000000,0x0000001ba,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000, 0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000, 0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000000000,0x000237de8,0x0fffffffe,0x000000001,0x000006768,0x000000000,0x0000007d0,0x000237e10,0x0ffffffff,0x000000000,0x000000000, 0x000000000,0x0000007d0,0x000000e10,0x000000000,0x00000003c,0x000000000,0x000015180,0x000000000,0x000000e10,0x000000000,0x00000003c,0x000000000,0x000015180,0x000000000,0x000000e10,0x000000000}; //测试样例a1=0x177d75fa,a2=0x23dc9ac5 b1=ntohl(a2);//转化字节序 b2=ntohl(a1); v1=(b2^(b1>>4))&0x0f0f0f0f; v2=v1^b2; v3 = 16 * v1 ^ b1; v4 =(v3 ^ ((v1 ^ b2) >> 16))&0xffff; v5 = v4 ^ v3; v6 = (v4 << 16) ^ v2; v7 = (v6 ^ (v5 >> 2)) & 0x33333333; v8 = v7 ^ v6; v9 = 4 * v7 ^ v5; v10 = v9 ^ (v8 >> 8); v10 &= 0xFF00FFu; v11 = v10 ^ v9; v12 = (v10 << 8) ^ v8; v13 = (v12 ^ (v11 >> 1)) & 0x55555555; v14 = ((v13 ^ v12) >> 29) + 8 * (v13 ^ v12); v15 = ((2 * v13 ^ v11) >> 29) + 8 * (2 * v13 ^ v11); v16 = 28; unsigned int t1,t2,t3,t5,t6,t7,t8; do { t1=v14 ^ dword_108E3410[v16+4];//0xef73ac5c t2=(t1 >> 4) + (t1 << 28);//0xcef73ac5 t3=v14 ^ dword_108E3410[v16+3]; //t2>>0x12&0x3f;//0x0000003d v4=dword_100874A8[(t2 >> 18) & 0x3F]; v4^=dword_100872A8[(t2 >> 10)& 0x3F]; v4^=dword_100870A8[(t2 >> 2) & 0x3F]; v4^=dword_100873A8[(t3 >> 18) & 0x3F]; v4^=dword_100871A8[(t3>>0xA)&0x3F]; v4^=dword_10086FA8[(t3 >> 2) & 0x3F]; v4^=dword_100876A8[t2 >> 26] ; v4^= dword_100875A8[t3 >> 26]; v15^=v4; t5=v15 ^ dword_108E3410[v16+2];//0x3fc2f242 t6=(t5 >> 4) + (t5 << 28);//0x23fc2f24 t7=v15 ^ dword_108E3410[v16+1]; t8=dword_100874A8[(t6 >> 18) & 0x3F]; t8^=dword_100872A8[(t6 >> 10) & 0x3F]; t8^=dword_100870A8[(t6 >> 2) & 0x3F] ; t8^=dword_100873A8[(t7 >> 18) & 0x3F]; t8^=dword_100871A8[(t7>>0xA)&0x3F]; t8^=dword_10086FA8[(t7 >> 2) & 0x3F]; t8^=dword_100876A8[t6 >> 26]; t8^=dword_100875A8[t7 >> 26]; v17=t8; v16 -= 4; v14 ^= v17; } while ( v16 >-2 ); v18 = (v14 >> 3) + (v14 << 29); v19 = (((v15>>3)+(v15<<29)^(v18 >> 1))) & 0x55555555; v20 = v19 ^ ((v15>>3)+(v15<<29)); v21 = 2 * v19 ^ (v18); v22 = v21 ^ ((unsigned int)v20 >> 8); v22 &= 0xFF00FFu; v23 = v22 ^ v21; v24 = (v22 << 8) ^ v20; v25 = (v24 ^ ((unsigned int)v23 >> 2)) & 0x33333333; v26 = v25 ^ v24; v27 = 4 * v25 ^ v23; v28 = (unsigned __int16)(v27 ^ HIWORD(v26)); v29 = v28 ^ v27; v30 = (v28 << 16) ^ v26; v31 = (v30 ^ ((unsigned int)v29 >> 4)) & 0xF0F0F0F; v32 = v30 ^ v31; //result = b2; //b2 = v32; b1 = v29 ^ 16 * v31; result=b1>>16; b2=result/0x100; b1=result%0x100; if((b1==0x56)&&(b2==0x5A))//验证2个字节 bOK=true; else bOK=false; return bOK; }
算法是还原出来了,可是怎么样才能验证成功呢?要么得把算法倒推回去,从最后2个字节做逆运算,要么就穷举。厂家提供序列号的时候肯定要用这个算法的逆算法,所以这个算法肯定是可逆的,这时我忽然想到一个这么复杂还要可逆,肯定不是自己发明的,这个算法是不是什么知名算法,我搜了一下算法里面的常数0x33333333,在论坛里面找到了2个类似的:http://bbs.pediy.com/showthread.php?t=136534和http://bbs.pediy.com/showthread.php?t=26409
从这里可以断定这个是DES算法,不过我看我这个的SPBOX跟他们的不一样。
DES逆运算要知道KEY,而要知道KEY就得找到DESSetkey的函数,那还得回头去分析算法,要找发现是DES可能还会去分析,现在我都还原到C了,花了整整一下午时间,也就没兴趣再弄了,还是先试试穷举看速度能不能接受。
DWORD WINAPI SearchThread(LPVOID lParam) { CSearchDlg* pDlg=(CSearchDlg*)lParam; unsigned int a1,a2; char str1[50]={0},,str2[50],; pDlg->GetDlgItemText(IDC_EDIT1,str1,50); a1=strtoul(str1,NULL,16); if(a1<0x10000000) { a1=0x10000000; _ui64toa(a1,str1,16); pDlg->SetDlgItemText(IDC_EDIT1,str1); } pDlg->GetDlgItemText(IDC_EDIT2,str2,50); a2=strtoul(str2,NULL,16); if(a2<0x10000000) { a2=0x10000000; _ui64toa(a2,str2,16); pDlg->SetDlgItemText(IDC_EDIT2,str2); } pDlg->bContinue=true; while(pDlg->bContinue) { _ui64toa(a2,str2,16); if(pDlg->Check2(str1,str2)&&pDlg->Check(a1,a2)) { pDlg->MessageBox("找到一组序列号"); pDlg->SetDlgItemText(IDC_EDIT2,str2); break; } if(0==(a2%0x20)) { _ui64toa(a2,str2,16); pDlg->SetDlgItemText(IDC_EDIT2,str2); } a2++; } return 0; }
结果基本上不到一秒钟就能穷举出来一个。然后用这个key去测试,关键跳正确跳转了。
这就算成功了吧,但是我觉得这个有点奇怪,加密的算法是:
8字节-8字节 des加密-----> 8字节-8字节,
程序只验证了2个字节,剩下14字节都没用上。根据高中的排列组合知识不难知道穷举能碰对的几率:
一共0x10的16次方个数,符合条件的密文有0x10的14次方个,也就是0x10000分之一的几率,des是可逆的,反推回去同样明文也就是0x10000分之一的几率能通过验证,也就是6万分之一,即使不知道算法,直接穷举碰对的概率也是相当大的,这样的条件作为序列号验证有点不合理。
/////////////////////////////////////////////////////////////////////////////////////////
过了2天后朋友跟我说算出来的序列号不行,虽然能连上,但是再执行一段时间后就断开了。
他经过测试得到的结论是程序连上去之后还有一处验证,算法他已经分析好了(写这篇文章时我去找了这个算法,找了半天也找不到,IDA里面就没有2040f这个常数,因为没有设备,本地架的服务器不能完全连上,所以不能继续后面的测试,大家要不信的话我也没办法了)。
比如序列号9b393966-8a2a99b2,那么验证过程是:
先把9b393966-8a2a99b2 分别转成ASIIC码,如下(都是16进制数据,我就省略0x了),
39 62 33 39 33 39 36 36 -- 38 61 32 61 39 39 62 32
把前8位相加 +3962 +3339+ 3339 +3636 = D60A
然后把后8位,先调一下位置, 把最后一位调到最前面,得到如下 3238 61 32 61 39 39 62
最后,把3238+6132+6139+3962 = 12E05
d60a+12e05=2040f,就为总和。
总和为2040ff时,验证通过。
接下来我们把这个算法写成C,首先做一下调整
每一位拆开
(39+33+33+36)*100+(62+39+39+36)= D60A
(32+61+61+39)*100+(38+32+39+62)= 12E05
2个算式合并
(39+33+33+36+32+61+61+39)*100+(62+39+39+36+38+32+39+62)=2040f
最后代码这样的
//第二个验证算法
bool CSearchDlg::Check2(char *stra,char *strb) { byte arr1[10]={0},arr2[10]={0}; unsigned int sum1,sum2; sum1=sum2=0; int i; for(i=0;i<8;i++) { arr1[i]=stra[i]; } arr2[0]=strb[7]; for(i=0;i<7;i++) { arr2[i+1]=strb[i]; } sum1=(arr1[0]+arr1[2]+arr1[4]+arr1[6])*0x100+arr1[1]+arr1[3]+arr1[5]+arr1[7]; sum2=(arr2[0]+arr2[2]+arr2[4]+arr2[6])*0x100+arr2[1]+arr2[3]+arr2[5]+arr2[7]; if((sum1+sum2)==0x2040f) return true; return false; }
2个条件,前面的线程函数对应改成
if(pDlg->Check2(str1,str2)&&pDlg->Check(a1,a2))
这下子速度慢了,我改成了批量生成,把结果写入文件,2个月之后朋友又来找我,说挂了那么多天才跑出来几十个号,刚开始几天还行,到后面一天可能都出不来一个号。
那么我们接着分析一下第2个算法,
第二个算法是求和,和是常数,我们没有必要每一位都穷举,穷举其中几位,其它几位就唯一确定了,
比如说2个2位数相加等于100,
如果把2个数都从0-99穷举,就要10000次
如果穷举一个,另外一个用100去减,就是穷举100次。
接着原算式:
(39+33+33+36+32+61+61+39)*100+(62+39+39+36+38+32+39+62)=2040f
换成符号来表示
(a1+a2+a3+a4+a5+a6+a7+a8)*100+(a9+a10+a11+a12+a13+a14+a15+a16)=2040f
记为m*100+n=s
其中ai为0-9的数字或者A-F的字母对应的ASCII码,
其中30<=ai<=39 或者61<=ai<=66。
根据ai的范围,8个ai相加最小值为30*8=180,最大值为66*8=330,
但是ai不能取39-61之间的值,也就是不一定180-330之间的每个值都能取得。
我这就不做数学推导了,直接让电脑算最精确了。
//计算8个1-f的数字对应ascii码和的范围
int _tmain(int argc, _TCHAR* argv[]) { int i,j,sum,c=0; int v0,v1,v2,v3,v4,v5,v6,v7; char ch[5]={0}; int a[0x10]={0}; int r[0x400]={0}; int t; for(i=0;i<0x10;i++) { _itoa(i,ch,16); a[i]=ch[0]; } for(v0=0;v0<16;v0++) { printf("v0=%x ",v0);//程序运行比较慢,加个进度条。。 for(v1=0;v1<16;v1++) for(v2=0;v2<16;v2++) for(v3=0;v3<16;v3++) for(v4=0;v4<16;v4++) for(v5=0;v5<16;v5++) for(v6=0;v6<16;v6++) for(v7=0;v7<16;v7++) { sum=a[v0]+a[v1]+a[v2]+a[v3]+a[v4]+a[v5]+a[v6]+a[v7]; r[sum]=1; } } for(i=0;i<=0x330;i++) { if(r[i]) { if(r[i-1]==0) printf("<<<"); printf("%x ",i); c++; if(r[i+1]==0) printf(">>>"); if(i%5==0) printf("\n"); } } return 0; }
运行的结果303 >>><<<308,说明304-307的和是取不到的。
也就是说ai和的范围为 180~303 和 308~330。
再回头看m*100+n=s
其中s=2040f为常数,
因为m*100以00结尾,又s结尾为0f
所以n结尾为0f。
又n在180~303 和 308~330这个范围内,
n的取值只能为:20f或者30f。
s减去n得到m的取值为202或者201。
原问题归结为2个方程组
方程组1:
a1+a2+a3+a4+a5+a6+a7+a8=201
a9+a10+a11+a12+a13+a14+a15+a16=30f
方程组2:
a1+a2+a3+a4+a5+a6+a7+a8=202
a9+a10+a11+a12+a13+a14+a15+a16=20f
其中30<=ai<=39 或者61<=ai<=66。
这样好像缩小到了一个很小的范围了,这几个方程组到底有多少组解呢,毕竟解完方程后还要通过第一个验证,而第一个验证只有65536分之一的几率能过,我们现在需要1千个序列号,大约要这个方程产生6千万组解,有可能一共都有效key都没这么多。
到底能不能到这么多号,随手写了个程序统计一下方程组的解。
//求4个8元不定方程解的数量
int _tmain(int argc, _TCHAR* argv[]) { int i,sum,c=0; int v0,v1,v2,v3,v4,v5,v6,v7; int a[0x10]={0}; char ch[5]={0}; int r[0x400]={0}; char line[100]={0}; for(i=0;i<0x10;i++) { _itoa(i,ch,16); a[i]=ch[0]; } for(v0=0;v0<16;v0++) { printf("v0=%x ",v0); for(v1=0;v1<16;v1++) for(v2=0;v2<16;v2++) for(v3=0;v3<16;v3++) for(v4=0;v4<16;v4++) for(v5=0;v5<16;v5++) for(v6=0;v6<16;v6++) for(v7=0;v7<16;v7++) { sum=a[v0]+a[v1]+a[v2]+a[v3]+a[v4]+a[v5]+a[v6]+a[v7]; r[sum]++; } } sprintf(line,"0x201:%d个解\n0x202:%d个解\n0x20f:%d个解\n0x30f:%d个解\n", r[0x201],r[0x202],r[0x20f],r[0x30f]); printf(line); return 0; }
运行结果:
0x201:52456096个解
0x202:52892616个解
0x20f:12291776个解
0x30f:3368个解
很多组解,看来之前的担忧是多余的,很多解。
方程组有2个方程,都穷举的话比较慢,我们先解其中一个方程求得500个解,存入一个文件,然后再解另外一个方程。
//计算和为20f的500组解
i
nt _tmain(int argc, _TCHAR* argv[]) { int i,j,sum,c=0; int v0,v1,v2,v3,v4,v5,v6,v7; char ch[5]={0}; int a[0x10]={0}; int r[0x400]={0}; int t; for(i=0;i<0x10;i++) { _itoa(i,ch,16); a[i]=ch[0]; } FILE* file,*f20f; file=fopen("result.txt","a+"); f20f=fopen("20f.txt","a+"); char line[100]={0}; for(v0=0;v0<16;v0++) { printf("v0=%x ",v0); for(v1=0;v1<16;v1++) for(v2=0;v2<16;v2++) for(v3=0;v3<16;v3++) for(v4=0;v4<16;v4++) for(v5=0;v5<16;v5++) for(v6=0;v6<16;v6++) for(v7=0;v7<16;v7++) { sum=a[v0]+a[v1]+a[v2]+a[v3]+a[v4]+a[v5]+a[v6]+a[v7]; if(sum==0x20f) { r[sum]++; //求前500个解 if(r[sum]<=500) { sprintf(line,"%c%c%c%c%c%c%c%c\n", a[v0],a[v1],a[v2],a[v3],a[v4],a[v5],a[v6],a[v7]); fwrite(line,sizeof(char),9,f20f); } else goto end; } } } end: fclose(file); fclose(f20f); printf(line); return 0; }
解出来
008999ff
00899f9f
00899ff9
0089f99f
0089f9f9
0089ff99
008f999f
008f99f9
008f9f99
008ff999
009899ff
00989f9f
00989ff9
0098f99f
0098f9f9
0098ff99
009989ff
00998f9f
00998ff9
009998ff
009999ef
......
......
然后收尾工作:
DWORD WINAPI NewSearchThread(LPVOID lParam) { CSearchDlg* pDlg=(SearchDlg*)lParam; unsigned int a1,a2;//,an; unsigned int i,c=0;//个数 char ch[5]={0}; char str1[50]={0},str2[50]={0},strn[50]={0},tmpstr[50]={0}; pDlg->GetDlgItemText(IDC_EDIT4,strn,50);//IDC_EDIT4为20f那个方程的解任取一个手动填入作为常数 //初始化已知字符 str1[1]=strn[0]; str1[3]=strn[1]; str1[5]=strn[2]; str1[7]=strn[3]; str2[0]=strn[4]; str2[2]=strn[5]; str2[4]=strn[6]; str2[6]=strn[7]; int a[0x10]={0}; for(i=0;i<0x10;i++) { _itoa(i,ch,16); a[i]=ch[0];//初始化ASCII码 } pDlg->bContinue=true; FILE *f; f=fopen("result.txt","a+"); SYSTEMTIME sys; GetLocalTime( &sys ); sprintf(tmpstr,"start time:%02u-%02u %02u:%02u:%02u:%03u \n",sys.wMonth,sys.wDay, sys.wHour,sys.wMinute,sys.wSecond,sys.wMilliseconds); fwrite(tmpstr,1,strlen(tmpstr),f); int v0,v1,v2,v3,v4,v5,v6,v7; for(v0=0;v0<16;v0++) { printf("v0=%x ",v0); for(v1=0;v1<16;v1++) for(v2=0;v2<16;v2++) for(v3=0;v3<16;v3++) for(v4=0;v4<16;v4++) for(v5=0;v5<16;v5++) for(v6=0;v6<16;v6++) for(v7=0;v7<16;v7++) { if(!pDlg->bContinue) goto end; if(a[v0]+a[v1]+a[v2]+a[v3]+a[v4]+a[v5]+a[v6]+a[v7]==0x202) { str1[0]=a[v0]; str1[2]=a[v1]; str1[4]=a[v2]; str1[6]=a[v3]; str2[7]=a[v4]; str2[1]=a[v5]; str2[3]=a[v6]; str2[5]=a[v7]; a1=strtoul(str1,NULL,16); a2=strtoul(str2,NULL,16); if(pDlg->Check(a1,a2)) { sprintf(tmpstr,"%s----%s\n",str1,str2); fwrite(tmpstr,sizeof(char),strlen(tmpstr),f); c++; if(c%10==9) fflush(f); pDlg->SetDlgItemInt(IDC_EDIT3,c); } } } } end: fclose(f); return 0; }
运行一下,速度还行,平均一秒钟可能算出来几个号,说明算法优化还是很重要的,原先一天一个号,现在一秒钟几个。
start time:11-12 10:49:15:244
00a07859----99f1f99b
00b09889----95fcf295
00c02879----9df4f698
00c028f9----96f2f897
00c088e9----92f2f797
00d09899----91f5ff90
00e03899----93f5fd95
00e09809----99faf694
00f018e9----99f9f094
00f03889----90f8fa98
00f088a9----95f7f295
1000c879----9ef1f899
1000f889----9cf7f396
10204869----98f8fa9d
102078a9----94f6f99d
10307819----94f8ff9d
103088e9----9cf0f698
1030f8a9----93f9f398
10401849----9df9fd97
104018d9----95f7ff96
xxsdk.rar
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
谁下载
看原图
赞赏
雪币:
留言: