【发布时间】:2019-11-30 17:54:16
【问题描述】:
我正在尝试手动签署现有的便携式可执行文件。
我正在按照this document 中的说明进行操作:
- 将图像头加载到内存中。
- 初始化哈希算法上下文。
- 按照可选标头 Windows 特定字段中的规定,将图像标头从其基地址散列到校验和地址开始之前。
- 跳过校验和,这是一个 4 字节的字段。
- 按照可选标头数据目录中的指定,对从校验和字段末尾到证书表条目开始之前的所有内容进行哈希处理。
- 从证书表条目中获取属性证书表地址和大小。有关详细信息,请参阅 PE/COFF 规范的第 5.7 节。
- 从计算中排除证书表条目并散列从证书表条目末尾到图像标题末尾的所有内容,包括节表(标题)。证书表条目长 8 个字节,如可选标题中指定数据目录。
- 创建一个名为 SUM_OF_BYTES_HASHED 的计数器,它不是签名的一部分。将此计数器设置为 SizeOfHeaders 字段,如可选标题 Windows 特定字段中指定的那样。
- 构建一个指向映像中所有节标题的临时指针表。 COFF File Header 的 NumberOfSections 字段指示表应该有多大。不要在表中包含 SizeOfRawData 字段为零的任何节标题。
- 使用引用的 SectionHeader 结构中的 PointerToRawData 字段(偏移量 20)作为键,按升序排列表的元素。也就是说,根据sections的磁盘文件偏移量对section headers进行升序排序。
- 遍历排序表,将相应部分加载到内存中,并对整个部分进行哈希处理。使用 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 entry (32 bit: offset 132, 64 bit: offset 148) 中的第二个 ULONG 值中指定数据目录。
- 最终确定哈希算法上下文。注意:此过程使用来自 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);
非常感谢。
【问题讨论】:
-
您不能使用 CryptoAPI(即
SignerSignEx2)? Example. -
没有。重点是用CadES签名。然而实际的签名不是问题,问题是什么哈希。
-
您写了
offsetof(optional_header_32, DataDirectory[DIR_SECURITY]),但我不知道需要offsetof才能支持下标。 -
对于所有这些
offset += sizeof(nt.FileHeader) + sizeof(nt.Signature); offset += pnt - full.data();,为什么不为“可选标题的开头”创建一个变量并只计算一次。 -
@BenVoigt 我什至对字节进行了硬编码以确保偏移正确:
d = full; d.erase(d.begin() + 296, d.begin() + 296 + 8); d.erase(d.begin() + 216, d.begin() + 216 + 4);
标签: c++ winapi portable-executable