手动签署 PE 文件

Mic*_*kis 6 c++ winapi portable-executable

我正在尝试手动签署现有的便携式可执行文件。

我正在按照本文档中的说明进行操作:

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

一定是哪里出了问题。对该数据进行签名,然后更新可执行证书条目并附加 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);
Run Code Online (Sandbox Code Playgroud)

非常感谢。

Mic*_*kis 3

我找到了解决方案。

PE内部的签名不是典型的PKCS#7,它还包含文档中描述的特定的已验证和未验证属性。

一旦满足了这些,签名就OK了。但是,我无法使用 CAdES,因为 Windows 不会接受列表中任何其他经过身份验证的属性,而这是验证 CAdES 所必需的。