收藏 分享(赏)

软件加密技术及实现.doc

上传人:dreamzhangning 文档编号:2635205 上传时间:2018-09-24 格式:DOC 页数:101 大小:510.50KB
下载 相关 举报
软件加密技术及实现.doc_第1页
第1页 / 共101页
软件加密技术及实现.doc_第2页
第2页 / 共101页
软件加密技术及实现.doc_第3页
第3页 / 共101页
软件加密技术及实现.doc_第4页
第4页 / 共101页
软件加密技术及实现.doc_第5页
第5页 / 共101页
点击查看更多>>
资源描述

1、软 件 加 密 技 术 及 实 现雷 鹏( 桂林电子工业学院 计算机系 )摘 要 当今盗版 软件的泛滥成灾几乎已经成为了我们中国民族软件的灾难,为了防止软件的非法复制、盗版,保护软件开发商的利益,就必须对软件进行加密保护。现在市面上有 许多反盗版软件,但 这类软 件多是单机处理,并且只使用简单的加密手段,很容易被解密者破解。本文描述了一个通过 Internet,集加密和电子注册于一身的完善的软件保护方案。该方案基于多种密码 学意义上可靠的算法,如 对称加密算法,散列算法,数字签名,密钥交换等等。通过对 Windows 下 PE 可执行文件的结构及载入机制进行深刻的剖析, 巧妙的使用这些密码学算

2、法及多种反破解方案对 PE 文件进行加密保护。在该方案的实现中,使用 CryptoAPI 中的数字签名算法 RSA,加密算法RC2 和 RC4,散列算法 SHA,同时自己编写了使用了 MD5 算法用于快速计算大量数据的摘要;网络接口使用 WinSocket;编程语言选用汇编语言和 C+混合编程方式;反破解方案有检测文件完整性、 检测代码完整性、反跟踪、反反汇编、反 Dump、代码变形等等。由于使用了可靠的密码学算法,使 软件加密的强度大大提高;由于使用了 Internet 在线 注册方式,用户使用也非常方便。关键词 加密 ;数字签名 ;散列;反跟踪 ;电子注册Software Protecti

3、on technique and its realizationLEI Peng( GuiLin Institute of Electronic Technology . The Department of Computing )Abstract The flooding of pirate software has been a calamity of our national software industry . In order to prevent software from pirate , and protect the profit of the software develo

4、per , they must encrypt their software to get a protection . There are several software protection tools in the market currently , but these tools were standalone nine tenths , and they only used simple encryption algorithms , so they could be cracked easily by the crackers .This thesis describes a

5、perfect software encryption and protection scheme which integrate the encryption and electronic register . This scheme is based on multiple reliable cryptographic algorithms such as symmetric encryption algorithm , digital signature , hashing and key exchange . The PE file format (Portable Executabl

6、e File Format) and its loading mechanism under Windows are dissected thoroughly in this thesis . Then these cryptographic algorithms and several anti-crack method are used gracefully to encrypt and protect the PE file .Within the realization of this scheme , the RSA digital signature algorithm , RC2

7、 and RC4 encryption algorithm , SHA hasing algorithm etc in MicroSoft CryptoAPI are used . In order to increase the performace of caculate the digest of large number of data, MD5 hashing algorithm was rewritten . WinSocket API is used as the network interface . The blend of C+ and assembly are used

8、for easily contoling the bottom layer of the system and simplify the programming . The anti-crack method consits the integralization of the file checking , the integralization of the code checking , and anti-debug , anti-disassembly , anti-dump and code metamorphose etc .The reliable cyrpto algorith

9、ms guarantee the crypto strength . As a result of online register , the retail users and the software developers get convenience .Key words Encrypt ; Digital Signature ; Hashing ; Anti-Debug ; Electronic Register目 录1 概述 12 密码学简介2.1 概念32.2 对称密码算法62.3 公开密码算法62.4 单向散列函数72.5 数字签名83 Windows 环境下 PE 文件简介3.

