本帖最后由 jixun66 于 2015-12-2 03:55 编辑
主要使用 IDR + IDA 进行静态分析,然后有一个地方没看懂后开了下调试器看 :D
仅供学习交流之用,禁止用于商业用途。
首先因为没有发现输入注册码的位置,于是参考了 @风吹屁屁凉 发布的注册文件,得知关键文字 “key.txt”、“name”、“str”。
扔给 IDR 分析,寻找字符串然后在 IDA 定位到这里:[C++] 纯文本查看 复制代码 (**v11)("Name", dword_4F2D70, &username, 0);
(**v11)(&str, dword_4F2D70, &serial, 0);
TObject_free(v15, v16, v17, v18, v19, v20);
v6 = Unit114_4F29FC(username, serial, v11);
if ( v6 )
{
lstrArg(v5, username);
*v26 = sub_4F2C00(v12, v5);
}
其中,004F2CF8 调用了 4F29FC 函数用于检查用户名与序列号的合法性。CODE:004F2CF2 mov edx, [ebp+serial]
CODE:004F2CF5 mov eax, [ebp+username]
CODE:004F2CF8 call Unit114_4F29FC
如果返回了 1 即注册成功。此处应该可以爆破,没仔细看了。
然后就是对用户名进行检查。如果用户名不够长 (3 字符),依次自动填充 L、T、U 确保用户名最少有 3 个字符。
比如用户名是 “A”,那么将填充 L 以及 T 成为 “ALT”。
然后就是检查序列号的格式以及合法性:
[Asm] 纯文本查看 复制代码 CODE:004F2A33 mov eax, [ebp+_lpSerial]
CODE:004F2A36 call LStrLen
CODE:004F2A3B cmp eax, 14
CODE:004F2A3E jnz short _err_serial_format_error ; Serial must have 14 char in length.
CODE:004F2A3E ; 1111-2222-3333
CODE:004F2A40 mov eax, [ebp+_lpSerial]
CODE:004F2A43 cmp byte ptr [eax+4], '-'
CODE:004F2A47 jnz short _err_serial_format_error
CODE:004F2A49 mov eax, [ebp+_lpSerial]
CODE:004F2A4C cmp byte ptr [eax+9], '-'
CODE:004F2A50 jz short _step1_OK
CODE:004F2A52
CODE:004F2A52 _err_serial_format_error: ; CODE XREF: Unit114_4F29FC+42j
CODE:004F2A52 ; Unit114_4F29FC+4Bj
CODE:004F2A52 xor ebx, ebx
CODE:004F2A54 jmp short loc_4F2A58
依次取出并检查每一个分段是否合法。mov eax, serial_part1
mov edx, part ; 第一段: 1, 以此推类
call checkSerialPart
若是 al = 0,则失败。另外这个函数是我认为最方便的爆破点, 爆破之后伪造一个相同格式的序列号即可,如:[Key]
Name=Jixun // LCG
Str=1111-2222-3333
回到正题,程序然后检查每一段序列号与用户名的兼容性。
具体算法为每一段的 ASCII 码相加,然后求余(% a, a 的获取参见下方),然后余数应等于分段号 + 1。
[C++] 纯文本查看 复制代码 bool __usercall checkSerialPart@<al>(char *serial_part@<eax>, int part@<edx>, int a3){
// *(a3 - 4) is lpUsername
return (serial_part[3] + serial_part[2] + serial_part[1] + *serial_part) % sub_4F28C0(*(a3 - 4), part) == part + 1;
}
首先加一个变量 b,这个值取决于分段号以及用户名:
一、用户名的第一个字节
二、用户名的中间字节(忽略小数点): 用户名[用户名长度 / 2]
比如如果长度是 5,除以 2 得出 2.5,然后忽略小数点就是 2,第三个字符。
三、用户名的最后一个字节
然后 a 就是 b % 11 + 15。
IDA 反编译的部分代码如下:
[C++] 纯文本查看 复制代码 v3 = part - 1; if ( v3 ) // If part = 2 or 3
{
if ( v3 == 1 ) // If part = 2
v4 = lpUsername[LStrLen() / 2];
else // If part = 3
v4 = lpUsername[LStrLen() - 1];
}
else // if part = 1
{
v4 = *lpUsername;
}
v5 = v4 % 15 + 11;
return v5;
最后,用户名与序列号检查通过后,通过检查用户名结尾判断其版本:
[S] 无限制授权; [B] 企业授权(7 电脑); 其它为个人授权。
既然算法已经出来,我们来看看怎么手动算一个合法的序列号,目标为无限制授权。
用户名: Jixun // LCG[S]
首字符: J (74)
中字符: / (47)
末字符: ] (93)
使用了 JavaScript 辅助计算当前序列号会产生的值,方便辅助计算。
另外因为并没有单独判断是否为字母、数字或其他数据的组合,序列号其实可以是任何内容。
亦可以把序列号的分段里面的字母顺序随意变换。
第 1 段序列号的计算:
和 % (74 % 15 + 11) = 2
合法的字符之一: gJix
第 2 段序列号的计算:
和 % (47 % 15 + 11) = 3
合法的字符之一: un6/
第 3 段序列号的计算:
和 % (93 % 15 + 11) = 4
合法的字符之一: LCG*
最后合并起来的授权文件就是: [Key]
Name=Jixun // LCG[S]
Str=gJix-un6/-LCG*
保存至程序目录下的 key.txt 即可。
附:手算辅助代码,例如 calcSerial ('gJix', 74 % 15 + 11) 就会给我 2,然后改字符磨就行了。
[JavaScript] 纯文本查看 复制代码 function calcSerial (str, dv) {
var sum = 0;
for (var i = 0; i < 4; i++)
sum += str.charCodeAt(i);
return sum % dv;
}
|