ROCKEY系列加密锁 ---- 加密参考八方案

        飞天诚信科技股份有限公司通过几年来的努力,在软件加密方面,总结了八套加密方案,完全斩断了黑客之手,能有效地杜绝软件盗版。欢迎阅后与我们联系,做进一步探讨。

        方案一:

        取得系统的时间,根据系统的时间进行加密检测。

        例如:Function 1 内部的加密检测是每次运行都要检测。

        Function 2 是在软件安装一个月后开始检测。

        Function 3 可以在软件安装半年后开始检测。

        [要求] Function 1, Function 2, Function 3 中的加密锁检测子程序应该各不相同, 否则很容易被人找到一个后, 利用串匹配的方法找到所有的加密点。 如果有可能, 最好取得系统时间的函数也采用不同的方法(在DOS 下可用 INT 21 的 2A 功能或利用 I/O 检测 CMOS 在 Win95 下可以用 GetSystemTime 和 GetLocalTime的系统服务来取得时间)。

        [优点] 这种方法的加密很难被人破解得彻底, 对于那些解密者本身不是软件的使用者的情况能做到很好的保护。

        [缺点] 因为要实现加密锁的多个检测子程序, 在编程上要花费更多的时间。 而使用者可以通过修改系统时间来屏蔽检测(可以利用记录最后运行时间的方法来进行进一步的保护, 但对于发现了这个软件的保护是同时间有关的解密者而言, 这种附加的保护并不能阻挡很多时间)。

        方案二:

        记录软件的运行次数, 根据运行次数进行加密检测。

        例如:Function 1 内部的加密检测是每次运行都要检测。

        Function 2 是在软件运行 20 次后开始检测。

        Function 3 可以在软运行 50 次后开始检测。

        [要求] Function 1, Function 2, Function 3 中的加密锁检测子程序应该各不相同。 这种加密的运行次数应该有多份记录, 每个加密点检测相对独立的记录。 如果有可能,一个加密点可以对应多个记录, 然后进行随机的抽测,但一个加密点所对应的多个记录同另一个加密点所对应的多个记录不应有太多重叠。

        [优点] 这种方法的加密很难被人破解得彻底, 对于那些解密者本身不是软件的使用者的情况能做到很好的保护。

        [缺点] 因为要实现加密锁的多个检测子程序, 在编程上要花费更多的时间。 而且需要一个附加的文件来保存这些记录。

        方案三:

        利用锁中记录的数据保护功能调用。

        例如:

int add(int a, int b){
    return(a+b);}
    int err(){
    printf("No dog\n");
}
void main(void){
    int (*ptr)();
    int c;
    int write_to_dog, read_from_dog; //write_to_dog = read_from_dog
    write_to_dog = add ^ err;
    ptr = err;
    ptr = ptr ^ read_from_dog;
    c = ptr(3, 4);
}

        上面的程序只是一个示例说明, 不能被编译执行, write_to_dog = add ^ err 这行由于 C/C++ 编译器的问题必须通过汇编来实现。

        上面的程序中例如编译后 add 函数的地址是 0x0071df32,err 函数的地址是 0x006504f3, 那么程序在开始的时候

write_to_dog = 0x14dbc1

        这样, 如果被加密的程序读锁正确 ptr = 0x006504f3 ^

0x14dbc1 = 0x0071df32 就是 add 的地址, 如果读锁错误

read_from_dog 应该为 0, 那么 ptr = err 的地址

        [要求] write_to_dog = add ^ err; 和写锁的操作最好不出现在被加密的程序中, 但要注意每次编译后 add 和 err 的地址都会变化。

        [优点] 这种方法能够保证解密者在没有加密锁的情况下不可能完成解密的工作。 在加入另外一些变化后可以保证目标程序难以理解。

        [缺点] 因为写到锁里的加密内容同编译结果有关, 每次编译后要重新写锁。 比较适合那些在版本升级时不做改变的固定被调用的程序。

        方案四:

        利用锁中记录的数据进行计算工作。

        例如:

double SinAngle(int angle){
    PI = read_from_dog;
    return(sin(angle*PI/180))
}

        [要求] 最好锁里面存放的是浮点数, 因为浮点计算对解密的工作造成极大的困难。 而且计算算法要进可能的复杂些, 保证不被解密者能在汇编的级别上可以理解。

        [优点] 这种方法的加密所造成的错误不会产生很明显的区别, 但没有正确加密锁的时候, 其结果肯定是错误的。 解密者很难找到问题的出发点。

        [缺点] 考虑到软件锁的硬件原因, 有可能因为软件锁没插好或一些突发的打印事件造成的读入数据的错误, 除了要多读几次外, 不建议对那些可能造成直接经济损失的工程计算的关键算法进行加密。

        方案五:

        利用软件锁的存储器做变量进行加密

        例如:

int a;
a = x + y;
writedog(a);
readdog(a);
d = a+b*c;

        [要求] 这是利用软件锁内部的存储单元来保存程序的变量。 因为程序运行时 a 每次都有可能不同, 所以 readdog 的返回值也会各不相同。 但由于大部分锁中的存储单元有写寿命的限制, 每次运行软件时的写操作不应太多。

        [优点] 用这种方法加密的软件解密者只找到读锁的部分是不行的。而且如果锁内的数据是进行不同程序之间的数据交换, 解密者除了要看懂两段程序外, 还必须边编制自己的仿真程序来完成这个数据交换才行。 这是一种对解密者来说极其困难的加密手段。 而且这种加密是利用每个锁的初试化序列和密码的不同进行的加密, 对锁的内部数据内容没有要求, 节约了在软件销售前的写锁时间, 特别适合那些经常升级的软件。

        [缺点] 因为这种加密手段在程序执行过程中要对加密锁进行写操作, 除了要减少锁的寿命外, 而且造成了锁的不安全性(例如在写锁的时候突然停电)。 而且不能防止解密者通过购买相同型号的加密锁后的破解。

        方案六:

        利用加密锁进行初试化保护。

        例如:程序 a 是要被加密的文件, 程序 b 是读锁的文件。 程序 a 开始执行时调用程序 b。 程序 b 中进行各种锁的检测工作, 如果检测成功, 生成一利用当前日期保护的数据文件 c, 回到程序 a。 程序 a 利用当前日期对数据文件c 进行解码工作, 并用解码后的结果进行程序的初始化工作。

        [要求] 程序 b 必须被很好的保护, 因为程序 b 是一个独立的执行文件, 可以对这个文件进行尽可能好的保护。 如果可能的话, 最好对数据文件 c 进行某些压缩工作。 然后在程序 a 中进行解压缩。 而且可以在程序 b 中完成对程序 a 的完整性检查。

        [优点] 这种方法的加密因为对象比较单一, 实现起来比较容易而且数据块 c 可以同时带有多个程序的初始化变量, 容易实现多模块加密。 而且由于数据文件 c 是纯的数据加密, 可以同现在成熟的数据加密算法相结合, 如 DES,RAS 而且由于程序 b 是个独立的程序, 不会被程序升级的工作所影响。

        [缺点] 整个程序的加密重点放在了程序 b 上, 如果这个文件被解掉, 整个程序就被解了。

        方案七:

        利用种子码功能加密。

        ROCKEY 加密锁具备种子码功能,它的基本原理就是,当用户输入一个 WORD 值,返回 4 个WORD 值,但输入与输出之间的关系是保密的,用户也不知道。相同密码的加密锁,当输入值相同的时候输出值也相同,不同密码的加密锁输出值不同。种子码功能实现了加密锁检测的另一种手段。我们可以看看这种检测方法同传统的检测方法有何不同:传统的加密锁检测方案是首先根据密码操作加密锁,如果错误说明指定密码的加密锁不在,如果正确则从加密锁中读出相应的内容进行比对,进一步检查是否是自己的加密锁。

        例如:

