首页
社区
课程
招聘
[原创]破解ecc保护的flexlm (翻译by nujia)
发表于: 2007-9-18 11:27 12341

[原创]破解ecc保护的flexlm (翻译by nujia)

2007-9-18 11:27
12341

破解ecc保护的flexlm  (翻译by nujia)

说句老实话,我很早就知道这个缺陷,:),但是因为RNG(random number generator,随机数发生器)做得足够很安全了,所以现在发表这个缺陷,将不会损害flexlm使用者的利益(除非他们没有更新RNG),Crackz。

简介

本文主要描述了怎样获得ecc flexlm保护的seed3和seed4。虽然本文是是讲述方法的,但是如果你破解一个真正的软件的话,下面这些软件将是必须的。

    * NuMega SoftICE
    * Datarescue IDA (4.15 或更新的版本)
    * FLEXlm v7.2h SDK
    * Microsoft Visual C++
    * TASM 假如你想分析某些代码的话
   
怎样开始工作呢?

标准的老的flexlm版本用两个32位的加密种子(seed1和seed2)来加密软件。这两个种子提供全部的加密保护。种子会在目标程序中出现,所以程序可以制作注册机,编译得到lmcrypt(flexlm key 生成器),这样cracker就能生成有效的license了。ecc flexlm需要两个额外的加密种子(seed3和seed4)。这两个种子用来生成公钥/私钥对,并且只有公钥存在于发布的软件中。Certicom Security Builder ECC 库用来生成公钥/私钥对,看起来比较安全。我没有发现用来生成公钥/私钥对的seed3和seed4与公钥之间有什么关系(就是由公钥是不能推导出seed3和seed4的)。为了暴力破解seed3和seed4,很明显需要2^64次尝试。这是不太现实的。

Globetrotter在使用seed3和seed4时犯了一个错误。虽然seed3和seed4不能像seed1和seed2那样完全的复原,但是也为缩短暴力破解的时间提供了足够的信息,这样能在可以接受的时间内暴力破解出seed3和seed4。

缺陷出现在_do_handshake函数里。这个函数用seed3和seed4来验证vendor daemon的真假。下面是使用seed3和seed4的部分代码。

                mov        edx,  dword ptr ds:_seed3         ;将seed3放入edx
                xor        edx,  dword ptr ds:_seed4         ;将seed3和seed4取异或,结果在edx
                shr        edx, 10h                          ;edx右移16位,
                and        edx, 0FFFFh                       ;将edx的低16位填充ffff
                push        edx                               ;将edx压栈
                mov        eax,  dword ptr ds:_seed3         ;将seed3放入eax
                add        eax,  dword ptr ds:_seed4         ;将seed3和seed4相加,结果在eax
                and        eax, 0FFFFh                       ;将eax的低16位填充ffff
                push        eax                               ;将eax压栈
                mov        ecx,  dword ptr ds:_seed3         ;将seed3放入ecx                       
                xor        ecx,  dword ptr ds:_seed4         ;将seed3和seed4取异或,结果在ecx      
                and        ecx, 0FFFFh                       ;将ecx的低16位填充ffff
                push        ecx                               ;将ecx压栈
                call        _srand16                          ;调用随机函数_srand16,三个输入,因为压栈了三个数
                add        esp, 0Ch                          ;函数外调整堆栈,
                call        _rand16                           ;四次调用另外一个随机函数_rand16
                call        _rand16                           ;返回结果在eax里
                call        _rand16                           ;
                call        _rand16                           ;
                mov        [ebp+var_60], eax ; "random" numbers are used as constants in lm_new :)
                                                          ;将返回值送入[ebp+var_60],这个随机数在lm_new保持常数,
                                                          ;于是缺陷就产生了,:)
                call        _rand16                           ;调用随机函数_rand16,无输入,因为没有压栈
                mov        [ebp+var_5C], eax                 ;将返回结果送入[ebp+var_5C]
                call        _rand16                           ;调用随机函数_rand16      
                mov        [ebp+var_58], eax                 ;将返回结果送入[ebp+var_58]
                call        _rand16                           ;调用随机函数_rand16      
                mov        [ebp+var_54], eax                 ;将返回结果送入[ebp+var_54]

假如我们可以复原传递给函数srand16的seed值,那我们就知道了很多关于seed3和seed4的信息了。仅凭一个由rand16产生的值,我们很难复原伪随机数发生器的初始状态(译者注:也就是由返回的eax的值确定随机函数rand1的输入值是不可能的,否则就不叫随机数发生器了,:))。为了复原传递给随机函数的输入值,我们需要仔细检查rand16函数和srand16函数。