10、1 WIN32 与 PE 基本概念 .103.2 PE 首部 123.3 PE 文件的导入表 144 当前流行的一些软件保护技术4.1 序列号保护214.2 时间限制224.3 Key File 保护 234.4 CD-check .234.5 反跟踪技术(Anti-Debug ) 234.6 反反汇编技术(Anti- Disassmbly) .244.7 软件狗254.8 Vbox 保护技术 .254.9 SalesAgent 保护技术 .264.10 SecuROM 保护技术 .264.11 软盘加密264.12 将软件与机器硬件信息结合264.13 加壳275 该软件的设计思想5.1 传

11、统保护的不足285.2 网络的流行295.3 我的方案295.4 该方案的可行性分析296 该软件的整体构架、开发工具及方法6.1 需求分析326.2 整体框架356.3 各取所长(汇编与 C/C+ 各取所长) .356.4 C/C+ 与汇编语言混合编程时的互调协议 .366.5 该软件中各模块对语言特性的限制及解决方法406.6 C/C+ 和汇编语言的预编译 .457 该软件的实现及技术细节7.1 CryptoAPI 简介 477.2 几个公共函数和宏497.3 模块共用的结构体定义547.4 Shield 模块 .567.4.1 壳程序中 API 和库函数的处理 .597.4.2 壳程序主

12、体627.4.3 加密壳程序637.4.4 运行中修改自身代码647.4.5 代码散列校验647.4.6 跳转到客户程序入口657.4.7 载入并销毁 Client 程序的 ImportTable667.4.8 自毁壳程序代码697.4.9 编译方法707.5 Merge 模块 .717.6 Register 模块 767.7 Server 模块 .777.8 软件授权协议的实现787.9 Client 的代码(数据)的加密/解密流程图示 828 使用说明及演示8.1 使用说明838.2 演示及效果839 限制、不足与展望9.1 使用该软件的限制869.2 该软件的不足869.3 对该软件的展

13、望8710 结束语10.1 总结9110.2 致谢91参考文献92- 1 -1 概述我引用应用密码学作者 Bruce Schneier 的话:世界上有两种密码:一种是防止你的小妹妹看你的文件;另一种是防止当局者阅读你的文件资料。如果把一封信锁在保险柜中,把保 险柜藏在纽约的某个地方 ,然后告诉你去看这封信。这并不是安全,而是隐藏。相反,如果把一封信锁在保险柜中,然后把保险 柜及其设计规范和许多同样 的保险柜给你,以便你和世界上最好的开保险柜的专家能够研究锁的装置。而你还是无法打开保险柜去读这封信, 这样才是安全的。意思是说,一个密码系统的安全性只在于密钥的保密性,而不在算法的保密性。对纯数据的

14、加密的确是这样。对于你不愿意让他看到这些数据(数据的明文)的人,用可靠的加密算法,只要破解者不知道被加密数据的密码,他就不可解读这些数据。但是,软件的加密不同于数据的加密,它只能是“隐藏” 。不管你愿意不愿意让他(合法用户,或 Cracker)看见这些数据(软件的明文) ,软件最终总要在机器上运行,对机器,它就必须是明文。既然机器可以“看见”这些明文,那么 Cracker,通过一些技术,也可以看到这些明文。于是,从理论上,任何软件加密技术都可以破解。只是破解的难度不同而已。有的要让最高明的 Cracker 忙上几个月,有的可能不费吹灰之力,就被破解了。所以,反盗版的任务(技术上的反盗版,而非行

15、政上的反盗版)就是增加 Cracker 的破解难度。让他们花费在破解软件上的成本,比他破解这个软件的获利- 2 -还要高。这样 Cracker 的破解变得毫无意义谁会花比正版软件更多的钱去买盗版软件 ?然而,要做到“难破解” ,何尝容易? Sony 曾宣称的超强反盗版(Key 2 Audio 音乐 CD 反盗版) ,使用了很尖端的技术,然而最近却被一枝记号笔破解了,成为人们的饭后笑料!所以,很多看上去很好的技术,可能在 Cracker 面前的确不堪一击。就像马其诺防线一样,Cracker 不从你的防线入手,而是“绕道” 。这样,让你的反盗版技术在你做梦也想不到的地方被 Crack 了。为什么会

