- UID
- 6086
注册时间2005-12-29
阅读权限10
最后登录1970-1-1
周游历练
该用户从未签到
|
楼主 |
发表于 2006-7-8 17:52:16
|
显示全部楼层
4.7 软件狗
软件狗是一种智能型加密工具。它是一个安装在并口、串口等接口上的硬件电路,同时有一套使用于各种语言的接口软件和工具软件。当被狗保护的软件运行时,程序向插在计算机上的软件狗发出查询命令,软件狗迅速计算查询并给出响应,正确的响应保证软件继续运行。如果没有软件狗,程序将不能运行,复杂的软硬件技术结合在一起防止软件盗版。真正有商业价值得软件一般都用软件狗来保护。
平时常见的狗主要有"洋狗"(国外狗)和"土狗"(国产狗)。这里"洋狗"主要指美国的彩虹和以色列的HASP,"土狗"主要有金天地(现在与美国彩虹合资,叫"彩虹天地")、深思、尖石。总的说来,"洋狗"在软件接口、加壳、反跟踪等"软"方面没有"土狗"好,但在硬件上绝对无法破解(应当说破解难度非常大);而"土狗"在软的方面做的很好,但在硬件上不如"洋狗",稍有单片机功力的人,都可以复制。
4.8 Vbox 保护技术
Vbox 是一个软件 。它是用来保护其它软件的 。凡被 Vbox 保护的软件,一旦过了试用期,就不能再使用了,删了重装也没用,除非删除整个操作系统再重装。
4.9 SalesAgent 保护技术
SalesAgent 保护的软件一般具有 x 天试用再购买的接口,是一种时间限制保护方式。才用这种保护方式的软件主要有 Macromedia Flash 4 、DreameWaver 等等。
4.10 SecuROM 保护技术
SecuROM (http://www.securom.com)是Sony 开发的一种商业光盘加密技术,它可以阻止用户对加密光盘的复制,被保护的光盘上有 CMS16.dll 、cms32_95.dll 、cms32_nt.dll 这几个文件。很多游戏光盘才用这种保护技术。
4.11 软盘加密
通过在软盘上格式化一些非标准磁道,在这些磁道上写入一些数据,如软件的解密密钥等等。这种软盘成为"钥匙盘"。软件运行时用户将软盘插入,软件读取这些磁道中的数据,判断是否合法的"钥匙盘"。
软盘加密还有其它一些技术,如弱位加密等等。
随着近年来软盘的没落,这种方法基本上退出了历史舞台。
4.12 将软件与机器硬件信息结合
用户得到(买到或从网上下载)软件后,安装时软件从用户的机器上取得该机器的一些硬件信息(如硬盘序列号、BOIS序列号等等),然后把这些信息和用户的序列号、用户名等进行计算,从而在一定程度上将软件和硬件部分绑定。在我的加壳程序中将使用这种方法和其它方法的结合,后面会详细说明。
4.13 加壳
就是在完整的软件上--已编译连接,可以运行的程序上,加上一个"壳",这个"壳",对软件进行保护,这些壳一般综合运用了4.1~4.6 节所述的软件保护技术。因为我的设计方案中使用了加壳技术,后面将会详细说明,这里不再赘述。
5 该软件的设计思想
5.1 传统保护的不足
上一章介绍了当前流行的一些软件保护技术,其中有些工作得非常好,如序列号技术,几乎所有的软件都使用了这种技术。
这里我不会指出这些技术如何会被破解,因为前面已经说明了,软件保护都可以被破解。只说出这些方案的"非技术"的缺点。
可以看出,这些技术中,软件和硬件仍然是分离的。
在软件狗保护中,软件和硬件有了一定的结合,但是,还没有把软件和一台特定的机器绑定 。在软盘保护中,软件和硬件也有一定结合。但是用户仍然可以在多台机器上安装同一套软件。只在需要这些硬件的时候时候,如插上软件狗,插入钥匙盘,就可以在多台机器上使用同一套软件了 。并且,要正版用户在使用软件时要插上软件狗,插入钥匙盘,造成很多不必要的麻烦 。如用户的并行口可能用户打印机……还有,由于加入了硬件,这种保护方法的成本较高,对一些比较小的软件,这种方法是不实用的。
而 CD-Check 等光盘加密技术,有个缺点是使用中用户必须插入光盘,而现在的硬盘技术的发展,使得存储容量不再是一个问题,用户往往把光盘上的所有东西都装入硬盘,而要用户在每次运行软件时都插入光盘,有点难以接受。
而比较看好的序列号保护技术,则存在一个通病--算法要软件开发者自己设计,而且,如果一对(序列号,用户名)被 Craker 从 Internet 上发布出去,所有的用户都可以用这对(序列号,用户名)来"注册"软件,从而非法使用。
5.2 网络的流行
现在,我想没有哪一个使用过计算的人没有使用过 Internet。全世界有几亿人在使用 Internet,我们中国有几千万万人在使用 Internet 。许多商业软件也都有 Internet 试用版,共享软件(这里不讨论免费软件)甚至都是在 Internet 上发布的 。而几乎所有的软件在 Internet 上都有破解版 。于是,把软件保护和 Internet 结合起来是自然而然的事 。
要把软件保护和 Internet 结合起来,就自然要保证安全 。要保证安全就离不开密码学,在第一章已经简要介绍了密码学的一些概念 。网络上信息传输的安全很重要,特别是一些敏感信息,如用户资料,密码等等 。
5.3 我的方案
针对前面的一些问题,通过各方面比较,权衡,我提出了这套软件保护方案 。
这套方案集传统的序列号保护 、利用硬件信息保护 、加壳保护 、反跟踪 、反-反汇编 、反 Dump 、反 API 断点等于一身。又加入了密码学中的数字签名 、散列 、密钥交换等等 。形成了我自己独特的 、加密强度更高 、使用上更方便(现在只是作为一个演示,使用上还谈不上方便)、更合用户口味的反盗版 、电子注册解决方案 。
该方案的具体细节将在接下来的两章中进行描述。
5.4 该方案的可行性分析
可以说,没有密码学和网络的话,这个方案只能是纸上谈兵 。我正是看到了这两点,才萌发了这个方案的设计思想 。这个设想在 3 个月之前有了一个模糊的想法,在随后,经过查阅大量相关资料,加上自己多方面的考虑,逐渐地就在我的脑海里有了一个清晰的轮廓,这三个月的时间,只是要把这个设想变成现实 。
现代密码学(特别是非对称密码学),是直到 70 年代才初现端倪的,但是发展很快,到现在,我们已经可以使用许多现成的密码算法 。这些算法甚至成为 Windows 不可分割的一部分,称为 CryptoAPI,如果没有 CryptoAPI,我将不得不把庞大的算法库包括在我的源代码内,甚至有许多算法要自己编写代码实现 。这对我将是一个灾难 !
为了简化设计,并突出主要问题,不拘泥于花俏的外表,我不会去做图形用户接口,而使用简单的 Consol 控制台用户接口 。
踩在巨人的肩膀上,将看的更高,更远。正是有了这么多前人的努力,我才能在此之上进行自己的创新。
Internet 的流行,盗版软件的泛滥,几乎成为我们中国民族软件企业的灾难 。使得该软件具有很积极的现实意义 。--只需要极少的修改,增加图形界面,本系统即可作为商业应用。甚至不需要修改,只编写一些简单的批处理文件,都可以让软件开发者和普通用户方便的使用(后面的演示就是使用简单的批处理文件来简化用户界面的)。
正是基于这些原因,比起传统方案,本方案有以下优点:
(1) 当前许多软件保护技术,大多只求精于使用操作系统本身的特点 、沉溺于繁杂的技术细节,而不考虑使用更好的密码学协议 、算法。本方案不同,首要的是使用可靠的密码学协议 、算法,使加密强度得到了保证。
(2) 通过网络对软件授权 、是当前流行软件保护技术的一个盲点,而综合使用数字签名 、散列技术 、密钥交换等技术的,更是少之又少。据我所知,有一个俄罗斯人编写的非常有名的加壳软件,使用了很多的密码算法--被人们戏称:"用光了世界上所有的密码算法!"但是他仍然没有数字签名以及密钥交换,并且也是单机的。
(3) 通过网络实现软件授权,成本很低,用户使用又非常方便,软件开发者管理软件的销售 、代理 、等等也很方便 。
(4) 本方案不光在密码算法 、协议上设计得很合理,而且,在最终受保护程序执行时,也比传统方案要好,这些将在详述壳程序主要技术时具体说明 。
(5) 本方案有很强的可扩展性,在软件授权 、密码协议的实现上使用了面向对象方法 。在壳程序的开发中使用了结构清晰 、分层次分明的分析、设计 。几个模块相对独立,在此基础上,各个模块在遵循一套规则的前提下可以相对独立设计,如相同的 Merge 、Register 程序,可以用不同的 Shield 壳程序来保护客户软件 。
(6) 本软件使用 VC 和汇编语言组合开发,使得开发成本降低了许多,软件的可靠性 、可用性 、可测试性 、可维护性都有很大的提高 。而当前几乎所有的同类软件都是用汇编语言开发,成本很高 。--如果本软件完全使用汇编语言,在这短短的三个月之间是不可能完成的 。
(7) 使用 CryptoAPI,使该软件的整体设计独立于具体的密码学算法,为了使用不同的算法,只需要修改相关参数即可,而不需要重新编写大量代码;并且,使用 CryptoAPI,使得该软件的体积大大缩小 --如果自己编写密码算法,软件的体积将异常庞大,至少是目前的 3倍以上 。
6 该软件的整体构架、开发工具及方法
6.1 需求分析
本软件的需求,就是一个软件的授权协议,及保护壳应该完成的保护功能,下面将简单说明:
(1) 角色说明(软件授权协议中的各个角色):
P :是一个软件产品;
A :是 P 的开发者;
B :是 A 的一个代理商(也可以是 A 自己) ;
C :是 P的最终用户。
(2) 软件授权的协议执行过程
约定:
(a) Server 、Merge 、Register 都运行在不同的机器上 。Server 运行在 A 的机器上,Merge 运行在 B 的机器上,Register 运行在 C 的机器上 。
(b) 服务器对开发者卖出的软件进行授权----当然他的软件是用我的软件保护过了的 。
(c) Server 是昼夜不停一直运行着的,它接收来自 Merge 和 Register 的请求 。
(d) 在下面的协议叙述过程中,Server 和 A 、Merge 和 B 、Register 和 C 交叠使用 。它们多数时刻是同义词 。
协议开始执行:
这里对授权协议的说明只是简单的说明该软件的需求 。后面章节将详细地说明软件的授权过程,并给出协议所用的算法 。
(a) A把他的软件 P 给 B,A 运行 Server 。
(b) B 要卖出一套软件 P,就运行 Merge 程序。
Merge 程序产生一个随机的 SN,将此 SN 发往 Server (即 A )。
(c) Server 收到 SN,从注册数据库中查找 SN,如果找到(能找到该 SN 的概率的数量级在 以下),就发回信息给 Merge,它产生了重复的 SN,要它再重新计算一个 SN 。如果未找到(几乎总是找不到的),就把 SN 登进注册数据库,用自己的私人密钥 ASK对 SN 进行数字签名,得到 K1,把 K1 作为解密密码发给B , 同时也将自己的公钥 APK 发给 B 。B 用 K1 对 P 进行加密 。
(d) 软件卖给 C 后,C 可以在本地主机(用户自己的计算机)上,通过网络(拨号上网,或 ADSL,或其它方式)向开发者的网络服务器注册软件,然后才能使用 。C 运行注册程序 Register,Register 从 Q 中取得序列号SN(serial number) , 再取得本地主机的硬件信息 ,计算并存储该硬件信息 HD 的散列值 SAC (System Autentication Code)。 将它发送给服务器 Server 。服务器 Server 从数据库中查找这个 SN,如果找到 , 并且这个序列号的拷贝已经注册 , 并且 它收到的 SAC和以前注册的 SAC相同--相同拷贝可以在同一台被授权的计算机上多次安装/注册--或者找到了 SN,但该 SN 还未注册,就将随 SN 一起发来的 SAC 存入数据库,待以后再验证这台计算机。Server把SN对应的解密密码K1发给用户 C,同时将自己的公钥 APK 也发给 C 。
(e) Register 程序用K1解密 Q,同时用本地主机硬件信息HD的另一个散列值 K2 作为密钥加密 Q,最终得到 R 。
现在,用户C 可以运行软件 R 了(R 就是经过注册的P),R具有了一些反Cracker 功能及其它保护功能。
(3) 最终受保护的软件 P (即上面授权协议执行到最后产生的可执行程序 R )应该具有的功能:
(a) 只能在注册的那太机器上运行(反非法使用) ;
(b) 病毒检测和 Cracker 更改检测(保护功能) ;
(c) 反跟踪功能(Anti-Debug,或称 Anti-Trace );
(d) 反 Dump 功能(Anti-Dump);
(e) 反反汇编功能(Anit-Disassembler);
(f) 其它反 Crack 功能 。
(4) 协议的图示:
图6-1中方框表示处理的文件,Shield 是"壳程序", 椭圆表示"处理过程",也即该软件的模块 。
该图在后面的章节中还要引用到 。
6.2 整体框架
从上一节的需求分析可以看到,本软件至少要三个独立的模块:
(1) Server 服务器模块 。
(2) Merge 产生软件拷贝的模块 。
(3) Register 最终用户注册软件的模块 。
然而,还差一个模块,即保护软件的"外壳"模块 。这个模块就叫做 Shield,意思是保护壳 。
整个软件就划分为这四个模块 !
它们的关系如图 6-1,授权协议的过程同时也指出了这四个模块之间的关系。
6.3 各取所长(汇编与 C/C++ 各取所长)
从图中可以看出,Server 、Merge 、Register 模块都没有涉及到底层的操作 。而 Shield 有没有涉及到操作系统底层,由前面的图形及协议过程还不能得出 。
但是,从需求可以知道:Shield 是将要附加到受保护的软件上的,运行受保护的软件时,Shield 程序将首先运行,从文件中提取一些数据,做一些必要的检查,还要解密,还要进行反跟踪,反-反汇编,最终还要在比较容易控制的情况下跳转到原程序的入口等等 。
由于 Shield 程序要进行这些底层的操作,使得它不适用高级语言开发,甚至不可能用高级语言开发 。这时,不得不使用汇编语言--最强大 、最高效,同时也是最难使用的语言 。
但是,从需求中也可以看到,Shield 程序也要使用一些高级的算法,如密码学算法,检验文件完整性算法等等 。这使得如果 Shield 程序完全用汇编语言开发,这么多复杂的,需要较高技巧的算法,可能都要用汇编语言写 。而这些并不是汇编语言所特长的,用汇编语言写的结果只能是:代价高昂,并且可扩展性,可维护性都很差 !
经过认真的考虑,阅读大量的资料,我终于找到了一个折衷的办法,把必须用 、不得不用汇编语言写的部分,如要从文件中提取数据的部分,以及反跟踪,反-反汇编,内存布局设计部分,用汇编语言写,其余部分,涉及到复杂算法的,用 C++ 写 。然后把两部分结合编译。
而 Server 、Merge 、Register,由于不涉及到这些底层的操作,可以全部用高级语言写,我用的是 C++,主要是因为:虽然这三个模块不涉及到最低层的操作,然而它们仍要和 Shield 进行通信(Server 不和 Shield通信),有些算法它们(Merge 、Register和Shield) 还要共用,于是高级语言选择 C++ 是理所当然的 。
至于开发工具,C/C++ 开发工具我选用 VC6.0,不用多说,汇编语言开发工具选用 MASM,版本是7.0,这是程序员使用最多的汇编语言。
6.4 C/C++ 与汇编语言混合编程时的互调协议
既然 Shield 是用 C++ 和汇编语言混合编写的,那么它们之间通信(即互相调用)就是不可避免的。要通信,就要服从共同的协议,经过查阅大量的资料 。我对 C/C++ 和汇编语言之间的通信终于了如指掌 。下面将详细说明:
(1) 命名约定:
VC 编译 C 文件(不是C++文件),产生的目标文件(.obj文件,也即编译器产生的C和可执行文件"之间"的中间文件)中,每个全局符号(函数名、全局变量名)前面都加了一个下划线"_"。也就是说,如果 C 文件中有一个全局函数(C中也只有全局函数)"fun1",经过编译,在目标文件中,该函数的符号名就是"_fun1"。全局变量也相同。
对 Cpp 文件(C++语言源文件,后面将称为 C++文件)中符号的处理要复杂的多,这里我不打算介绍对类名 、类变量(即类中定义的 static 变量)、类方法(类中的static函数),对象名 、对象变量(类中定义的非 staitc 变量)、对象方法(类中的非 statioc函数)……,这些太多太多,不可能介绍完,并且因为我的设计中也未牵涉到这些方面。下面仅对 C++ 源程序中的全局非重载函数的命名约定进行说明 。
一般的说,C++ 源程序中一个完整的全局函数的声明应该是这样的:
[extern "C"] returntype calling_convension fun_name(paramtype1 [param1] , paramtype2 [param2] ,...);
函数的定义必须和声明完全一致 。对这个函数,定义应该如下:
[extern "C"] returntype calling_convension fun_name(paramtype1 [param1] , paramtype2 [param2] ,...)
{
.....
return v; // v 的类型应是returntype
}
extern "C" 表示目标文件中函数的命名将按 C 语言的协议,为求简化,我的代码中所有C++ 和汇编语言互相调用的函数都加了 extern "C" 。下面也是假定函数有 extern "C" 声明的 。
对不同的 calling_convension,即调用协议,在目标文件中产生的函数名、以及参数传递的次序,一般都不同。calling_convension 将在下面详细说明。现在只说函数名。
如果 calling_convension 是 __stdcall,产生的函数名,在源程序的函数名之前加一个下划线,在函数名之后加一个"@",后面在加上该函数形式参数区的字节数(10进制表示)。如对该函数,假设该函数有三个long类型(C语言标准规定long类型在所有的机器中都必须是32位)的形式参数,目标文件中的函数名将是 "_fun_name@12"。
如果 calling_convension 是 __cdecl,产生的函数名,在源程序的函数名之前加一个下划线,在函数名之后没有 "@",后面也没有该函数形式参数区的字节数(10进制表示)。如对该函数,假设该函数有三个 long形式参数,目标文件中的函数名将是 "_fun_name"。这个命名协议实际上就是C 的标准命名协议 。
其它的 calling_convension,在后面的一个表(表6-1)中示出。
(2) 参数传递协议
对 extern "C" __cdecl 函数,参数传递是从右向左压入堆栈,并且堆栈的恢复由调用者(caller)完成,即对上述函数,如果有以下调用:
fun_name(x,y,z);
产生的汇编指令将是:
push z
push y
push x
call _fun_name
add esp , 12
fun_name 的函数体如下:
_fun_name proc
push ebp
mov ebp , esp
....
ret ;这里未跳过参数区(未恢复堆栈)
_fun_name endp
这里只举这个例子 。这类函数由调用者恢复堆栈而不是由被调者(callee)恢复堆栈的一个主要原因是 C 语言允许参数个数可变的函数,如 printf 。这样,callee将"不知道"传递给它的参数到底有几个,而 caller 知道 。这样在一定程度上降低了效率 。后面你将看到 。
对extern "C" __stdcall 函数,将有如下代码:
函数调用 :
fun_name(x,y,z);
产生的汇编指令将是:
push z
push y
push x
call _fun_name@12
;;add esp , 12 ;;没有这条指令,堆栈已由被调函数恢复
fun_name 的函数体如下:
_fun_name@12 proc
push ebp
mov ebp , esp
....
ret 12 ;这里跳过了参数区(恢复了堆栈)
_fun_name@12 endp
可以看到,__stdcall 的函数调用产生的指令条数少了一条堆栈恢复指令,代码体积自然小了,执行速度也快了。但丧失了灵活性(一般情况下,传给被调函数的参数必须与函数定义及声明的参数区字节数相同,对该函数,就是 12 个字节,见后面的说明)。
(3) 非 extern "C" 的函数 :
对于非 extern "C" 的函数,参数传递协议差不多,但函数命名协议就有些不同,如对非extern "C" 的 __stdcall 函数 。命名是在函数名前面加上 __imp__ ,函数名后面再加上 @ 和参数区的字节数 。
因为该软件C++语言和汇编语言之间的通信未使用这种调用协议,这种例子不列举了。
(4) 各种函数调用协议列表(表6-1):
cdecl syscall stdcall basic fortran pascal
前面加下划线 Yes Yes
转化为大写 Yes Yes Yes
参数传递方向 ← ← ← → → →
堆栈恢复 caller callee 注 callee callee callee
保存bp(ebp) Yes Yes Yes
参数个数可变否 ? Yes Yes Yes
表 6-1 各种函数调用协议
注: 对 stdcall, 当形式参数个数可变时,stdcall 型的函数调用由调用者(caller)来恢复堆栈,而不是被调者(callee),这是显而易见的。在这种情况下,调用协议就和cdecl一样了。
6.5 该软件中各模块对语言特性的限制及解决方法
Server 、Merge 、Register 由于不涉及到系统底层的处理。所以 C++ 的所有语言特性都可以使用 。
然而 Shield 模块不同,因为它附加到其它程序上运行时要进行重定位,所以,所有的"绝对地址"必须进行转化,包括静态变量的地址和函数的地址,C++中用 static 修饰的变量,和全局变量,还有用引号定义的字符串,都是静态变量,它们的绝对地址在程序编译时已经确定 。对函数的调用,编译器一般使用的是函数的相对地址,即相对于 call 指令的下一条指令的地址 。如:
....
....
:01006432 call fun1 ; 这里的机器码将是 E800001000 :注
:0100643B mov eax , 1000
......
:0100743b push ebp
:0100743c mov ebp , esp
....
...
注:E8 是这条 call 指令的操作码,00001000 是这条call 指令的操作数,这个操作数是一个相对地址:
0100743b-0100643b == 00001000 。
这样的指令,不管重定位到什么地方都没问题 。但是,C++ 中的虚函数不同(详情请参阅参考文献[13])。它的调用是这样的:假设一个对象anObject ,anObject对象所属的的类有一些虚函数,下面将以图示(图6-2)。
从图6-2可以看出,对象 anObject 中有一个指针 vftable,指向一个虚函数表,虚函数表中的每项都是一个函数指针,指向一个函数 。这些指针(地址)都是绝对地址 。
这种机制在一般的程序中工作的很好 。但是,在 Shield 壳程序中,这种机制是不能工作的 。因为 Shield 要附加在任何受保护的程序上都能运行 。再看看 Client (受保护的软件)和 Shield 的部分示意图(图 6-3):
可以看到,Client 的基址是 0x01000000,Shield 的基址是 0x00400000,当把 Shield 附加到 Client 上之后,图示如下(图 6-4) :
可以看到,vfun1 的地址发生了变化,变成了 0x01101486,而虚函数表中的 pvfun1 仍是 0x00401486 --且不说对象 anObject 中的"虚函数表指针(地址)"的也是错的。这样,运行时当然会出错 。
上面说的只是函数地址的变化,全局数据也一样,地址也会发生变化--因为全局变量的地址也是在编译 Shield 时确定的 。
这样,在 Shield 程序中,就只有两种办法了:
(1) 把虚函数表中的函数地址(和anObject对象中的虚函数表指针)转化成 Shield 附加到 Client 上之后的地址。
(2) 不用虚函数。
因为虚函数机制被 C++ 语言封装起来了,进行地址转化不大可能,所以,在 Shield 程序中"决不能使用虚函数"!
然而,上面也说了,静态变量的地址在 Shield 附加到 Client 上之后也不对了 。由于静态变量没有像虚函数那样被 C++ 封装得那么彻底,所以可以使用静态变量,但要进行地址转化 。地址转化将在后面谈到壳程序的主要技术时详细说明。
上面说了虚函数和静态变量在 Shield 程序中的限制,C++ 中还有一个机制,在 Shield 中也是不能使用的,那就是异常处理机制 。即 try,catch,throw 。异常处理也使用了函数的绝对地址,不能使用的原因和虚函数相同,这里就不再赘述 。
还有,C++ 中的动态内存管理函数,即 new 、delete 也不能使用 。因为它们都和 C++ 库联系起来了 。--除非自己编写 new 、delete处理函数 。但仍受到一些限制,如设置 new handler 要用到库函数 set_new_handle 。
Shield 程序中对 C++ 的使用还有一些限制:不能使用 C++ 库!C++ 库中少不了虚函数,少不了静态变量,绝对地址,不能使用的原因是显而易见的 。
然而理论上 C 库函数可以使用,但不能使用 C 库函数中的 IO 函数,因为其中的IO函数几乎都和全局变量联系起来,不能使用的原因也是显而易见的。但是 C 库函数中的字符串函数,内存操作函数(不包括内存管理函数,如malloc 、free 等)可以使用。但是因为其它一些原因(将在壳程序主要技术中详述),我也没有使用 C 的这些库函数 。
总之,这些限制,使得开发 Shield 程序犹如做一个嵌入式系统,现成的东西大多不能用 。
6.6 C/C++ 和汇编语言的预编译
由于该软件规模较大--大约有 6000 行代码,而且由于 Shield 程序要交叉编译 、调试,使得开发难度很高 。一个小错误可能要反复修改代码,跟踪若干次才能发现,而如果为了找到一个错误而频繁修改代码 --错误更容易积累,--软件工程的原则是:代码的修改弊大于利 。
并且,有好些算法,如 MD5 、和其它加密 、解密算法,Shield 程序要和其它模块共用,然而,在这些算法中调用的其它函数,在 Shield 中和 其它模块中是不同的 。如果就因为这一点而重写这些共用的算法,成本是非常高的,因为算法中出现错误,就要修改,那两处就都要修改,很难保证修改的一致,这将是一个非常棘手的问题 。
幸好,C/C++ 和汇编语言都有强大的预编译功能,利用预编译功能,上面的问题便迎刃而解 。在这里只是先提出这些问题 。第七章将详细说明这些使用预编译的实现方法 。
7 该软件的实现及技术细节
前面介绍了密码学、PE文件格式、传统软件保护方案,及至该软件的设计思想、策略、整体构架。现在到了最后一步,也是关键的实现细节了。
有了前面的基础及整个方案的规划,实现起来就有了明确的目标,有了正确的路子走。然而路仍很多,走哪条呢?
比如说,在密码方面,密码协议的选用、设计,加密算法的选用、设计,密码函数库的选用,是用源代码形式的库,还是用 DLL形式的库?在网络方面,网络接口的选用,是用 WinSocketAPI,还是用 VC已经包装好的 Socket 类 ?是用 TCP协议,还是用 UDP 协议?如何对待待加密的软件?把它加密后存储在硬盘上,运行时把它作为壳程序的一个子进程(或子线程),还是把它和壳程序交织在一起(这一点在前面的章节露出了一点端倪)?……
这些方案选择的痛苦过程我不想多说,我只说最终选了哪一个和为什么选它。
(1) 密码学协议、算法、库的选用:
(a) 密码协议,选用最简单的单向数字签名。--这对本设计方案也足够了。
(b) 密码算法,数字签名算法选用RSA,不用多说,数字签名中的散列算法选用SHA,SHA 到目前还未露出被破解的迹象。加密算法选用 RC2,不用多说。
(c) 密码库(CryptoLib)选用微软 CryptoAPI,因为它在 Win95 ie3.02 以上的版本都有支持。并且,该库是以 DLL 形式提供,生成的代码体积要小许多。在该软件中频繁是用的 MD5 散列算法,因为考虑到效率及灵活性原因,自己编写的代码实现。 |
|