/*------------------------------------------------------------------------*
*
* my_srand16: emulate the srand16 routine.  ;自己写的my_srand16函数,模仿srand16函数
* seeds: seed values.
*------------------------------------------------------------------------*/
int my_srand16(int seed1, int seed2, int seed3)
{
        sdata_0 = seed1 % 32363;            //32363就是7E6B
        sdata_1 = seed2 % 31727;            //31727就是7BEE
        sdata_2 = seed3 % 31657;            //31657就是7BA8,这三个数应该都是质数。

        if (sdata_0 == 0) sdata_0 += 32362;
        if (sdata_1 == 0) sdata_1 += 31726;
        if (sdata_2 == 0) sdata_2 += 31656;
        return(0);
}

/*------------------------------------------------------------------------*
*
* my_rand16: emulate the rand16 routine.    ;自己写的my_rand16函数,模仿rand16函数
*------------------------------------------------------------------------*/
int my_rand16()
{

        int var_8;
        int var_4;
        int edxval;
        int eaxval;

        var_4 = sdata_0 / 206;
        edxval = (sdata_0 / 206) * 206;
        eaxval = sdata_0 - edxval;
        sdata_0 = eaxval * 157 - (var_4 * 21);
        if (sdata_0 <= 0)
        {
                sdata_0 += 32363;
        }

        var_4 = sdata_1 / 217;
        edxval = var_4 * 217;
        eaxval = sdata_1 - edxval;
        sdata_1 = eaxval * 146 - (var_4 * 45);
        if (sdata_1 <= 0)
        {
                sdata_1 += 31727;
        }

        var_4 = sdata_2 / 222;
        edxval = var_4 * 222;
        eaxval = sdata_2 - edxval;
        sdata_2 = eaxval * 142 - (var_4 * 133);
        if (sdata_2 <= 0)
        {
                sdata_2 += 31657;
        }
        var_8 = sdata_0 - sdata_1;
        if (var_8 > 706)
        {
                var_8 = var_8 - 32362;
        }
        var_8 = var_8 + sdata_2;
        if (var_8 <= 0)
        {
                var_8 += 32362;
        }
        return(var_8 >> 1);
}

尽管看起来比较乱,但是它确实是起到了伪随机数发生器的功能。这个rand16函数的算法和Bruce Schneier编的《实用密码学》(Applied Cryptography) 里提到的线性同余发生器的例子相同。(译者注:线性同余发生器是最常见的随机数发生器方法之一,比如1/7=0.142857142857....,当分母7换成一个足够大的质数p的时候,它的商的不同位看起来就像随机产生的)在这本书里作者提到了这个算法不具有加密安全性,并且给出怎样破解这个随机数发生器。这个随机数发生器由三个发生器组成,每个发生器产生不同长度的随机数。当每个发生器初始化后,每产生一个随机数,发生器就变换到下一个状态。给定一个输出和两个发生器的初始状态,第三个发生器的状态就可以确定了。

第一个随机数可以从目标程序中获得,而且RNG开始两个LCG是循环码,第三个LCG从目标程序获得。假如随机数序列匹配超过四个值,我们就确定了RNG的状态了。三个LCG的全部的循环都在一个巨大的数组了,所以我们可以获得RNG的状态,而且无需任何计算,只要一个指向LCG的指针即可。一旦确定RNG状态的那四个值出现了,LCG就一步步回退,直到找到传递给PRNG的那个初始值。因为数据在给LCG时已经经过取模运算,这样肯定会有不同的值经过取模运算后得到相同的值传递给LCG。所有有效的排列都循环传递给srand函数。经过这些工作后,我们知道关于seed的如下信息。

高16位seed3 ^ seed4 == value1
低16位seed3 + seed4 & 0xffff == value2
低16位seed3 ^ seed4 == value3

(译者注:这个结论是上面提到的_do_handshake函数的部分代码的功能)

知道这些后,根据value1、value2、value3的值,暴力破解可以减少到大约2^30次,这样暴力破解才是可行的。在flexlm目标程序里有三个ecc公钥。选择那个最小的公钥,因为从seed3和seed4算公钥/私钥对时,最快算到这个值。seed值通过vendor名被最低限度的“模糊”了。

int reveal_data(sb_keydata_t *inkey, char *inname)
{
        int i;
        char *p;

        p = inname;
        for (i = 0; i < inkey->nbytes; i++)
        {
                if (*p == '\0') p = inname;
                if (i & 0x0000001)
                {
                        if (i%3)
                        {
                                inkey->keydata[i] = inkey->keydata[i] + *p;
                        }
                        else
                        {
                                inkey->keydata[i] = inkey->keydata[i] ^ *p;
                        }
                }
                else
                {
                        inkey->keydata[i] = inkey->keydata[i] - *p;
                }
                p++;
        }
        return(0);
}

