发明内容
本发明的目的在于解决现有技术中动态链接库加密后容易被跟踪调试,被应用程序私自加载,被静态反汇编等技术问题,提供一种通用的Windows动态连接库加密保护方法以及私有的加载方法,能够有效防止动态链接库被跟踪调试、私自加载、静态反汇编等操作。
本发明针对现有技术问题主要是通过下述技术方案得以解决的,一种Windows动态链接库的分段双重加密及安全加载方法,包括如下步骤:
(1)解析Windows动态链接库文件结构;
(2)建立私有动态链接库导出表;
(3)清除动态链接库导出表;
(4)分别对动态链接库文件的代码段、数据段使用指定加密算法的一个加密密钥进行加密;
(5)对整个动态链接库文件使用指定加密算法的另外一个加密密钥进行加密;
(6)安全加载步骤包括:
a)对加密的动态链接库文件使用指定加密算法的一个密钥进行解密,建立动态链接库文件内存映像I,对内存映像I中的代码段、数据段使用指定加密算法的另一个密钥进行解密;
b)建立内存映像II,分配一个虚拟内存块,拷贝内存映像I中的内容,重定位虚拟内存块中的地址数据;
c)从隐藏在内存映像II代码段中的私有动态链接库导出表输出接口函数给应用程序;
d)删除内存映像I,清除内存映像II的段头(sectionheader)。
本发明通过解析Windows动态链接库文件的结构(即PE结构),建立私有的动态链接库导出表并加以隐藏,将原有的动态链接库导出表(ExportTable)清除,分别对动态链接库文件的代码段、数据段用一个密钥加密,最后,再对整体动态链接库文件用另外一个密钥进行加密。
本发明采用私有的加载方法:从私有动态链接库导出表中输出接口函数,自己为段分配内存以建立内存映像,而且建立的内存映像是没有段头(sectionheader)信息的。由于动态链接库的内存映像的内存是自己的加载器自己分配的,其内存结构具有保密性,所以一些工具软件也难于动态跟踪分析这种动态链接库。
经过本发明处理的动态链接库文件,不能被应用程序直接加载,即使密钥泄露后被应用链接加载,由于动态链接库的导出表已被清除,以致不能获得其中的导出函数,该库最终还是个不能使用的库。
作为优选,私有动态链接库导出表存放的是导出函数的相对虚拟地址。该导出表不包含导出函数名,没有函数名的导出表只有特殊程序才能识别,更为安全。
作为优选,本方案中编制一制造私有动态链接库导出表存储区间的代码文件,所述制造私有动态链接库导出表存储区间的代码文件与动态链接库的源文件一起编译后,在代码段中会包含两个字节串,称之为分隔码,在两个分隔码之间存放私有动态链接库导出表。将私有动态链接库导出表隐藏在动态链接库代码段中。两个分隔符之间根据需要设置足够的存放区域。
作为优选,私有动态链接库导出表的建立步骤包括:
(A)分析动态链接库的头部,获得导出表的起始地址;
(B)分析动态链接库的导出表,获得所有的导出函数的函数名和它的相对虚拟地址;
(C)将动态链接库导出表中的函数的相对虚拟地址放入私有动态链接库导出表中。
私有动态链接库导出表放置在具有分隔符的动态链接库代码段中。
作为优选,清除动态链接库导出表的步骤包括:
(A)分析动态链接库的头部,获得导出表的起始地址;
(B)分析动态链接库导出表,获得所有函数名称或变量名称和对应的地址,将这些函数名称或变量名称和地址都清除。
作为优选,加密数据段的步骤包括:
(A)分析动态链接库的头部,获得重定位表;
(B)分析动态链接库的头部,获得只读数据段的起始地址,从该地址起采用指定加密算法的一个密钥加密整个只读数据段,该只读数据段中需要重定位的所有地址数据保留原来的值,这些地址数据的位置从重定位表中获得;
(C)分析动态链接库的头部,获得可写数据段的起始地址,从该地址起采用指定加密算法用与只读数据段相同的密钥加密整个可写数据段,该可写数据段中需要重定位的地址数据保留原来的值,这些地址数据的位置从重定位表中获得。
(D)作为优选,指定加密算法是采用AES算法的密码区块链
模式作为加密算法。AES(AdvancedEncryptionStandard)算法的密码区块链(CipherBlockChaining-CBC)模式作为加密算法安全性好,能有效防止非法篡改。
作为优选,解密及加载动态链接库的步骤包括:
(A)从服务器端获得密钥K1和K2,所述密钥K1和K2是加密时的密钥;
(B)查找加密库文件,根据文件名,在用户机器上找到加密的动态链接库文件;
(C)用密钥K2解密动态链接库文件;
(D)为动态链接库文件建立内存映像I;采用WindowsAPI函数LoadLibrary为动态链接库文件建立内存映像I;
(E)对内存映像I中的代码段、数据段使用K1解密可写数据段;解密只读数据段;
(F)建立内存映像II,分配一个虚拟内存块,拷贝内存映像I中的内容,重定位虚拟内存块中的地址数据;
(G)从内存映像代码段中的私有导出表输出接口函数给应用程序;
(H)清除内存映像II的头部数据,为库内存映像II设置属性;
(I)释放内存映像I。
本发明带来的有益效果是:无硬件要求,对动态链接库文件及其代码段数据段使用不同密钥双重加密,具有足够的安全性;无标准的动态链接库导出表,只有私有动态链接库导出表,使用私有的加载方法为库建立内存映像,能够有效地防止发布出去的动态链接库被非法使用、非法分析、非法调试跟踪,操作简单使用方便。
实施例:如图1至图5所示,本发明是一种Windows动态链接库的分段双重加密及安全加载方法,以在UT斯达康IPTV系统中的实际应用来说明本发明的具体实施方式。在UT斯达康IPTV系统中,Windows终端上的应用程序为PC-Client,它使用一个动态链接接库DRMClient从IPTV系统端获得加密频道或加密VOD节目的密钥,并使用该库提供的函数对媒体流进行解密。由于PC-Client连同DRMClient是安装在用户个人PC机上的Windows软件,而DRMClient之中包含了一些关键技术,是一个需要安全保密的库,所以UTIPTV系统采用本发明的方法对其在发布前进行加密,并在使用时也采用本发明的方法对其进行解密加载。终端程序PC-Client集成了具有本发明解密加载功能的模块DRM_Lorder,DRM_Lorder先与IPTV服务器建立一个安全的加密信道,然后从服务器端获得两个加密密钥K1和K2,之后再加载解密动态链接库DRM-Client,并输出接口函数集给PC-Client。
需要加密的动态链接库,首先在编译上做特殊限制:
1、要编译成Release版本,而不是Debug版本,因为Debug版本包含太多信息,容易泄露程序内部的实现;
2、图3所示的制造私有动态链接库导出表存储区间的代码文件和动态链接库的源文件一起编译,这样做是为了在编译后的动态链接库中制造一个能存放私有动态链接库导出表的区域。图3说明,编译后,在代码段中会包含两个字节串<0x40484048CAFECBF4>,称之为分隔码。在两个分隔码之间有128字节的区域,用来存放私有动态链接库导出表,该表存放的是导出函数(即DRMClient接口函数)的相对虚拟地址,而标准的动态链接库导出表还包含了导出函数的名字,但本发明的私有动态链接库导出表不包含导出函数名。没有函数名的导出表只有特殊程序才能识别,更为安全。128字节大小的导出表能存32个4字节的函数地址,足够DRMClient库用。
参阅图1,加密的动态链接库的结构为:文件头、段头(即DOSHead,NThead,Sectionhead)最初不加密,分别对其中的代码段(.text)、数据段(.data)、只读数据段(.rdata)用加密密钥K1加密,并对其中的导出表(.edata)清除,建立隐藏在代码段中的私有的动态链接库导出表(.privateexporttable),最后,再对整个库文件用加密密钥K2加密。
参阅图4,DRMClient库中私有的导出表存放函数相对虚拟地址的数据结构drm_client_t,该结构存放在代码段中一无用的块中(即两个分隔码之间的区域),在代码段解密后,该结构直接返回给PC-Client使用,该结构C语言的定义如下:
typedefstruct
{
int(*set_start_para)(charls_ip[20],uint16_tport,uint16_tinterval);
int(*set_basic_info)(uint32_tuser,uint8_t*pwd,int8_tstbtype,char*stb);
int(*set_channel_list)(drm_channel_info*channels,uint32_tchannel_num);
int(*start_up)(void);
int(*client_version)(charversion[20]);
int(*request_vod)(charmedia_id[128],uint32_tcontent_id,intenable_flag);
int(*decrypt_stream)(char*ts_buffer,uint32_tsize);
int(*request_livetv)(charchannel_id[128],charmeida_id[128]);
}drm_client_t;
参阅图2,本发明加密动态链接库的流程为:
步骤1,输入动态链接库文件;
步骤2,建立私有导出表;
步骤3,清除导出表;
步骤4,用K1加密代码段(.text);
步骤5,用K1加密数据段:包括只读数据段(.rdata)和可写数据段(.data);
步骤6,计算文件校验和并写入;
步骤7,用K2加密整个文件;
步骤8,得到加密的动态链接库文件。
上述步骤2,建立私有导出表的流程如下:
步骤21,分析动态链接库的头部,获得导出表的起始地址;
步骤22,分析动态链接库的导出表,获得所有的导出函数的函数名和它的相对虚拟地址;
步骤23,将导出表中的函数的相对虚拟地址一一放入一个drm_client_t数据结构中对应的成员,比如DRMClient库的导出函数decrypt_stream放入drm_client_t结构中的成员decrypt_stream;
步骤24,参阅图4,分析动态链接库的头部,获得代码段的起始地址,从该起始地址开始,搜索分隔码<0x40484048CAFECBF4>。找到后,将上一步骤得到的drm_client_t结构数据拷贝到分隔码后的位置。
上述步骤3,清除导出表的流程如下:
步骤31,分析动态链接库的头部,获得导出表的起始地址;
步骤32,分析动态链接库导出表,获得一个个的命名符号(函数名称或变量名称)和对应的地址,将这些符号名和地址都清除。这样处理之后,这个动态链接库就没有了标准的导出表了。
上述步骤4,用K1加密代码段的流程如下:
步骤41,分析动态链接库的头部,获得重定位表;
步骤42,分析动态链接库的头部,获得代码段的地址,从该地址开始用采用AES算法用密钥K1加密代码段,加密过程中对重定位表中标示的所有需要重定位的地址数据都保留原来的值而不加密。
上述步骤5,用K1加密数据段的具体步骤如下:
步骤51,分析动态链接库的头部,获得重定位表;
步骤52,分析动态链接库的头部,获得只读数据段(.rdata)的起始地址,从该地址起采用AES加密算法用密钥K1加密整个段,但该段中需要重定位的所有地址数据保留原来的值,这些地址数据的位置从重定位表中获得;
步骤53,同样,分析动态链接库的头部,获得可写数据段(.data)的起始地址,从该地址起采用AES加密算法用密钥K1加密整个段,但该段中需要重定位的地址数据保留原来的值,这些地址数据的位置从重定位表中获得。
上述步骤6,计算文件校验和的具体步骤如下:
步骤61,分析动态链接库的头部,获得可选头(OptionalHeader),将其中的域CheckSum设置为零;
步骤62,从库文件头部的第一个字节起到尾部最后一个字节,计算校验和;
步骤63,将计算所得的校验和的值填入可选头(OptionalHeader)的域CheckSum。
上述步骤7,采用AES加密算法用密钥K2加密整个文件。
解密及加载动态链接库的步骤参阅图5,步骤如下:
步骤1,登陆IPTV服务器,终端与服务器建立安全的加密信道;
步骤2,从服务器端获得密钥K1和K2;
步骤3,查找加密库文件,根据文件名,在用户机器上找到加密的动态链接库文件;
步骤4,拷贝加密库文件到一个临时文件;
步骤5,用密钥K2解密临时库文件;
步骤6,验证校验和,如果验证不过,中断解密加载过程;
步骤7,为临时库文件建立内存映像I;
步骤8,解密代码段;
步骤9,解密可写数据段;
步骤10,解密只读数据段;
步骤11,导入外部动态库;
步骤12,建立内存映像II;
步骤13,内存映像II重定位;
步骤14:导入外部动态库和函数;
步骤15,输出接口函数;
步骤16,清除新内存映像的头部数据;
步骤17:为库内存映像II设属性;
步骤18,释放内存映像I;
步骤19,删除临时库文件。
上述步骤6,验证校验和的具体步骤为:
步骤6.1,分析临时库文件的头部,获得可选头(OptionalHeader),将其中的域CheckSum保留后设置为零;
步骤6.2,从文件的头部的第一个字节起到尾部的最后一个字节,计算校验和;
步骤6.3,将计算所得的校验和的值和步骤6.1的保留值比较,如果相等,校验通过。
上述步骤7为临时库文件建立内存映像I的具体过程为:
直接调用WindowsAPI函数LoadLibrary,输入参数为临时库文件的文件名。该函数将动态链接库文件载入内存,并将该内存映射到当前进程PC-Client使用的内存地址空间以建立库的内存映像,并重定位需要重定位的地址数据。函数返回内存映像的起始地址,但这时,内存中的代码段和数据段是加密的,而且又无导出表,所以该库被加载了但还是不能使用。
上述步骤8,解密代码段的具体步骤为:
步骤8.1,分析动态链接库内存映像I的头部,获得代码段的头(Sectionheader);
步骤8.2,从代码段头中获得代码段的相对虚拟地址(RelativeVirtualAddress);
步骤8.3,将代码段的相对虚拟地址转化为实际虚拟地址(RealVirtualAddress);
步骤8.4,分析磁盘上的已用密钥K2解密的动态链接库临时文件的头部,获得重定位表;
步骤8.5,调用WindowsAPI函数VirtualProtect将代码段的内存属性改为可读可写;
步骤8.6,采用AES算法用密钥K1解密内存中的代码段,解密时跳过重定位表所标识的需重定位的所有地址数据。
上述步骤9解密可写数据段的具体步骤为:
步骤9.1,分析动态链接库内存映像I的头部,获得可写数据段的头;
步骤9.2,从可写数据段头中获得可写数据段的相对虚拟地址(RelativeVirtualAddress);
步骤9.3,将可写数据段的相对虚拟地址转化为实际虚拟地址(RealVirtualAddress);
步骤9.4,分析磁盘上的已用密钥K2解密的动态链接库临时文件的头部,获得重定位表;
步骤9.5,采用AES算法用密钥K1解密内存中的可写数据段,解密时跳过重定位表所标识的需重定位的所有地址数据。
上述步骤10,解密只读数据段的具体步骤为:
步骤10.1,分析动态链接库内存映像I的头部,获得只读数据段的头;
步骤10.2,从只读数据段头中获得只读数据段的相对虚拟地址(RelativeVirtualAddress);
步骤10.3,将只读数据段的相对虚拟地址转化为实际虚拟地址(RealVirtualAddress);
步骤10.4,调用WindowsAPI函数VirtualProtect将代码段的内存属性改为可读可写;
步骤10.5,分析磁盘上的已用密钥K2解密的动态链接库临时文件的头部,获得重定位表;
步骤10.6,采用AES算法用密钥K1解密内存中的只读数据段,解密时跳过重定位表所标识的需重定位的所有地址数据。
上述步骤11,导入外部动态库的具体步骤为:
步骤11.1,分析动态链接库内存映像I的头部,获得导入表中的第一个导入描述符(importdescriptor);
步骤11.2,从导入描述符中获得一个需要导入的动态链接库的名字,调用WindowsAPI函数LoadLibrary加载该库;
步骤11.3,取下一个导入描述符,再按上述步骤加载外部动态链接库,直到穷尽了所有的导入描述符。
上述步骤12,建立内存映像II的具体步骤为:
步骤12.1,分析动态链接库内存映像I的头部,获得内存映像的大小;
步骤12.2,调用WindowsAPI函数VirtualAlloc分配虚拟内存,大小等于内存映像I的大小;
步骤12.3,将动态链接库内存映像I拷贝到上述步骤分配的虚拟内存,该内存即为动态链接库内存映像II了。
由于内存映像II是从内存映像I的复制而来的,所以其中的一些地址数据依然指向内存映像I范围内的地址,而内存映像I所占的内存在解密加载的最后阶段是要释放的,所以需要将内存映像II中的所有需要重定位的地址数据按照内存映像II的位置进行重定位。
上述步骤13,内存映像II重定位的具体步骤为:
步骤13.1,分析磁盘上的已用密钥K2解密的动态链接库临时文件的头部,获得重定位表;
步骤13.2,计算动态链接库内存映像II的起始地址和动态链接库内存映像I的起始地址之间的差值,假设为diff,即diff=内存映像II起始地址-内存映像I起始地址;
步骤13.3,取得重定位表中的第一个重定位块,假设定义它为RelocBlock;
步骤13.4,对重定位块RelocBlock,将其中包含的页虚拟地址转化成内存映像II中对应页的虚拟地址,假设转化得到的地址定义为pageAddr;
步骤13.5,读取重定块RelocBlock中的第一个需要重定位的地址数据的偏移量,假设记为offset;
步骤13.6,计算偏移量offset在页地址pageAddr中的地址值,即为pageAddr+offset,将该地址内的地址数据加上diff;
步骤13.7,尝试再读取重定位块RelocBlock中下一个需要重定位的地址数据的偏移量,如果还有偏移量,也定义为offset,跳到步骤13.6继续执行,若果没有,执行一下步骤;
步骤13.8,尝试读取重定位表中的下一个重定位块,如果有,也定义为RelocBlock,跳到步骤13.4执行,如果没有,那么整个重定位的过程就此结束。
上述步骤14,导入外部动态库和函数的具体步骤为:
步骤14.1,分析动态链接库映像II的头部,获得导入表中的导入描述符(importdescriptor)数组,取数组中的第一个导入描述符;
步骤14.2,从导入描述符中获得一个需要导入的动态链接库的名字,调用WindowsAPI函数LoadLibrary加载该库;
步骤14.3,遍历导入描述符中image_thunk_data数组中的每一项,获得导入函数的函数名或序号,调用WindowsAPI函数GetProcAddress获取函数地址,再将该地址赋给image_thunk_data中的成员Function;
步骤14.4,从导入描述符(importdescriptor)数组中,取下一个导入描述符,再从步骤14.2开始操作,直到穷尽了所有的导入描述符。
上述步骤15,输出接口函数的具体步骤为:
步骤15.1,分析动态链接库内存映像II的头部,获得代码段的虚拟地址;
步骤15.2,在代码段中查找分隔码<0x40484048CAFECBF4>,分隔码后的第一个字节就是存储一个drm_client_t结构的起始位置;
步骤15.3,调用WindowsAPIImageRvaToVa,将drm_client_t结构中的函数地址从相对虚拟地址转化为在内存映像II的实际虚拟地址;
步骤15.4,将代码段中的drm_client_t结构拷贝给PC-Client使用。
上述步骤16,清除动态链接库内存映像II的头部数据的具体步骤为:
步骤16.1,分析动态链接库内存映像II的头部,获得整个头部的大小;
步骤16.2,从内存映像II的起始地址开始,将整个头部的字节数据赋零值。
上述步骤17,为库内存映像II设属性的具体步骤为:
步骤17.1,调用WindowsAPI函数VirtualProtect,将库内存映像II中的代码段设置成可读可执行属性;
步骤17.2,调用WindowsAPI函数VirtualProtect,将库内存映像II中的可写数据段设置成可读可写属性;
步骤17.3,调用WindowsAPI函数VirtualProtect,将库内存映像II中的只读数据段设置成只读属性。
上述步骤18,释放库内存映像I的具体过程为:
调用WindowsAPI函数FreeLibrary释放库内存映像I,参数为映像起始地址。
经过上述步骤,完成了Windows动态链接库的分段双重加密及安全加载过程。
所以本发明具有:无硬件要求,对动态链接库文件及其代码段数据段使用不同密钥双重加密,具有足够的安全性;无标准导出表,只有私有导出表,使用私有的加载方法为库建立内存映像,能够有效地防止发布出去的动态链接库被非法使用、被非法分析、被非法调试跟踪,操作简单使用方便等特征。