前言
最近,我维护的一个对进程进行控制的模块出现了一个bug,简单来讲就是在xp系统下利用微软的api无法读取文件的sha256签名人名称,这样的话,我的模块就无法支持 xp系统下的正常进程管控功能了,也就是说无法支持在配置签名的情况下,匹配到相应的进程了。
`
解决思路
找了一些方法,查了一些网上的资料,开源的openssl可以读取到数字签名证书,但是我用c++还是没能实现,比如x509_st这个结构体1.0版本之后就不公开了,研究了几天还是没有能够解决,不过用python倒是读取到了,下面是python实现的方法。
但是我们还是需要使用c++的方法来实现,由于时间有限,openssl暂时没有找到解决方案,我就想到了一个算法来匹配数字签名者姓名信息。
首先先简单介绍下我的解决思路,安全证书的这段数据是存储在pe头中的某个位置,所以要先从pe投中拿到这段数据,然后使用“最长公共连续子序列”(由最长公共子序列改造)这个算法来匹配这段数据中是否含有签名者信息。
一、读取pe中的安全证书信息数据
下面就是pe头的证书数据
**可以打开文件拿到这部分的数据,certTableRva 这个是偏移地址,certTableSize 是这块数据的大小。
PIMAGE_OPTIONAL_HEADER32 header = (PIMAGE_OPTIONAL_HEADER32)&pNtHeaders->OptionalHeader;
certTableRva = header->DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress;
certTableSize = header->DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size;**
二、如何匹配
由于我并不熟悉openssl,又需要尽快实现签名匹配的功能,所以我想到了一个算法就是LCR 095. 最长公共子序列(leetcode),但是这个算法有个问题就是匹配的字符串不是连续的,所以要把这个算法改造成最长连续公共子序列,这样的话,我们可以把签名和证书信息的数据在一起匹配返回公共字符串的长度,这个长度如果和签名人字符串的长度大小相等,那么我们可以认为证书信息包含签名人,匹配成功否则匹配失败。
BOOL longest_common_substring(const char *str1, int len1, const char* str2, int len2)
{
int m = len1;
int n = len2;
int max_len = 0;
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (str1[i - 1] == str2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > max_len)
{
max_len = dp[i][j];
}
//new
if(max_len == len2)
{
return TRUE;
}
}
else
{
dp[i][j] = 0;
}
}
}
return FALSE;
}
上面这个算法是我根据最长公共子序列改造的“最长连续公共子序列”
输入证书信息字串、长度和签名人字串、长度,返回判断结果。
三、如何使用openssl
当然这个匹配方法还是有些“投机取巧”,还是需要用比较正统的方法来拿到这个签名人的字符串,所以经过了几天的研究还是找到了解决的方案。
我直接把解析的代码贴出来
//新建BIO对象
BIO* bio = BIO_new_mem_buf(strBuffer, certTableSize);
PKCS7* p7 = d2i_PKCS7_bio(bio,NULL);
//释放BIO对象
BIO_free(bio);
if (!p7) {
printf("Error loading PKCS7 structure.\n");;
return FALSE;
}
//获取X509
STACK_OF(X509)* certs = PKCS7_get0_signers(p7, NULL, 0);
if (sk_X509_num(certs) < 1) {
printf("No signer certificate found.\n");
PKCS7_free(p7);
return FALSE;
}
X509* cert = sk_X509_value(certs, 0);
sign = get_certificate_info(cert, NID_organizationName);
PKCS7_free(p7);
strBuffer就是上面我说的certTableRva 那块安全证书的内存。
四、总结
其实在使用openssl的过程中还是遇到蛮多坑的,openssl的编译使用网上有很多资料并不难实现,但是在解析PKCS7时遇到的一些问题并不是太容易查到,比如我在使用d2i_PKCS7_bio这个函数的时候 ,一直返回NULL,然后查不到任何的错误信息,最后才知道我需要在工程中包含applink.c这个源文件。