好友
阅读权限30
听众
最后登录1970-1-1
|
本帖最后由 currwin 于 2014-6-4 12:55 编辑
【文章标题】【算法分析】010 Editer
【文章作者】currwin【F8LEFT】
【软件名称】010Editer v5.0
【软件大小】安装包 12.8 MB
【下载地址】自行搜索下载
【加壳方式】无
【编写语言】Qt (C++)
【操作平台】windows Xp sp3
【操作工具】OD,IDA Pro,VS 2010
【作者声明】在这个010Editor的破解随随便便都可以搜索出来的时候,研究它的算法就真的纯粹是感兴趣,为了学习而已,请各位大大多多指教。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
说实话,发完这篇帖子后就打算潜水一段时间了,最近比较忙,而且发的东西又没有什么人气,至少算法分析类是不打算再发的了。估计随便破个软件发到发布区里面都能够得到更多的热心与回复,唉,不说了,说多都是泪。直接进入正题吧。
010Editor这个软件是使用重启验证的,它的注册信息会直接保存注册表的
{HKEY_CURRENT_USER\Software\SweetScape\010 Editor}
这个位置上面,名称是Name与Password,直接就是明码保存了。
Password的格式有两个,这个我会在正文中说明。对应着Password,核心算法也可以分成两个,虽然他们都在同一个Call里面,但实际上计算部分是分开的。
另外,由于是使用了Qt进行开发,所以逆向出来的程序与我们平时看到的有点不一样,这给Keygen带来了不少的麻烦。
核心算法部分非常的长,如果贴ASM代码的话估计也没有几个人会看,就直接贴C源码了。
IDA还不会使用,所以代码阅读起来可能会有一点吃力,我尽量加上了注释,希望能够使算法流程更加清晰一点。
[C++] 纯文本查看 复制代码 // 参数是固定的,u6 = 0x6,u3E08 = 0x3E08
unsigned int __thiscall sub_63CEB0(int this, unsigned int u6, unsigned int u3E08)
{
int v3; // esi@1
int v4; // eax@1
unsigned int v5; // ebp@1
int v6; // ebx@1
signed int v7; // edi@3
char P5; // bl@6
__int16 v9; // di@7
unsigned __int16 v10; // ax@7
unsigned int v11; // ecx@7
int v12; // edi@10
__int16 v13; // dx@14
unsigned __int8 v14; // cl@14
unsigned int v15; // eax@14
int v16; // eax@17
unsigned int result; // eax@23
unsigned int v18; // edx@28
bool bFlag1; // cf@28
bool bFlag2; // zf@28
int v21; // eax@30
signed __int32 *v22; // ecx@30
unsigned int v23; // edx@30
void *v24; // [sp-8h] [bp-34h]@21
int QName; // [sp+Ch] [bp-20h]@17
int v26; // [sp+10h] [bp-1Ch]@7
char Pass[10]; // [sp+14h] [bp-18h]@3
int v28; // [sp+28h] [bp-4h]@17
v3 = this;
v4 = *(_DWORD *)(this + 4);
v5 = 0;
v6 = this + 4;
*(_DWORD *)(this + 48) = 0;
*(_DWORD *)(this + 24) = 0;
*(_DWORD *)(this + 52) = 0;
if ( !*(_DWORD *)(v4 + 8) || !*(_DWORD *)(*(_DWORD *)(this + 8) + 8) )
return 0x93u;
StringToHex(Pass); // 将用户名转换为Hex
v7 = (signed int)&off_816580;
do
{
if ( (unsigned __int8)QString::operator__(v6, *(_DWORD *)v7) )
return 0xE7u;
v7 += 4;
}
while ( v7 < (signed int)&unk_816584 ); // P[3] 用于判断注册码类型,只有 9C 和 AC 是接受的,其他的值均会失败
P5 = Pass[5];
if ( Pass[3] == 0x9C ) // 当P[3] = 9C 时V3+28为判断依据
{
LOBYTE(v26) = Pass[0] ^ Pass[6];
v9 = (unsigned __int8)(Pass[2] ^ Pass[5]) + ((unsigned __int8)(Pass[1] ^ Pass[7]) << 8);// v9 = P[1]P[7] P[2]P[5]
*(_DWORD *)(v3 + 28) = (unsigned __int8)Chg_1(Pass[0] ^ Pass[6]);// return ((a1 ^ 0x18) + 61) ^ 0xA7;
// 这里返回的值要大于6才会成功
v10 = Chg_2(v9); // 整数:(unsigned __int16)(((a1 ^ 0x7892) + 19760) ^ 0x3421) / 11;
v11 = *(_DWORD *)(v3 + 28);
*(_DWORD *)(v3 + 32) = v10;
if ( !v11 || !v10 || v10 > 0x3E8u ) // v11!=0,0<v10<3E8
return 0xE7u;
v12 = v11 < 2 ? v11 : 0;
}
else
{
if ( Pass[3] == 0xFC )
{
v12 = 255; // P[3] == FC时会失败,这些都可以忽略
*(_DWORD *)(v3 + 28) = 255;
*(_DWORD *)(v3 + 32) = 1;
*(_DWORD *)(v3 + 48) = 1;
}
else
{
if ( Pass[3] != -84 // Pass[3] == AC时,会进入这里
|| (v13 = (unsigned __int8)(Pass[1] ^ Pass[7]) << 8,// v13 = (P[1] ^ P[7]) << 8
v14 = Pass[2] ^ Pass[5], // v14 = P[2] ^ P[5]
*(_DWORD *)(v3 // 这个参数在这种情况下是无效的
+ 28) = 2,
v15 = (unsigned __int16)Chg_2(v14 + v13),// 这里,v15 = Chg_2()的效果与上面的 Chg_2的效果是等价的
*(_DWORD *)(v3 + 32) = (unsigned __int16)v15,
!(_WORD)v15)
|| v15 > 0x3E8 ) // v15 != 0 且 v15 <= 0x3E8
return 0xE7u;
v5 = Chg_4( // 这里当P[3] = AC时,v5为判断关键
((unsigned __int8)Pass[0] ^ (unsigned __int8)Pass[6])// v5 = Chg_4(P[5]P[9] P[8]P[4] P[6]P[0])
+ ((((unsigned __int8)Pass[8] ^ (unsigned __int8)Pass[4])
+ (((unsigned __int8)P5 ^ (unsigned __int8)Pass[9]) << 8)) << 8),
0x5B8C27u);
*(_DWORD *)(v3 + 52) = v5;
v12 = v5;
}
}
QString::toUtf8(v3 + 4, &QName); // 取名字QString
v28 = 0;
QByteArray::detach(&QName); // QName + C 就是指向名字的指针
v16 = Chg_3(*(char **)(QName + 0xC), Pass[3] != -4, v12, *(_DWORD *)(v3 + 32));// Chg_3(UserName, P[3] != FC, v12, v3 + 32)
// 利用加密表对用户名进行变换
if ( Pass[4] != (_BYTE)v16 ) // 使P[4] = (_BYTE)v16
goto LABEL_20; // 跳转则失败
if ( P5 != BYTE1(v16) ) // 使P[5] = (_BYTE1(v16))
goto LABEL_50; // 跳转则失败
if ( Pass[6] != (unsigned __int8)((unsigned int)v16 >> 16) )// 使P[6] = BYTE2(V16)
{
LABEL_20: // 失败
v28 = -1;
if ( _InterlockedExchangeAdd((signed __int32 *)QName, 0xFFFFFFFFu) )
return 0xE7u;
v24 = (void *)QName;
LABEL_22:
qFree(v24);
return 0xE7u;
}
if ( Pass[7] != BYTE3(v16) ) // 使P[7] = BYTE3(v16)
{
LABEL_50: // 失败
v28 = -1;
if ( _InterlockedExchangeAdd((signed __int32 *)QName, 0xFFFFFFFFu) )
return 0xE7u;
v24 = (void *)QName;
goto LABEL_22;
}
if ( Pass[3] == 0x9C )
{
v18 = -1;
bFlag1 = u6 < *(_DWORD *)(v3 + 28);
bFlag2 = u6 == *(_DWORD *)(v3 + 28);
goto LABEL_37; // v3 + 28 >= 6
}
if ( Pass[3] != 0xFC )
{
if ( Pass[3] == 0xAC && v5 )
{
v18 = -1;
bFlag1 = u3E08 < v5;
bFlag2 = u3E08 == v5;
LABEL_37:
v28 = -1;
if ( bFlag1 | bFlag2 )
{
if ( !_InterlockedExchangeAdd((signed __int32 *)QName, v18) )
qFree((void *)QName);
result = 0x2Du; // 成功
}
else
{
if ( !_InterlockedExchangeAdd((signed __int32 *)QName, v18) )
qFree((void *)QName);
result = 0x4Eu; // 早期版本
}
return result;
}
v22 = (signed __int32 *)QName;
v28 = -1;
v23 = -1;
LABEL_45:
if ( _InterlockedExchangeAdd(v22, v23) )
return 0xE7u;
v24 = (void *)QName;
goto LABEL_22;
}
v21 = Chg_4((unsigned __int8)Pass[0] + (((unsigned __int8)Pass[1] + ((unsigned __int8)Pass[2] << 8)) << 8), v16);
v22 = (signed __int32 *)QName;
v23 = -1;
v28 = -1;
if ( !v21 )
goto LABEL_45;
*(_DWORD *)(v3 + 24) = v21;
if ( !_InterlockedExchangeAdd(v22, 0xFFFFFFFFu) )
qFree((void *)QName);
return 0x93u;
}
上面的就是核心的算法了,不过只需要大致的看看就可以了。
当程序返回0x2D的时候,便会注册成功,所以跟着这一条线索,
我们追踪一下它的骨架。
[C++] 纯文本查看 复制代码 // 参数是固定的,u6 = 0x6,u3E08 = 0x3E08
unsigned int __thiscall sub_63CEB0(int this, unsigned int u6, unsigned int u3E08)
{ ... //省略无用的代码
StringToHex(Pass); // 将用户名转换为Hex
if ( Pass[3] == 0x9C ) // 当P[3] = 9C 时V3+28为判断依据
{
...
}
else
{
if ( Pass[3] == 0xFC )
{
... // P[3] == FC时会失败,这些都可以忽略
}
else
{
if ( Pass[3] != 0xAC ) // Pass[3] == AC时,进入这里才不会失败
return 0xE7u;
}
}
if ( Pass[3] == 0x9C )
{
v18 = -1; //P[3] = 0x9C
bFlag1 = u6 < *(_DWORD *)(v3 + 28); //这里开始对结果进行验证
bFlag2 = u6 == *(_DWORD *)(v3 + 28); //设置标志旗
goto LABEL_37; // v3 + 28 >= 6
}
if ( Pass[3] != 0xFC )
{
if ( Pass[3] == 0xAC && v5 )
{
v18 = -1; //P[3] = 0xAC
bFlag1 = u3E08 < v5; //设置标志旗
bFlag2 = u3E08 == v5;
LABEL_37:
v28 = -1;
if ( bFlag1 | bFlag2 )
{
...
result = 0x2Du; // 成功
}
else
{
...
}
return result;
}
... //下面的都只会返回失败
}
上面一段便是验证的代码了,当然,估计如果翻译为switch语句的话会更加清晰,不过就算是用if else 的组合,也是足够让我们看到程序在做些什么的。
我们会看到,返回0x2D的地方只有一个, 并且,只有当 P[3] = 0x9C 或者 0xAC的时候才会进入判定成功的段,其他的值都会失败。所以根据P[3]的具体的值,算法被分成了两个基本上完全不同的两个验证部分. 现在我们来看一看P[3] = 0x9C 的算法
[C++] 纯文本查看 复制代码 // 参数是固定的,u6 = 0x6,u3E08 = 0x3E08
unsigned int __thiscall sub_63CEB0(int this, unsigned int u6, unsigned int u3E08)
{
v3 = this;
StringToHex(Pass); // 将用户名转换为Hex
P5 = Pass[5];
if ( Pass[3] == 0x9C ) // 当P[3] = 9C 时V3+28为判断依据
{
LOBYTE(v26) = Pass[0] ^ Pass[6];
v9 = (unsigned __int8)(Pass[2] ^ Pass[5]) + ((unsigned __int8)(Pass[1] ^ Pass[7]) << 8);// v9 = P[1]P[7] P[2]P[5]
*(_DWORD *)(v3 + 28) = (unsigned __int8)Chg_1(Pass[0] ^ Pass[6]);// return ((a1 ^ 0x18) + 61) ^ 0xA7;
// 这里返回的值要大于6才会成功
v10 = Chg_2(v9); // 整数:(unsigned __int16)(((a1 ^ 0x7892) + 19760) ^ 0x3421) / 11;
v11 = *(_DWORD *)(v3 + 28);
*(_DWORD *)(v3 + 32) = v10;
if ( !v11 || !v10 || v10 > 0x3E8u ) // v11!=0,0<v10<3E8
return 0xE7u;
v12 = v11 < 2 ? v11 : 0;
}
QString::toUtf8(v3 + 4, &QName); // 取名字QString
v28 = 0;
QByteArray::detach(&QName); // QName + C 就是指向名字的指针
v16 = Chg_3(*(char **)(QName + 0xC), Pass[3] != -4, v12, *(_DWORD *)(v3 + 32));// Chg_3(UserName, P[3] != FC, v12, v3 + 32)
// 利用加密表对用户名进行变换
if ( Pass[4] != (_BYTE)v16 ) // 使P[4] = (_BYTE)v16
// 失败; // 跳转则失败
if ( P5 != BYTE1(v16) ) // 使P[5] = (_BYTE1(v16))
//失败; // 跳转则失败
if ( Pass[6] != (unsigned __int8)((unsigned int)v16 >> 16) )// 使P[6] = BYTE2(V16)
//失败;
if ( Pass[7] != BYTE3(v16) ) // 使P[7] = BYTE3(v16)
//失败;
if ( Pass[3] == 0x9C )
{
v18 = -1;
bFlag1 = u6 < *(_DWORD *)(v3 + 28);
bFlag2 = u6 == *(_DWORD *)(v3 + 28);
goto LABEL_37; // v3 + 28 >= 6
}
LABEL_37:
v28 = -1;
if ( bFlag1 | bFlag2 )
{
if ( !_InterlockedExchangeAdd((signed __int32 *)QName, v18) )
qFree((void *)QName);
result = 0x2Du; // 成功
}
...
}
这样一来就清晰多了。
首先,
result = Chg_1(P[0]^P[6]]), ;这里计算出来的v11便是最终比较的结果了。用result与u6进行对比,只有当result大于6的时候才会成功。
v9 = 高位 P[1]^P[7],低位 P[2]^P[5]; v9 = (unsigned __int8)(Pass[2] ^ Pass[5]) + ((unsigned __int8)(Pass[1] ^ Pass[7]) << 8)
紧接着便对v9进行变换
v10 = Chg_2(v9) ; 同样的,这里计算出来的结果要在 1 到 0x3E8之间。
这样,基本的数据初始化便算完成了。接着是对用户名进行加密。
Chg_3(UserName, P[3] != FC, v12, v3 + 32);
因为这里v12 = result < 2 ? v11 : 0; 但result必须是大于6的,故这里v12 恒等于0;
而v3 + 32 便是用来存储v10的地方。所以整理一下,这个函数应该是这样的
v16 = Chg_3(UserName, 1, 0, v10);
最后,这里计算出来的v16的各个字节分别与 P[4],P[5],P[6],P[7]进行比较,只有他们都相等了才会进入判断的地方。
最后再看一看,这种情况下只使用到了P[0] - P[7],所以密码的长度为 8*2 = 16个。
那么,P[3] = 9C 的情况到这里便结束了,接下来看 P[3] = 0xAC的情况。
[C++] 纯文本查看 复制代码 // 参数是固定的,u6 = 0x6,u3E08 = 0x3E08
unsigned int __thiscall sub_63CEB0(int this, unsigned int u6, unsigned int u3E08)
{
v3 = this;
v4 = *(_DWORD *)(this + 4);
v5 = 0;
v6 = this + 4;
*(_DWORD *)(this + 48) = 0;
*(_DWORD *)(this + 24) = 0;
*(_DWORD *)(this + 52) = 0;
StringToHex(Pass); // 将用户名转换为Hex
P5 = Pass[5];
if ( Pass[3] != 0xAC // Pass[3] == AC时,会进入这里
|| (v13 = (unsigned __int8)(Pass[1] ^ Pass[7]) << 8,// v13 = (P[1] ^ P[7]) << 8
v14 = Pass[2] ^ Pass[5], // v14 = P[2] ^ P[5]
v15 = (unsigned __int16)Chg_2(v14 + v13),// 这里,v15 = Chg_2()的效果与上面的 Chg_2的效果是等价的
*(_DWORD *)(v3 + 32) = (unsigned __int16)v15,
!(_WORD)v15)
|| v15 > 0x3E8 ) // v15 != 0 且 v15 <= 0x3E8
return 0xE7u;
v5 = Chg_4( // 这里当P[3] = AC时,v5为判断关键
((unsigned __int8)Pass[0] ^ (unsigned __int8)Pass[6])// v5 = Chg_4(P[5]P[9] P[8]P[4] P[6]P[0])
+ ((((unsigned __int8)Pass[8] ^ (unsigned __int8)Pass[4])
+ (((unsigned __int8)P5 ^ (unsigned __int8)Pass[9]) << 8)) << 8),
0x5B8C27u);
*(_DWORD *)(v3 + 52) = v5;
v12 = v5;
}
}
QString::toUtf8(v3 + 4, &QName); // 取名字QString
v28 = 0;
QByteArray::detach(&QName); // QName + C 就是指向名字的指针
v16 = Chg_3(*(char **)(QName + 0xC), Pass[3] != -4, v12, *(_DWORD *)(v3 + 32));// Chg_3(UserName, P[3] != FC, v12, v3 + 32)
// 利用加密表对用户名进行变换
if ( Pass[4] != (_BYTE)v16 ) // 使P[4] = (_BYTE)v16
// 失败; // 跳转则失败
if ( P5 != BYTE1(v16) ) // 使P[5] = (_BYTE1(v16))
//失败; // 跳转则失败
if ( Pass[6] != (unsigned __int8)((unsigned int)v16 >> 16) )// 使P[6] = BYTE2(V16)
//失败;
if ( Pass[7] != BYTE3(v16) ) // 使P[7] = BYTE3(v16)
//失败;
if ( Pass[3] == 0xAC && v5 )
{
v18 = -1;
bFlag1 = u3E08 < v5;
bFlag2 = u3E08 == v5;
v28 = -1;
if ( bFlag1 | bFlag2 )
{
result = 0x2Du; // 成功
}
...
}
当P[3] == AC时,貌似加密方法又复杂了一点。不过也是一样,是一直线的,没有任何的循环,分析起来也是同样简单的。
首先是 v13 = (P[1] ^ P[7]) << 8; v14 = P[2] ^ P[5]
v15 = Chg_2(v13 + v14); //这里仔细想一想,v13 + v14 不就等于上面的v9么,然后 v15 就等价于上面的v10了,所以这部分是一样的。
同样的,对v15的要求是 0 < v15 < 0x3E9
接着是
v5 = Chg_4(P[0]^P[6] + (P[8]^P[4])<<8 + (P[5]^P[9])<<16); //这里对v5的要求是 v5 > 0x3E08
这些就是初始化的操作了
紧接着就是对用户名进行变换了
Chg_3(UserName, P[3] != FC, v12, v3 + 32);
还是那个样子,P[3] == AC != FC, v12 = v5, v3 + 32 = v15
整理一下就是
v16 = Chg_3(UserName, 1, v5, v15);
变换完的v16再各位与P[4],P[5],P[6],P[7]进行比较,都相同的话就成功了。
注意一下,这里用到的密码位为 P[0] - P[9],密码长度为 10 * 2 =20个。
前面的是16个,呵呵,没想到密码居然是长度可变的。
好了,主体部分的算法基本上就是这些了,接下来给出一下Chg_1,2,3,4的具体流程吧。
Chg_1:
[C++] 纯文本查看 复制代码 char __cdec Chg_1(char a1)
{
return ((a1 ^ 0x18) + 61) ^ 0xA7;
}
Chg_2:
[C++] 纯文本查看 复制代码 int __cdec Chg_2(__int16 a1)
{
int result; // eax@1
result = (unsigned __int16)(((a1 ^ 0x7892) + 0x4D30) ^ 0x3421) / 11;
if ( (unsigned __int16)(((a1 ^ 0x7892) + 0x4D30) ^ 0x3421) % 11 ) //这里要求经过一堆变换后的结果是11的倍数才不会返回0
result = 0;
return result;
}
Chg_4:
[C++] 纯文本查看 复制代码 int __cdecl Chg_4(int a1, int Key)
{
int v2; // ecx@1
v2 = ((Key ^ a1 ^ 0x22C078) - 180597) ^ 0xFFE53167;
return (v2 & 0xFFFFFFu) % 0x11 == 0 ? (v2 & 0xFFFFFFu) / 0x11 : 0; //同样的,也是要求经过一堆变换后的结果是0x11的倍数
}
Chg_3:
[C++] 纯文本查看 复制代码 _DWORD __cdecl sub_63B3C0(char *Name, int a2, int a3, int a4)
{
char *v4; // eax@1
int i; // ebx@1
int v6; // ebp@1
char *v7; // edx@1
char v8; // cl@2
int v9; // edi@4
int v10; // esi@4
int v11; // eax@5
int v13; // [sp+8h] [bp-Ch]@4
int v14; // [sp+Ch] [bp-8h]@4
int iNameLen; // [sp+10h] [bp-4h]@3
v4 = Name;
i = 0;
v6 = 0;
v7 = Name + 1;
do
v8 = *v4++;
while ( v8 );
iNameLen = v4 - v7;
if ( (signed int)(v4 - v7) > 0 )
{
v9 = 15 * a4;
v14 = 0;
v13 = 0;
v10 = 17 * a3;
do // 对名字的每一个进行转换
{
v11 = toupper((unsigned __int8)Name[i]); // 小写转大写
if ( a2 ) // 这里总是成立,因为P[3] != FC
v6 = Name_Table[(unsigned __int8)v13] // 取加密表进行变换
+ Name_Table[(unsigned __int8)v9]
+ Name_Table[(unsigned __int8)v10]
+ Name_Table[(unsigned __int8)(v11 + 47)]
* (Name_Table[(unsigned __int8)(v11 + 13)] ^ (v6 + Name_Table[v11]));
else // 下面的可以忽略
v6 = Name_Table[(unsigned __int8)v14]
+ Name_Table[(unsigned __int8)v9]
+ Name_Table[(unsigned __int8)v10]
+ Name_Table[(unsigned __int8)(v11 + 23)]
* (Name_Table[(unsigned __int8)(v11 + 63)] ^ (v6 + Name_Table[v11]));
v13 += 19;
v14 += 7;
++i;
v10 += 9;
v9 += 13;
}
while ( i < iNameLen );
}
return v6;
}
这几个函数都是没有什么好讲的,因为都是很简单的。
算法分析完了,要如何去进行Keygen才是关键所在。
一个最简单的办法就是随机生成P[0]~P[9],然后代入算法里面验证是否满足算法,满足的话就输出结果,不满足的话就继续进行循环,直到找到一组可以使用的Key为止。
这个方法很好用,不过在这里就难说了。
只看看P[3] = 0x9C 的情况就可以知道这方法在这里是多么的糟糕了。
v9 = (unsigned __int8)(Pass[2] ^ Pass[5]) + ((unsigned __int8)(Pass[1] ^ Pass[7]) << 8); 这里,v9由4个密码运算得出
v10 = Chg_2(v9); Chg_2的计算中,要求v9经过一堆变换后,必须是11的倍数,这是非常难以满足的。
看看 Chg_2 与 Chg_4 的代码中,要求对参数进行奇怪的变换,最后居然还要求是11 或者 是 0x11 的倍数。这得多难满足。并且他们的参数都是由多个密码组合而成的,这样想random出正确的结果就更加难了,所以这个方法不行。反正我就试过用计算机random了很久也random不出来一个正确的Key。
还有另外一个方法。
我们设 x为注册码,y为判断结果,通常是 y = f(x) 求出结果的。假如,我们可以找到一个函数g,使得 x = g(y),不就可以直接通过结果逆推出注册码么。
还是同样的例子,在上面中,假如找到了 Chg_2 的 逆函数,Chg_2_1,使得 v9 = Chg_2_1(v10)。那么我们就只需要random一个数据v10,但是却可以确定两个数据P[2]^P[5],P[1]^P[7]。
把这个想法扩展一下,可以得到keygen的大致思路。
P[3] == 0x9C 时,
random出 v10,和 result
调用 Chg_1_1(result) 可以确定 P[0]^P[6]的值。
调用 Chg_2_1(v10) 可以确定P[2]^P[5],P[1]^P[7]的值
调用v16 = Chg_3(UserName, 1, 0, v10);可以确定 P[4],P[5],P[6],P[7]的值。
最后,P[0] = (P[0] ^ P[6]) ^ P[6] 可以求出P[0]
这样,密码就出来了。
P[3] == 0xAC时,
random出 v5与 v15
调用 Chg_2_1(v15) 可以确定P[2]^P[5], P[1]^P[7]的值
调用Chg_4_1(v5) 可以确定P[5]^P[9], P[8]^P[4], P[0]^P[6]的值。
调用v16 = Chg_3(UserName, 1, v5, v15);可以确定P[4], P[5], P[6], P[7]的值。
最后:
P[0] = (P[0]^P[6]) ^ P[6];
P[1] = (P[1]^P[7]) ^ P[7];
P[2] = (P[2]^P[5]) ^ P[5];
P[8] = (P[8]^P[4]) ^ P[4];
P[9] = (P[5]^P[9]) ^ P[5];
这样又一组密码出来了,那么,只需要保证能够找到Chg_1,Chg_2,Chg_4的逆算法 Chg_1_1, Chg_2_1, Chg_4_1就可以了。
Chg_1_1
[C++] 纯文本查看 复制代码 char Chg_1_1(char a1) //Chg_1的逆算法
{
return ((a1 ^ 0xA7) - 61) ^ 0x18;
}
Chg_2_1:
[C++] 纯文本查看 复制代码 unsigned short Chg_2_1(unsigned short a1) //Chg_2的逆函数。不求逆估计是算不出来的
{
return ((((a1 * 11) ^ 0x3421) - 0x4D30) ^ 0x7892);
}
Chg_4_1:
[C++] 纯文本查看 复制代码 int Chg_4_1(int v2, int key) //Chg_4的逆函数
{
int a1;
v2 = v2 * 0x11;
a1 = ((v2 ^ 0xFFE53167) + 0x2C175) ^ 0x22C078 ^ key;
return (a1 & 0xFFFFFF);
}
虽然Keygen是出来了,不过这个软件貌似还会不定时去链接网络验证key是否有效,不过,这个也不难去掉,大家搞起吧。
具体的Keygen代码我就不给了,因为不难啊。附上几张图,玩一下:
【版权声明】: 本文原创于F8LEFT, 转载请注明作者并保持文章的完整, 谢谢!
2014.6.4
|
免费评分
-
查看全部评分
|