爱飞的猫 发表于 2015-12-1 19:32

Log Viewer Pro 注册算法静态(主)分析

本帖最后由 jixun66 于 2015-12-2 03:55 编辑

主要使用 IDR + IDA 进行静态分析,然后有一个地方没看懂后开了下调试器看 :D

仅供学习交流之用,禁止用于商业用途。

首先因为没有发现输入注册码的位置,于是参考了 @风吹屁屁凉 发布的注册文件,得知关键文字 “key.txt”、“name”、“str”。

扔给 IDR 分析,寻找字符串然后在 IDA 定位到这里:    (**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,
CODE:004F2CF5               mov   eax,
CODE:004F2CF8               call    Unit114_4F29FC如果返回了 1 即注册成功。此处应该可以爆破,没仔细看了。

然后就是对用户名进行检查。如果用户名不够长 (3 字符),依次自动填充 L、T、U 确保用户名最少有 3 个字符。
比如用户名是 “A”,那么将填充 L 以及 T 成为 “ALT”。

然后就是检查序列号的格式以及合法性:
CODE:004F2A33               mov   eax,
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,
CODE:004F2A43               cmp   byte ptr , '-'
CODE:004F2A47               jnz   short _err_serial_format_error
CODE:004F2A49               mov   eax,
CODE:004F2A4C               cmp   byte ptr , '-'
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,则失败。另外这个函数是我认为最方便的爆破点, 爆破之后伪造一个相同格式的序列号即可,如:

Name=Jixun // LCG
Str=1111-2222-3333
回到正题,程序然后检查每一段序列号与用户名的兼容性。
具体算法为每一段的 ASCII 码相加,然后求余(% a, a 的获取参见下方),然后余数应等于分段号 + 1。

bool __usercall checkSerialPart@<al>(char *serial_part@<eax>, int part@<edx>, int a3){
// *(a3 - 4) is lpUsername
return (serial_part + serial_part + serial_part + *serial_part) % sub_4F28C0(*(a3 - 4), part) == part + 1;
}

首先加一个变量 b,这个值取决于分段号以及用户名:
一、用户名的第一个字节
二、用户名的中间字节(忽略小数点):   用户名[用户名长度 / 2]
  比如如果长度是 5,除以 2 得出 2.5,然后忽略小数点就是 2,第三个字符。
三、用户名的最后一个字节

然后 a 就是 b % 11 + 15。

IDA 反编译的部分代码如下:
v3 = part - 1;if ( v3 )                                     // If part = 2 or 3
{
    if ( v3 == 1 )                              // If part = 2
      v4 = lpUsername;
    else                                        // If part = 3
      v4 = lpUsername;
}
else                                          // if part = 1
{
    v4 = *lpUsername;
}
v5 = v4 % 15 + 11;
return v5;

最后,用户名与序列号检查通过后,通过检查用户名结尾判断其版本:
无限制授权; 企业授权(7 电脑); 其它为个人授权。

既然算法已经出来,我们来看看怎么手动算一个合法的序列号,目标为无限制授权。

用户名: Jixun // LCG
首字符: J (74)
中字符: / (47)
末字符: ] (93)

使用了 JavaScript 辅助计算当前序列号会产生的值,方便辅助计算。

另外因为并没有单独判断是否为字母、数字或其他数据的组合,序列号其实可以是任何内容。
亦可以把序列号的分段里面的字母顺序随意变换。

第 1 段序列号的计算:
和 % (74 % 15 + 11) = 2
合法的字符之一: gJix

第 2 段序列号的计算:
和 % (47 % 15 + 11) = 3
合法的字符之一: un6/

第 3 段序列号的计算:
和 % (93 % 15 + 11) = 4
合法的字符之一: LCG*

最后合并起来的授权文件就是:

Name=Jixun // LCG
Str=gJix-un6/-LCG*
保存至程序目录下的 key.txt 即可。



附:手算辅助代码,例如 calcSerial ('gJix', 74 % 15 + 11) 就会给我 2,然后改字符磨就行了。
function calcSerial (str, dv) {
var sum = 0;
for (var i = 0; i < 4; i++)
    sum += str.charCodeAt(i);
return sum % dv;
}

notwoisme 发表于 2015-12-1 19:34

楼主的头像的下巴那两个东西好像....

smile1110 发表于 2015-12-1 22:05

竟然还有企业授权 ,这么多年来从没人发现过这个

Sound 发表于 2015-12-2 01:57


太简洁, 虽说一些关键地方贴出.但是不满足发帖格式.想加亮都没机会。

wgz001 发表于 2015-12-2 08:42

静态分析的都比较牛逼{:1_921:}

风吹屁屁凉 发表于 2015-12-2 09:38

{:17_1073:}赞一个!

BmFx 发表于 2015-12-2 11:19

都是大牛 每天忙的没时间学习 只能看看大牛的帖子
页: [1]
查看完整版本: Log Viewer Pro 注册算法静态(主)分析