一旦真正的公钥恢复了,我们可以用程序循环计算出可能的seed3和seed4,就说道这儿。我还要简单提一下seed1和seed2,他们仍然用相同的方法隐藏在相同的地方,所以前面提到通过job结构和vendorcode结构找seed1和seed2的方法依然有效。

Nolan Blender.

附do_handshake函数

static void do_handshake(char *charptrname, char *idxname, char *switchname)
{
  char charname[MAXVARNAME];
  int i[4];
  char zeroname[MAXVARNAME];

        randvarname(charname, "charname");
        randvarname(zeroname, "zero");
        srand16((seed3 ^seed4) & 0xffff, (seed3 + seed4) & 0xffff,
                ((seed3 ^ seed4) >> 16) & 0xffff, lmrands);
        rand16(lmrands, 1);
        rand16(lmrands, 1);
        rand16(lmrands, 1);
        i[0] = rand16(lmrands, 1);
        i[1] = rand16(lmrands, 1);
        i[2] = rand16(lmrands, 1);
        i[3] = rand16(lmrands, 1);
        fprintf(ofp,
"        if (%s == %s) %s\n\
        {\n\
          unsigned char %s = *%s;\n\
          unsigned int %s = %s/%d; %s\n\
\n\
                if ((%s == %s) && ((%s ^ %d) & 0xff)) %s ^= %d;\n\
                if ((%s == (%s + 1)) && ((%s ^ %d) & 0xff)) %s ^= %d;\n\
                if ((%s == (%s + 3)) && ((%s ^ %d) & 0xff)) %s ^= %d;\n\
                if ((%s == (%s + 2)) && ((%s ^ %d) & 0xff)) %s ^= %d;\n\
                *%s = %s;\n\
                return 0;\n\
        }\n", switchname, bytenames[1],
        DEVCOMMENT("/* == 1 for handshake */"),
        charname, charptrname,
        zeroname, charname, rand16(lmrands, 1)%400 + 257,
        DEVCOMMENT("/* The result of the divide is guaranteed to be zero */"),
        idxname, zeroname, charname, i[0], charname, i[0],
        idxname, zeroname, charname, i[1], charname, i[1],
        idxname, zeroname, charname, i[2], charname, i[2],
        idxname, zeroname, charname, i[3], charname, i[3],
        charptrname, charname);
}

static
int
decode(job, d, buf, key, junk, rcvmsg)
LM_HANDLE *job;
long d;
char *buf;
long key;
char *junk;
int rcvmsg;
{
  int verrev;

#ifndef RELEASE_VERSION       
        which_failed++;
#endif
        verrev = (job->daemon->ver * 100) + job->daemon->rev;
        memset(junk, 0, MAX_HAND_DATA_LEN+1);
        l_encode_long_hex(junk, l_modify((long)d));
/*
*        Handle the case where it's a pre-v6 server, and
*        it's the extra handshake tests
*/
        if ((rcvmsg == -1 && (verrev < 579) )) return 1;

        if (rcvmsg != LM_HANDSHAKE && rcvmsg != -1)
        {
                LM_SET_ERRNO(job, LM_BADHANDSHAKE, 375, 0);
                return 0;
        }

        l_str_crypt(junk, MAX_HAND_DATA_LEN, key, 1);
        if (!memcmp( buf, junk, MAX_HAND_DATA_LEN)) return 1;

        if (rcvmsg == -1)         LM_SET_ERRNO(job, LM_BADHANDSHAKE, 376, 0)
        else                         LM_SET_ERRNO(job, LM_BADHANDSHAKE, 377, 0)

        return 0;
}


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (3)
雪    币: 135
活跃值: (191)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
这个太老了~
Envrypted_seed3/4是7.2x时代的事情了。。。。

现在的ECC的私有钥匙是不存在DAEMON中的。这是关键,

LM_SEED1~3是关键。找出的可能性很小!

只能Patch!
2007-9-18 14:06
0
雪    币: 1919
活跃值: (901)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
3
辛苦了,还是支持哈~~
2007-9-18 15:13
0
雪    币: 257
活跃值: (369)
能力值: ( LV12,RANK:370 )
在线值:
发帖
回帖
粉丝
4
太老了~~~~~~~~~~~~~仍然支持
2007-9-18 16:58
0
游客
登录 | 注册 方可回帖
返回
//