是否存在既没有序数也没有名称的导出,或者我不理解 PE 文档?

逆向工程 视窗 聚乙烯 文件格式 可执行
2021-06-10 02:37:42

我对PE文件的导出数据目录中的一件事不了解。

文档说有一组导出计数(让我们命名它ExportCount,下表的第一行)和另一个名称/序数计数(命名它NameCount,下表中的第二行)。我读起来就像导出名称的计数与序数索引计数相同。至少他们的文档是这样说的:

微软文档

我尝试解析 Win8.1.1 x64 Shell32.dll,与 Dependency Walker 相比,我得到了不同的结果。我有 933 个 asExportCount和 354 个 as NameCount所以总共应该有 933 个导出,只有 354 个有序数和/或名称。不要问我你将如何导入剩余的 579 个导出,因为那是我不明白的。

如果我在Dependency Walker中打开SHELL32,它首先列出了NameCount出口的名称和顺序,但随后显示的剩余量ExportCount - NameCount出口其令人惊讶地做到有序(在蓝线从这里开始):

依赖行者

对我来说,根据文档,这没有任何意义。我试图阅读ExportCount序数而不是NameCount按顺序阅读序数,但只有垃圾出现。

所以我的问题是:

  • 文档是否错误/不完整?
  • 我理解文档中有什么问题吗?
  • 如何获得像 Dependency Walker 这样的剩余序数呢?
3个回答

如果我正确理解http://win32assembly.programminghorizo​​n.com/pe-tut7.html,则序数表列出实际具有名称的导出数量。因此,您的程序可能具有以下导出:

n  name            address
0  funca           12345678
1  -- no name --   9abcdef0
2  funcb           76543210
3  -- no name --   fedcba98

这会导致

address table entries   = 4
number of name pointers = 2
export address table    = [ 12345678, 9abcdef0, 76543210, fedcba98 ]
name pointer table      = [ funca, funcb ]
ordinal table           = [ 0, 2 ]

名称指针条目和序数表条目之间存在 1:1 匹配,这就是名称指针的数量等于两个数组大小的原因。但是两个表都只列出了按名称导出的函数;仅按序排列的导出不会出现在其中任何一个中。但是,它们仍然出现在导出地址表中。

(如果你想要一个更好/更精确的答案,你必须等待 Jason Geffner,他是这类事情的惊人信息来源)。

文档是正确的。项目可以按名称或顺序导出。

对于按名称导出,序数表用于在导出表中定位地址。Name 表中名称的索引是 Ordinal 表中序数的索引。序号表中序号的值是地址表的索引。

对于按序数导出,序数的值用于直接索引到地址表中(因此完全有可能根本没有名称,但有很多导出)。序数表中没有条目。出于显而易见的原因,不建议按顺序导出,除非有协议规定此类导出永远不能移动。

作为一种特殊的边缘情况,ExportCount 可能看起来高于导出数量,直到您减去 Ordinal 基数。例如,如果我创建一个具有两个导出的 DLL,并且其 OrdinalBase 为 -1,则我的 ExportCount 将显示为 4。

这个答案最初是我添加的,作为对我的问题的编辑,我现在将它与那里分开:

感谢 Guntrams 接受的答案,我发现了我的大脑有问题的地方。这是它的真实情况:

  • 每个导出地址的序数只是导出地址数组中的索引(加上导出目录表中指定的序数基数,这可能使其与简单索引不同。这通常是这种情况,因为序数通常从 1 开始,根据到文档)。
  • 有些导出有名字,这些名字用序号映射到导出地址数组,依次是导出地址数组的索引。
  • 由于某些导出没有名称,因此在序数数组中未明确指定序数,因为将“无名称”映射到导出是没有意义的。

我的 C# 代码是这样做的,供参考 - 输出与 Dependency Walker 中的输出相同:

private void ReadExportTables(
    PEFile peFile,
    BinaryReader reader,
    DataDirectoryHeader header)
{
    // Read export address table which contains RVAs to exported code or forwarder names.
    ExportEntry[] exportEntries = new ExportEntry[EntryCount];
    reader.BaseStream.Seek(peFile.GetFileOffset(CodeAddressTableRva), SeekOrigin.Begin);
    for (int i = 0; i < exportEntries.Length; i++)
    {
        exportEntries[i].Ordinal = (ushort)(i + OrdinalStartNumber);
        exportEntries[i].CodeOrForwarderRva = reader.ReadUInt32();
    }

    // Read ordinal table containing indices (with base) to named entries in export entry table.
    reader.BaseStream.Seek(peFile.GetFileOffset(OrdinalTableRva), SeekOrigin.Begin);
    uint[] ordinals = new uint[NameEntryCount];
    for (int i = 0; i < ordinals.Length; i++)
    {
        // Get name for ordinal, which has the same index as the ordinal array element.
        ordinals[i] = reader.ReadUInt16();
    }

    // Read the export name pointer table which contains pointers to names of exports.
    reader.BaseStream.Seek(peFile.GetFileOffset(NameAddressTableRva), SeekOrigin.Begin);
    for (int i = 0; i < ordinals.Length; i++)
    {
        exportEntries[ordinals[i]].Hint = i;
        exportEntries[ordinals[i]].NameRva = reader.ReadUInt32();
    }

    // Read the names of the exports or forwarders.
    for (int i = 0; i < exportEntries.Length; i++)
    {
        if (exportEntries[i].NameRva > 0)
        {
            reader.BaseStream.Seek(peFile.GetFileOffset(
                exportEntries[i].NameRva),
                SeekOrigin.Begin);
            exportEntries[i].Name = reader.Read0AsciiString();
        }
        // Check if forwarder export (RVA points within export directory to forwarder name).
        if (exportEntries[i].CodeOrForwarderRva >= header.Rva
            && exportEntries[i].CodeOrForwarderRva < header.Rva + header.Size)
        {
            reader.BaseStream.Seek(
                peFile.GetFileOffset(exportEntries[i].CodeOrForwarderRva),
                SeekOrigin.Begin);
            exportEntries[i].ForwarderName = reader.Read0AsciiString();
        }
    }
}

如果您想知道:仍然可能存在完全空的导出,代码地址为 0,没有名称,因此不会被转发。只需在显示导出时对这些进行排序即可。