我正在尝试手动签署现有的可移植可执行文件。
我正在按照 this document 中的说明进行操作:
- 将图像 header 加载到内存中。
- 初始化哈希算法上下文。
- 按照可选 header Windows 特定字段中的规定,将图像 header 从其基址散列到校验和地址开始之前。
- 跳过校验和,这是一个 4 字节的字段。
- 按照可选 header 数据目录中的规定,对从校验和字段末尾到证书表条目开始之前的所有内容进行哈希处理。
- 从证书表条目中获取属性证书表地址和大小。有关详细信息,请参阅 PE/COFF 规范的第 5.7 节。
- 从计算中排除证书表条目,并对从证书表条目末尾到图像 header 末尾的所有内容进行哈希处理,包括节表( header )。证书表条目长 8 个字节,如可选 header 中所指定数据目录。
- 创建一个名为 SUM_OF_BYTES_HASHED 的计数器,它不是签名的一部分。将此计数器设置为 SizeOfHeaders 字段,如可选 header Windows 特定字段中所指定。
- 建立一个指向图像中所有节标题的临时指针表。 COFF 文件头的 NumberOfSections 字段指示该表应该有多大。不要在 SizeOfRawData 字段为零的表中包含任何节标题。
- 使用引用的 SectionHeader 结构中的 PointerToRawData 字段(偏移量 20)作为键,按升序排列表的元素。换句话说,根据节的磁盘文件偏移量按升序对节标题进行排序。
- 遍历排序后的表,将相应的部分加载到内存中,并对整个部分进行哈希处理。使用 SectionHeader 结构中的 SizeOfRawData 字段来确定要散列的数据量。
- 将该部分的 SizeOfRawData 值添加到 SUM_OF_BYTES_HASHED。
- 对排序表中的所有部分重复步骤 11 和 12。
- 创建一个名为 FILE_SIZE 的值,它不是签名的一部分。将此值设置为从底层文件系统获取的图像文件大小。如果 FILE_SIZE 大于 SUM_OF_BYTES_HASHED,则文件包含必须添加到散列中的额外数据。此数据从 SUM_OF_BYTES_HASHED 文件偏移量开始,其长度为: (File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED) 注意:Attribute Certificate Table 的大小在 Optional Header 中的 Certificate Table 条目中的第二个 ULONG 值中指定(32 位:偏移量 132,64 位:偏移量 148)数据目录。
- 完成哈希算法上下文。注意:此过程使用来自 PE/COFF 规范 8.1 版的偏移值。有关权威偏移值,请参阅最新版本的 PE/COFF 规范。
以下代码尝试从图像中获取待哈希部分:
// Variables
// full: vector<char> holding the image
// d: vector<char> where to store the data-to-be-hashed
// sections: vector of the sections, ensuring size > 0
// nt/pnt* : pointer inside full that points to the beginning of NT header
// Sort Sections
std::sort(sections.begin(), sections.end(), [](const section& s1, const section& s2) -> bool
{
if (s1.sec->PointerToRawData < s2.sec->PointerToRawData)
return true;
return false;
});
// Up to where?
size_t BytesUpToLastSection = ((char*)(sections[sections.size() - 1].sec) - full.data()) + sizeof(image_section_header);
d.resize(BytesUpToLastSection);
memcpy(d.data(), full.data(), BytesUpToLastSection);
// We remove the certificate table entry (8 bytes)
size_t offset = 0;
if (nt.Is32())
{
offset = offsetof(optional_header_32, DataDirectory[DIR_SECURITY]);
}
else
{
offset = offsetof(optional_header_64, DataDirectory[DIR_SECURITY]);
}
offset += sizeof(nt.FileHeader) + sizeof(nt.Signature);
offset += pnt - full.data();
d.erase(d.begin() + offset, d.begin() + offset + 8);
// We remove the checksum (4 bytes)
if (nt.Is32())
offset = offsetof(optional_header_32,CheckSum);
else
offset = offsetof(optional_header_64,CheckSum);
offset += sizeof(nt.FileHeader) + sizeof(nt.Signature);
offset += pnt - full.data();
d.erase(d.begin() + offset, d.begin() + offset + 4);
// Counter
size_t SUM_OF_BYTES_HASHED = 0;
if (nt.Is32())
SUM_OF_BYTES_HASHED = std::get<optional_header_32>(nt.OptionalHeader).SizeOfHeaders;
else
SUM_OF_BYTES_HASHED = std::get<optional_header_64>(nt.OptionalHeader).SizeOfHeaders;
for (auto& ss : sections)
{
if (ss.sectionData.sz == 0)
continue;
s = d.size();
d.resize(d.size() + ss.sectionData.sz);
memcpy(d.data() + s, ss.sectionData.p, ss.sectionData.sz);
SUM_OF_BYTES_HASHED += ss.sec->SizeOfRawData;
}
size_t FILE_SIZE = full.size();
if (FILE_SIZE > SUM_OF_BYTES_HASHED)
{
// Not entering here, test executable does not have extra data
}
一定是哪里出了问题。签署此数据,然后更新可执行证书条目并附加 PCKS#7 签名会导致 Windows 无法识别可执行文件。右击->“无效签名”。
与signtool.exe
的结果比较,签名不一样。当我尝试使用 CryptVerifyDetachedMessageSignature
验证此签名时,出现错误 0x80091007,这意味着散列不正确。
这意味着我没有正确计算“要签名的内容”缓冲区。我想念什么?
我什至对条目的删除进行了硬编码:
d = full;
d.erase(d.begin() + 296, d.begin() + 296 + 8);
d.erase(d.begin() + 216, d.begin() + 216 + 4);
非常感谢。
最佳答案
我找到了解决方案。
PE 中的签名不是典型的 PKCS#7,它还包含 document 中描述的特定已验证和未验证属性.
这些都满足了,签名就OK了。但是我不能使用 CAdES,因为 Windows 不会接受列表中的任何其他经过身份验证的属性,这是验证 CAdES 所必需的。
关于c++ - 手动签署 PE 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57151382/