16、这样呢 ?归根到底是因为软件在机器上运行,并且软件和机器是分离的这一点是关键,如果软件和硬件完全绑定,不能分离,是可以做到象 IDEA 之类几乎不可破解的系统的。这将在后面谈传统软件保护技术时详细说明。对我的这个解决方案,我不能保证 Crack 高手在几天之内不能破解它,我只能说:“在这个软件中,我尽量堵住了当前破解者普遍使用的方法以及“我想得到”的可能的缺口。 ”但是我相信,倾注了我三个月心血的反盗版软件,决不是一个“玩具式”的反盗版软件。- 3 -2 密码学简介2.1 概念(1) 发送者和接收者假设发送者想发送消息给接收者,且想安全地发送信息:她想确信偷听者不能阅读发送的消息。(2) 消息

17、和加密消息被称为明文。用某种方法伪装消息以隐藏它的内容的过程称为加密,加了密的消息称为密文,而把密文转变为明文的过程称为解密。图 2-1 表明了这个过程。加 密 解 密明 文 密 文 原 始 明 文图 2-1 加密和解密明文用 M(消息)或 P(明文)表示,它可能是比特流(文本文件、位图、数字化的语音流或数字化的视频图像) 。至于涉及到计算机,P 是简单的二进制数据。明文可被传送或存储,无论在哪种情况,M 指待加密的消息。密文用 C 表示,它也是二进制数据,有时和 M 一样大,有时稍大(通过压缩和加密的结合,C 有可能比 P 小些。然而,单单加密通常达不到这一点) 。加密函数E 作用于 M 得

18、到密文 C,用数学表示为:E(M)=C.相反地,解密函数 D 作用于 C 产生 MD(C)=M.先加密后再解密消息,原始的明文将恢复出来,下面的等式必须成立:- 4 -D(E(M) )=M(3) 鉴别、完整性和抗抵赖除了提供机密性外,密码学通常有其它的作用:.(a) 鉴别消息的接收者应该能够确认消息的来源;入侵者不可能伪装成他人。(b) 完整性检验消息的接收者应该能够验证在传送过程中消息没有被修改;入侵者不可能用假消息代替合法消息。(c) 抗抵赖发送者事后不可能虚假地否认他发送的消息。(4) 算法和密钥密码算法也叫密码,是用于加密和解密的数学函数。 (通常情况下,有两个相关的函数:一个用作加密

19、,另一个用作解密)如果算法的保密性是基于保持算法的秘密,这种算法称为受限制的算法。受限制的算法具有历史意义,但按现在的标准,它们的保密性已远远不够。大的或经常变换的用户组织不能使用它们,因为每有一个用户离开这个组织,其它的用户就必须改换另外不同的算法。如果有人无意暴露了这个秘密,所有人都必须改变他们的算法。更糟的是,受限制的密码算法不可能进行质量控制或标准化。每个用户组织必须有他们自己的唯一算法。这样的组织不可能采用流行的硬件或软件产品。但窃听者却可以买到这些流行产品并学习算法,于是用户不得不自己编写算法并予以实现,如果这个组织中没有好的密码学家,那么他们就无法知道他们是否拥有安全的算法。-

20、5 -尽管有这些主要缺陷,受限制的算法对低密级的应用来说还是很流行的,用户或者没有认识到或者不在乎他们系统中内在的问题。现代密码学用密钥解决了这个问题,密钥用 K 表示。K 可以是很多数值里的任意值。密钥 K 的可能值的范围叫做密钥空间。加密和解密运算都使用这个密钥(即运算都依赖于密钥,并用 K 作为下标表示) ,这样,加/解密函数现在变成:EK(M)=CDK(C)=M.这些函数具有下面的特性(见图 2-2):DK(E K(M) )=M.加 密 解 密明 文 密 文 原 始明 文密 钥 密 钥图 2-2 使用一个密钥的加/解密加 密 解 密明 文 密 文 原 始明 文加 密密 钥 解 密密 钥

