手动签署 PE 文件

Manually sign a PE file

我正在尝试手动签署现有的 Portable Executable。

我正在按照 this document 中的说明进行操作:

  1. 将图像 header 加载到内存中。
  2. 初始化哈希算法上下文。
  3. 按照可选 Header Windows-Specific 字段中的指定,将图像 header 从其基址散列到校验和地址开始之前。
  4. 跳过校验和,这是一个 4 字节的字段。
  5. 按照可选 Header 数据目录中的规定,对从校验和字段末尾到证书 Table 条目开始之前的所有内容进行哈希处理。
  6. 从证书 Table 条目中获取属性证书 Table 地址和大小。有关详细信息,请参阅 PE/COFF 规范的第 5.7 节。
  7. 从计算中排除证书 Table 条目,并对从证书 Table 条目末尾到图像 header 末尾的所有内容进行哈希处理,包括部分 Table (headers)。证书 Table 条目的长度为 8 个字节,如可选 Header 数据目录中所指定。
  8. 创建一个名为 SUM_OF_BYTES_HASHED 的计数器,它不是签名的一部分。将此计数器设置为 SizeOfHeaders 字段,如可选 Header Windows-Specific 字段中指定。
  9. 构建一个临时的 table 指针,指向图像中的所有部分 header。 COFF 文件 Header 的 NumberOfSections 字段指示 table 应该有多大。不要在 SizeOfRawData 字段为零的 table 中包含任何部分 headers。
  10. 使用引用的SectionHeader结构中的PointerToRawData字段(偏移量20)作为关键字,按升序排列table的元素。也就是说,根据sections的disk-file偏移量,对sectionheaders进行升序排序。
  11. 遍历已排序的 table,将相应部分加载到内存中,并对整个部分进行哈希处理。使用 SectionHeader 结构中的 SizeOfRawData 字段来确定要散列的数据量。
  12. 将该部分的 SizeOfRawData 值添加到 SUM_OF_BYTES_HASHED。
  13. 对已排序 table 中的所有部分重复步骤 11 和 12。
  14. 创建一个名为 FILE_SIZE 的值,它不是签名的一部分。将此值设置为从底层文件系统获取的图像文件大小。如果 FILE_SIZE 大于 SUM_OF_BYTES_HASHED,则文件包含必须添加到散列中的额外数据。此数据从 SUM_OF_BYTES_HASHED 文件偏移量开始,其长度为: (File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED) 注:Attribute Certificate Table 的大小在 Certificate Table 的第二个 ULONG 值中指定可选 Header 数据目录中的条目(32 位:偏移量 132、64 位:偏移量 148)。
  15. 完成哈希算法上下文。注意:此过程使用来自 PE/COFF 规范 8.1 版的偏移值。有关权威偏移值,请参阅最新版本的 PE/COFF 规范。

以下代码尝试从图像中获取 part-to-be-hashed:

        // 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
        }

一定是哪里出了问题。签署此数据,然后更新 executable 证书条目并附加 PCKS#7 签名会导致 Windows 无法识别的 executable。右击-> "Invalid Signature".

signtool.exe的结果比较时,签名不同。当我尝试使用 CryptVerifyDetachedMessageSignature 验证此签名时,出现错误 0x80091007,这意味着哈希值不正确。

这意味着我没有正确计算 "what to be signed" 缓冲区。我想念什么?

我什至对条目的删除进行了硬编码:

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 所必需的。