“数字签名”是个老话题,相关文章多如牛毛。那年写的那个《Windows安全检测工具》里面有个“对进程或驱动文件的签名验证”的功能,好像用到了Windows的api,曾是个特色。
为什么写这个?一是好久没来怪想念的;二是源于看到一段代码,顺便学习下这个知识点。事后,自感学习能力下降得厉害;花了不少的时间才理出个头绪,确实烧脑,各位可能会头晕。
言归正传,那段py代码的功能是将一个PE的官方签名的移植到另一个无签名的PE程序,以作...之用。看了下,挺有意思的,如下:
flItms = {}
binary = open(binary, 'rb')
binary.seek(int('3C', 16))
flItms['buffer'] = 0
flItms['JMPtoCodeAddress'] = 0
flItms['dis_frm_pehdrs_sectble'] = 248
......
flItms['Characteristics'] = struct.unpack('<H', binary.read(2))[0]
flItms['OptionalHeader_start'] = flItms['COFF_Start'] + 20
......
binary.seek(flItms['OptionalHeader_start'])
flItms['Magic'] = struct.unpack('<H', binary.read(2))[0]
......
flItms['ImportTableRVA'] = struct.unpack('<I', binary.read(4))[0]
flItms['ImportTableSize'] = struct.unpack('<I', binary.read(4))[0]
flItms['ResourceTable'] = struct.unpack('<Q', binary.read(8))[0]
flItms['ExceptionTable'] = struct.unpack('<Q', binary.read(8))[0]
flItms['CertTableLOC'] = binary.tell()
flItms['CertLOC'] = struct.unpack("<I", binary.read(4))[0]
flItms['CertSize'] = struct.unpack("<I", binary.read(4))[0]
binary.close()
return flItms
顺着好奇一路走来,感觉实现难度不大,但现实是我花了不少时间且在若干资料的帮助下才完成了这篇习作,感觉当年大伽们设计数字签名的公私钥验证真非常有智慧,环环相扣,缜密绵绵。现按照我的理解整理出来,方便大家学习......
工具准备:
A、工具:makecert、signtool、asn1dump、010editor、dd、openssl等;
B、原始(x86) exe一个(x64的exe等也行),名为test.exe;
一、自制数字签名
1、makecert --help
-r: 自签名
-n: 证书名称,格式为-n “CN=名称, E=Email,O=组织名称,C=国家, S=省份(州), P=县城”
-a: 指定散列算法,其值必须是md5(默认值)或SHA1 ;
-$: 指定证书的签名权限,其值必须是commercial(商业软件)或individual(个人软件) ;
-b: 证书有效期的开始时间,格式为mm/dd/yyyy
-e: 证书有效期的结束时间,格式为mm/dd/yyyy
命令:makecert -r -$ "individual" /sv "test.PVK" -n "CN=YXZ,O=WHU,C=China,S=AnHui" -a md5 -b 03/16/2020 -e 01/01/2030 test.cer
生成了“test.cer(数字证书文件)、test.PVK(数字签名的私钥文件)”;
2、安装证书:
双击test.cer,输入密码,下一步...,成功安装;(略)
3、signtool进行签名
复制一个test.exe为test02.exe,对test02.exe进行数字签名;test.exe方便和test02.exe进行比对;
这里为test03.exe,因为02已经签名成功了,以03为例吧。
注意,这里的散列算法选择“SHA1”。这里的散列算法是PE文件的签名信息, 而之前makecert.exe设置的md5是证书的散列算法。
打开test02.exe文件属性,可以看到它增加了一个“数字签名”的区域,并且能够看到此数字签名是正常的及详细信息。
二、PE数字签名的头格式
1、签名在PE头的位置,位于:
PE头的struct IMAGE_NT_HEADERS NtHeader-->struct IMAGE_OPTIONAL_HEADER32 OptionalHeader-->struct IMAGE_DATA_DIRECTORY_ARRAY DataDirArray-->struct IMAGE_DATA_DIRECTORY Security这里,两项内容“偏移VirtualAddress和大小Size”,
从而得知,在文件偏移“BD8000h”处,大小为1176(498h);
我们比较下 无签名 的此处位置值:
都为0;
2、偏移处BD8000h的Certificate Table:
可以看到Certificate Table存储相关签名信息:
文件开始位置:BD8000h
表项长度:4字节,头部和签名数据的总长度498h;
证书版本:2字节,常见0x0200表示WIN_CERT_REVISION_2;
证书类型:2字节,常见0x0002表示包含PKCS#7的SignData结构
SignedData:包含PE文件Hash值的签名数据、软件发布者公钥,选用的签名及散列算法等。(在文件中为ASN.1编码)
3、提取签名数据
可以用010editor来提取,也可以用dd工具来提取;
Certificate Table从第9个字节开始后为签名信息,存为文件“test02Pkcs7Data.bin”;
这里我们用dd工具:
dd if=./test02.exe of=./test02Pkcs7Data.bin skip=12419080 bs=1 count=1168
BD8000h转换为十进制为12419072+8,498h为1176-8,(减去8个字节的版本号和证书类型);
如果导出正确了,可以用asn1dump正常打开,
三、PE签名数据分析
上图中左半部分为PE结构。右半部分,微软对PKCS#7格式的数字签名数据部分做了一个大概的结构介绍,实际的结构对应SignedData。
1、PKCS#7 微软官方文档
一个PKCS#7 SignedData结构包括PE文件的哈希值,一个被软件出版厂商的私钥创建的签名、将软件出版厂商的签名密钥和法人代表进行绑定的(系列)X.509 v3证书。PKCS#7 1.5版本规范定义了如下关于SignedData 的 ASN.1(抽象语法符号)结构:
SignedData的定义:
注意,导出的“test02Pkcs7Data.bin”签名数据为ASN.1抽象结构,需要用ASN1View或ASN1Dump进行解析,
如果不方便,可以将hex导出,放入这个网站https://holtstrom.com/michael/tools/asn1decoder.php将hex转换成asn1:
在这里对应的flag,如:指定SignedData结构值为“1.2.840.113549.1.7.2”,表示采用PKCS#7结构;生成签名的哈希算法MD5SHA1SHA256签名属性SPC证书颁发者信息(包括md5withRSA签名、证书颁发者YXZ、组织WHU、国家及省份)等。核心数据包括:散列算法摘要数据公钥数据签名后数据;
所有的对应最后显示成这张图;
2、分析Contentinfo部分数据。
该部分主要存储PE文件的hash值、以及标记变量、散列算法等。
比如上面的sha1算法。
3、分析Certificates部分数据。
该部分主要存储证书相关信息,包括证书发布者、证书时间戳等信息。注意,该部分内容可以直接导出,再和“test02.exe”的数字证书 进行对比。
比如省份“AnHui”。
4、采用010Editor导出证书部分数据,并进行对比实验。
我们从“30 82”开始复制。这里采用010Editor工具复制。
保存为h.cer,和test02.exe中的证书对比下,
对比从010Editor导出的“h.cer”证书和“test02.exe”数字签名信息,发现是一致的(包括公钥),该实验也证明了签名数据的 第二部分为证书信息。
四、数字签名的移植
将数字签名移植到无数字签名的程序上,移花接木。
步骤如下:
1、有签名程序开展,找到PE头的struct IMAGE_DATA_DIRECTORY Security,取出其签名的偏移和大小长度,移到偏移处,取大小长度的内容;
2、将以上长度的内容复制到无签名程序的尾部,修改struct IMAGE_DATA_DIRECTORY Security处的偏移和长度值,指向Certificates;
3、完工。
要不要写个程序呢?其实很简单了,有时间再写吧!
原文始发于微信公众号(MicroPest):PE数字签名的移植
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论