21、图 2-3 使用两个密钥的加/解密有些算法使用不同的加密密钥和解密密钥(见图 2-3) ,也就是说加密密钥 K1与相应的解密密钥 K2 不同,在这种情况下:EK1(M)=CDK2(C)=MDK2 (EK1(M)=M- 6 -所有这些算法的安全性都基于密钥的安全性;而不是基于算法的细节的安全性。这就意味着算法可以公开,也可以被分析,可以大量生产使用算法的产品,即使偷听者知道你的算法也没有关系;如果他不知道你使用的具体密钥,他就不可能阅读你的消息。密码系统由算法、以及所有可能的明文、密文和密钥组成的。基于密钥的算法通常有两类:对称算法和公开密钥算法。下面将分别介绍:2.2 对称密码算法对称算法有时

22、又叫传统密码算法,就是加密密钥能够从解密密钥中推算出来,反过来也成立。在大多数对称算法中,加/解密密钥是相同的。这些算法也叫秘密密钥算法或单密钥算法,它要求发送者和接收者在安全通信之前,商定一个密钥。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都能对消息进行加/解密。只要通信需要保密,密钥就必须保密。对称算法的加密和解密表示为:EK(M)=CDK(C)=M对称算法可分为两类。一次只对明文中的单个比特(有时对字节)运算的算法称为序列算法或序列密码。另一类算法是对明文的一组比特亚行运算,这些比特组称为分组,相应的算法称为分组算法或分组密码。现代计算机密码算法的典型分组长度为 64 比特这个长

23、度大到足以防止分析破译,但又小到足以方便使用(在计算机出现前,算法普遍地每次只对明文的一个字符运算,可认为是序列密码对字符序列的运算) 。2.3 公开密码算法公开密钥算法(也叫非对称算法)是这样设计的:用作加密的密钥不同于用作解密的密钥,而且解密密钥不能根据加密密钥计算出来(至少在合理假定的长时间- 7 -内) 。之所以叫做公开密钥算法,是因为加密密钥能够公开,即陌生者能用加密密钥加密信息,但只有用相应的解密密钥才能解密信息。在这些系统中,加密密钥叫做公开密钥(简称公钥) ,解密密钥叫做私人密钥(简称私钥) 。私人密钥有时也叫秘密密钥。为了避免与对称算法混淆,此处不用秘密密钥这个名字。用公开密

24、钥 K 加密表示为EK(M)=C.虽然公开密钥和私人密钥是不同的,但用相应的私人密钥解密可表示为:DK(C)=M有时消息用私人密钥加密而用公开密钥解密,这用于数字签名(后面将详细介绍) ,尽管可能产生混淆,但这些运算可分别表示为:EK(M)=CDK(C)=M当前的公开密码算法的速度,比起对称密码算法,要慢的多,这使得公开密码算法在大数据量的加密中应用有限。2.4 单向散列函数单向散列函数 H(M) 作用于一个任意长度的消息 M,它返回一个固定长度的散列值 h,其中 h 的长度为 m 。输入为任意长度且输出为固定长度的函数有很多种,但单向散列函数还有使其单向的其它特性:(1) 给定 M ,很容易

25、计算 h ;(2) 给定 h ,根据 H(M) = h 计算 M 很难 ;(3) 给定 M ,要找到另一个消息 M 并满足 H(M) = H(M) 很难。在许多应用中,仅有单向性是不够的,还需要称之为“抗碰撞”的条件:要找出两个随机的消息 M 和 M ,使 H(M) = H(M) 满足很难。- 8 -由于散列函数的这些特性,由于公开密码算法的计算速度往往很慢,所以,在一些密码协议中,它可以作为一个消息 M 的摘要,代替原始消息 M,让发送者为 H(M) 签名而不是对 M 签名 。如 SHA 散列算法用于数字签名协议 DSA 中。2.5 数字签名提到数字签名就离不开公开密码系统和散列技术。有几种

26、公钥算法能用作数字签名。在一些算法中,例如 RSA,公钥或者私钥都可用作加密。用你的私钥加密文件,你就拥有安全的数字签名。在其它情况下,如DSA,算法便区分开来了数字签名算法不能用于加密。这种思想首先由 Diffie和 Hellman 提出 。基本协议是简单的 :(1) A 用她的私钥对文件加密,从而对文件签名。(2) A 将签名的文件传给 B。(3) B 用 A 的公钥解密文件,从而验证签名。这个协议中,只需要证明 A 的公钥的确是她的。如果 B 不能完成第(3)步,那么他知道签名是无效的。这个协议也满足以下特征:(1) 签名是可信的。当 B 用 A 的公钥验证信息时,他知道是由 A 签名的

27、。(2) 签名是不可伪造的。只有 A 知道她的私钥。(3) 签名是不可重用的。签名是文件的函数,并且不可能转换成另外的文件。(4) 被签名的文件是不可改变的。如果文件有任何改变,文件就不可能用 A 的公钥验证。(5) 签名是不可抵赖的。B 不用 A 的帮助就能验证 A 的签名。- 9 -在实际应用中,因为公共密码算法的速度太慢,签名者往往是对消息的散列签名而不是对消息本身签名。这样做并不会降低签名的可信性。本章仅对密码学进行了一些简要的介绍,更多的请参阅参考文献1。- 10 -3 Windows 环境下 PE 文件简介3.1 WIN32 与 PE 基本概念只要用过电脑的人都知道什么是 Wind

28、ows,Windows95 已经是过时的昨日黄花了,Windows98 也已推出将近四年了。2000 年又推出了 Windows2000,今年又推出了 WindowsXP,微软的操作系统更新速度是如此的快,以至于昨天还在使用的东西,在今天看来就已经过时了。Windows98 以后,微软传言不在推出 9x 内核的操作系统,但是 2000 年下半年却正式推出了 WindowsMillennium,简称 Win.Me 。然而从 WindowsXP 的推出,可以断言,微软不会在升级 Win9x 操作系统了。Windows2000 和 WindowsXP 都是基于 NT 内核的。所有这些操作系统都使用一

29、种“可移植可执行文件格式”(Portable Executable File Format),简称 PE 文件格式。下面简短介绍一下 PE 文件的一些概念。详细内容请参阅参考文献14。Windows NT 继承了 VAX VMS 和 UNIX 的传统。许多 Windows NT 的创始人在进入微软前都在这些平台上进行设计和编码。当他们开始设计 Windows NT 时,很自然的,为了最小化工程的启动时间,他们会使用以前写好的并且已经测试过的工具。用这些工具生成并且工作的可执行文件和 OBJ 文件格式叫做 COFF(Common Object File Format 的首字母缩写)。COFF 的

30、年龄不超过八年。COFF 本身是一个很好的起点,但是需要扩展到一个现代操作系统如 Windows 95 和 Windows NT 就要进行一些更新。其结果就是产生了(PE 格式)可移植可执行文件格式。它被称为“可移植的”是因为在所有平台(如 x86,Alpha,MIPS 等等)上实现的 WindowsNT 都使用相同的可执行文件格式。当然了,也有许多不同的东西如二进制代码的 CPU 指令。重要的是操作系统的装入器和程序设计工具不需要为任何一种 CPU 完全重写就能达到目的。- 11 -关于 PE 文件最重要的是,磁盘上的可执行文件和它被 WINDOWS 载入内存之后(PE 文件载入内存之后称为

31、 PE 映像)是非常相像的(如图 3-1)。WINDOWS 载入器不必为从磁盘上载入一个文件而辛辛苦苦创建一个进程。载入器使用内存映射文件机制把文件中相似的块映射到虚拟空间中。构造式的进行分析,一个 PE 文件类似一个预制的屋子。它本质上开始于这样一个空间,这个空间后面有几个把它连到其余空间的机件(就是说,把它联系到它的 DLL 上,等等)。这对 PE 格式的 DLL式一样容易应用的。一旦这个模块被载入,Windows 就可以有效的把它和其它内存映射文件同等对待。图 3-1 PE 文件和 PE 映像的布局很相似- 12 -对 Win32 来讲,模块所使用的所有代码,数据,资源,导入表,和其它需

32、要的模块数据结构都在一个连续的内存块中。在这种形势下,你只需要知道载入器把可执行文件映射到了什么地方就可以了。通过作为映像的一部分的指针,你可以很容易的找到这个模块所有不同的块。另一个你需要知道的概念是相对虚拟地址(RVA)。PE 文件中的许多域都用术语 RVA 来指定。一个 RVA 只是一些项目相对于文件映射到内存的偏移。比如说,载入器把一个文件映射到虚拟地址 0x10000 开始的内存块。如果映像中一个实际的表的首址是 0x10464,那么它的 RVA 就是 0x464。(虚拟地址 0x10464)(基地址 0x10000)RVA 0x00464为了把一个 RVA 转化成一个有用的指针,只

33、需要把 RVA 值加到模块的基地址上即可。基地址是 EXE 和 DLL 内存映射文件的基址,这个基址在 Win32 中这是一个很重要的概念。为了方便起见,WindowsNT 和 Windows9x 用模块的基址作为这个模块的实例句柄(HINSTANCE)。可以对任何 DLL 调用 GetModuleHandle(dllname)得到一个指针去访问它的组件。如果 dllname 为 NULL,则得到执行体自己的模块句柄。这是非常有用的,如通常编译器产生的启动代码将取得这个句柄并将它作为一个参数 hInstance 传给 WinMain 。3.2 PE 首部和其它可执行文件格式一样,PE 文件在众

34、所周知的地方有一些定义文件其余部分面貌的域。首部就包含这样象代码和数据的位置和尺寸的地方,操作系统要对它进行干预,比如初始堆栈大小,和其它重要的块的信息。和微软其它执行体的格式相比,PE 格式的执行体的主要的首部不是在文件的最开始。典型的 PE 文件最开始的数百个字节被 DOS 残留部分占用。这个残留部分是一个打印如“这个程序不能在DOS 下运行!”这类信息的小程序。所以,你在一个不支持 Win32 的系统中运行这个程序,会得到这类错误信息。当载入器把一个 Win32 程序映射到内存,这个映射- 13 -文件的第一个字节对应于 DOS 残留部分的第一个字节,这是无疑的。于是,和你启动的任一个基

35、于 Win32 的程序一起,都有一个基于 DOS 的程序连带被载入。和微软的其它可执行格式一样,你可以通过查找它的起始偏移来得到真实首部,这个偏移放在 DOS 残留首部中。WINNT.H 头文件包含了 DOS 残留程序的数据结构定义(注),使得很容易找到 PE 首部的起始位置。e_lfanew 域是 PE 真实首部的偏移。为了得到 PE 首部在内存中的指针,只需要把这个值加到映像的基址上即可。/ 忽略类型转化和指针转化pNTHeader = dosHeader + dosHeader-e_lfanew;注:为了不失简洁,这里未列出 这些结构体的完整定义就直接引用,这里直接引用的结构体其定义都在

36、 winnt.h 中,建 议读者在 读本章时参考 Winnt.h 。一旦你有了 PE 主首部的指针,游戏就可以开始了!PE 主首部是一个IMAGE_NT_HEADERS 的结构,在 WINNT.H 中定义。这个结构由一个双字(DWORD)和两个子结构组成,布局如下:DWORD Signature; / 标志域IMAGE_FILE_HEADER FileHeader;IMAGE_OPTIONAL_HEADER OptionalHeader;标志域用 ASCII 表示就是“PE00”。标志域之后的是结构 IMAGE_FILE_HEADER 。这个域只包含这个文件最基本的信息。这个结构看上去并未从它

37、的原始 COFF 实现更改过。除了是 PE 首部的一部分,它还表现在微软 Win32 编译器生成的 COFF OBJ 文件的最开始部分。这个部分的详细说明请参阅参考文献14(本人已翻译为中文) 。- 14 -3.3 PE 文件的导入表因为导入表在该软件的设计中很关键,后面壳程序导入表的构建,对客户程序导入表的载入等,都牵涉到导入表 。所以,在这里有必要说明一下,更详细的说明请参阅参考文献141516。导入表,简单的说,导入表的作用相当于 DOS 的系统中断功能。两者都是操作系统 API 。只不过 DOS 中断不需要操作系统在载入每个执行体时填入 API 的实际地址,并且,导入表还可以导入除操作

38、系统 API 之外的其它模块中的函数。在一个 PE 文件中,当你调用另一模块中的一个函数时(比如在 USER32.DLL 中的 GetMessage ),编译器产生的 CALL 指令并不把控制直接转移到在 DLL 中的这个函数。代替的,CALL 指令把把控制转移到一个也在 .text 中的JMP DWORD PTR XXXXXXXX指令处(如图 3-2)。 这个 JMP 指令通过一个在导入表中的 DWORD 变量间接的转移控制。 导入表的这个 DWORD 包含操作系统函数入口的实际地址。为什么 DLL 调用用这种方式来实现呢?原来,通过一个位置传送所有的对一个给定的 DLL 函数的调用,载入器

39、不需要改变每个调用 DLL 的指令。所有的 PE 载入器必须做的是把目标函数的正确地址放到导入表的一个 DWORD 中。不需要改变任何 call 指令本身。如果你想通过函数指针调用一个函数,事情也会如你所预料的一样。但是,如果你想读取 GetMessage 开始的字节,你将不能如愿。后面讲到反 API 断点时会详细说明。- 15 -图 3-2 一个导入函数调用的图示前面描述了函数调用怎样到一个外部 DLL 中而不直接调用这个 DLL 。代替的,在执行体中的 .text 块中(如果你用 Borland C+ 就是 .icode 块),CALL 指令到达一个 JMP DWORD PTR XXXXX

40、XXX 指令处。JMP 指令寻找的地址把控制转移到实际的目标地址。PE 文件的导入表会包含一些必要的信息,这些信息是载入器用来确定目标函数的地址以及在执行体映像中修正他们的。导入表开始于一个 IMAGE_IMPORT_DESCRIPTOR 数组。每个 DLL 都有一个 PE 文件隐含链接上的 IMAGE_IMPORT_DESCRIPTOR 。没有指定这个数组中结构的数目的域。代替的,这个数组的最后一个元素是一个全 NULL 的IMAGE_IMPORT_DESCRIPTOR 。IMAGE_IMPORT_DESCRIPTOR 的格式显示在表 3-1 。- 16 -表 3-1 IMAGE_IMPOR

41、T_DESCRIPTOR 的格式DWORD Characteristics 在一个时刻,这可能已是一个标志集。然而,微软改变了它的涵义并不再糊涂地升级 WINNT.H 。这个域实际上是一个指向指针数组的偏移(RVA)。其中每个指针都指向一个 IMAGE_IMPORT_BY_NAME 结构。现在这个域的涵义是 OriginalFirstThunk 。DWORD TimeDateStamp 表示这个文件的创建时间。 DWORD ForwarderChain 这个域联系到前向链。前向链包括一个 DLL 函数向另一个 DLL 转送引用。比如,在 WindowsNT 中,NTDLL.DLL 就出现了的一

42、些前向的它向 KERNEL32.DLL 导出的函数。应用程序可能以为它调用的是 NTDLL.DLL 中的函数,但它最终调用的是KERNEL32.DLL 中的函数。这个域还包含一个 FirstThunk 数组的索引(即刻描述)。用这个域索引的函数会前向引用到另一个 DLL 。不幸的是,函数怎样前向引用的格式没有文档,并且前向函数的例子也很难找。DWORD Name 这是导入 DLL 的名字,指向以 NULL 结尾的 ASCII 字符串。通用例子是KERNEL32.DLL 和 USER32.DLL 。- 17 -PIMAGE_THUNK_DATA FirstThunk 这个域是指向 IMAGE_T

43、HUNK_DATA 联合的偏移(RVA)。几乎在任何情况下,这个域都解释为一个指向的 IMAGE_IMPORT_BY_NAME 结构的指针。如果这个域不是这些指针中的一个,那它就被当作一个将从这个被导入的 DLL 的导出序数值。如果你实际上可以从序数导入一个函数而不是从名字导入,从文档看,这是很含糊的。IMAGE_IMPORT_DESCRIPTOR 的一个重要部分是导入的 DLL 的名字和两个IMAGE_IMPORT_BY_NAME 指针数组。在 EXE 文件中,这两个数组(由Characteristics 域和 FirstThunk 域指向)是相互平行的,都是以 NULL 指针作为数组的最后

44、一个元素。两个数组中的指针都指向 IMAGE_IMPORT_BY_NAME 结构。图 3-3 显示了这种布局。图 3-3 导入表中一个项的结构图示- 18 -PE 文件导入表中的每一个函数有一个 IMAGE_IMPORT_BY_NAME 结构。IMAGE_IMPORT_BY_NAME 结构非常简单,看上去是这样:WORD Hint;BYTE Name?;第一个域是导入函数的导出序数的最佳猜测值。和 NE 文件不同,这个值不是必须正确的。于是,载入器指示把它当作一个进行二分查找的建议开始值。下一个是导入函数的名字的 ASCIIZ 字符串。为什么有两个平行的指针数组指向结构 IMAGE_IMPOR

45、T_BY_NAME ?第一个数组(由 Characteristics 域指向的)单独的留下来,并不被修改。经常被称作提名表。第二个数组(由 FirstThunk 域指向的)将被 PE 载入器覆盖。载入器在这个数组中迭代每个指针,并查找每个 IMAGE_IMPORT_BY_NAME 结构指向的函数的地址。载入器然后用找到的函数地址覆盖这个指向 IMAGE_IMPORT_BY_NAME 结构的指针。JMP DWORD PTR XXXXXXXX 中的 XXXXXXXX 指向 FirstThunk 数组的一个条目。因为由载入器覆盖的这个指针数组实际上保持所有导入函数的地址,叫做“导入地址表”。在优化上

46、无止境的探索中,微软在 WindowsNT 中“优化”了系统DLL(KERNEL32.DLL 等等)的 thunk 数组。在这个优化中,这个数组中的指针不再指向 IMAGE_IMPORT_BY_NAME 结构,它们已经包含了导入函数的地址。换句话说,载入器不需要去查找函数的地址并用导入函数的地址覆盖 thunk 数组(译注)。译注:这就是 Bound Import,关于 Bound Import,参考文献1516,有 详细介绍。不过,在我的软件中,忽略了对 Bound Import 的处理,这样会造成一些程序载入速度的减小。但使 问题简化了许多。- 19 -因为导入地址表在一个可写的块中,拦截

47、一个 EXE 或 DLL 对另一个 DLL 的调用就相对容易。只需要修改适当地导入地址条目去指向希望拦截的函数。不需要修改调用者或被调者的任何代码。注意微软产生的 PE 文件的导入表并不是完全被连接器同步的,这一点很有趣。所有对另一个 DLL 中的函数的调用的指令都在一个导入库中。当你连接一个 DLL 时,库管理器(LIB32.EXE 或 LIB.EXE)扫描将要被连接的 OBJ 文件并且创建一个导入库。这个导入库完全不同于 16 位 NE 文件连接器使用的导入库。32 位库管理器产生的导入库有一个.text 块和几个.idata$块。导入库中的.text 块包含 JMP XXXX 指令,这条

48、指令的标号在 OBJ 文件的符号表中用一个符号名来存储。这个符号名对将要从 DLL 中导出的所有函数名讲都是唯一的(例如:_Dispatch_Message4)。导入库中的一个.idata$块包含一个从导入库中引用的地址,即导入库的 .text 中的指令:JMP XXXX中的 XXXX 。另一个.idata$块有一个提示序号(hint ordinal)的空间。这两个域就组成了 IMAGE_IMPORT_BY_NAME 结构。当你晚期连接一个使用导入库的 PE 文件时,导入库的块被加到连接器需要处理的块的列表中,这个列表在你的 OBJ 文件中。一旦导入库中的这个 xxxx 的名字和和要导入的函数

49、名相同,连接器就假定这条 jmp xxxx指令就是这个导入函数,并修正其中的 xxxx ,使其指向这个.idata$中的一个存储导入函数地址的空间。导入库中的这条 jmp xxxx指令在本质上就被当作这个导入函数本身了。除了提供一个导入函数的指令 jmp xxxx,导入库还提供 PE 文件的.idata 块(或称导入表)的片断。这些片断来自于库管理器放入导入库中的不同的.idata$块。简而言之,连接器实际上不知道出现在不同的 OBJ 文件中的导入函数和普通函- 20 -数之间的不同。连接器只是按照它的内部规则去建立并结合块,于是,所有的事情就自然顺理成章了。本文有关导入表的内容,基本上就这么多,要得到更多的信息,请参阅参考文献141516。- 21 -4 当前流行的一些软件保护技术4.1 序列号保护数学算法一项都是密码加密的核心,但在一般的软件加密中,它似乎并不太为人们关心,因为大多数时候软件加密本身实现的都是一种编程的技巧。但近几年来随着序列号加密程序的普及,数学算法在软件加密中的比重似乎是越来越大了。 看看在网络上大行其道的序列号加

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 大学课件

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报