if (CheckDongle() == 1){
    ReadDongle(str);
    if (str == "My Dongle"){
        正确...
    }
    错误...
}

        错误...

        加密者: 这种方法结构简单,很容易被黑客找到并破解。

        黑 客: 那还用说...

        那么我们来看看种子码算法的例子:

if (CheckDongle() == 1){
    seedbuf = GetSeed(xxxx);
    if (seedbuf[0] != aaaa) 错误...
    if (seedbuf[1] != bbbb) 错误...
    if (seedbuf[2] != cccc) 错误...
    if (seedbuf[3] != dddd) 错误...
    正确...
}

        加密者: 因为输入的 xxxx 可以是任何值, 那么返回的值也不尽相同。同传统的方法相比算是进了一步。

        黑 客: 太简单了,你必须预先记录下 aaaa, bbbb, cccc, dddd 的值用来比对,我只要把这些判别一改就可以了,我甚至不需要加密锁就能破解。

        那么我们看看下面的例子:

if (CheckDongle() == 1){
    seedbuf = GetSeed(xxxx);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
}

        加密者: 这个例子是用种子码的结果把特定的数据变换一次。这种方法可以保证在没有锁的情况下,软件不可能被解密。

        黑 客: 嗯..., 我在有软件锁的情况下能破解。我先在有软件锁的情况下运行一次,记录下seedbuf 的返回值。然后屏蔽掉 seedbuf = GetSeed(xxxx) 一行, 而把初正确的值预先就放到 seedbuf 中。

        那么看看这个例子:

if (CheckDongle() == 1){
    seedbuf = GetSeed(aaaa);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    seedbuf = GetSeed(bbbb);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    seedbuf = GetSeed(cccc);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    seedbuf = GetSeed(dddd);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
}

        加密者: 这回,我的 seedbuf 反复用了 4 次,每次的内容都不同。

        黑 客: 这个...,这个...,看来我得找个空的地方来记录一下你的 seedbuf 的值, 喂! 你的seedbuf 能不能开的大一点?

        我又有了个更好的方案:

s = xxxx
if (CheckDongle() == 1){
    seedbuf = GetSeed(s);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    s = seedbuf[s%4];
    seedbuf = GetSeed(s);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    s = seedbuf[s%4];
    seedbuf = GetSeed(s);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    s = seedbuf[s%4];
    seedbuf = GetSeed(s);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
    s = seedbuf[s%4];
    seedbuf = GetSeed(s);
    for (i=0;i<1000;i++){
        MyData[i] = MyData[i] ^ seedbuf[i%4];
    }
}

        加密者: 这回,我的 seedbuf 不但反复用了 4 次,而且每次 seedbuf 的内容都是依赖上次变换的结果。你还能破吗,黑客? 黑客? 你在哪儿……?

        方案八:

        利用模块字功能加密。

        ROCKEY 加密锁为多模块软件加密引入了模块字的概念,模块字是为了区分同一软件的不同模块的加密而设立的。模块字在有二级密码下可以写入,但不能读出,必须通过对软件锁编程的方式来读出模块字。但很多用户要加密的软件只是个单一模块的软件,不需要多模块加密,这时候模块字就可以用来作一些其它的加密工作了。在这里我们设计了一套利用模块字对单一软件进行加密的方案。

        首先在模块字的 0 - 11 中写入 12 个不同的值。

        在计算1 的工作中会根据输入的模块号把对应模块的值代入。这里我们把模块号定为月份, 例如在 1月输入模块号1, 2月输入模块号2...

        写入的算法如下:

A = A + G (G 就是模块字)

        那么写入的算法因为模块号的不同(也就是月份的不同), 返回的结果也不同。可以说软件的加密算法每个月都在变。这种方法仅仅对模块字的应用举的一个例子。用户可以根据自己的需要设计出更多更复杂的算法。脑筋比较快的用户现在应该想到模块字的递减功能也不是仅仅控制 Demo 版软件才能用的了。