• 2.6 PE结构:导出表详细解析


    导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。

    导出表中包含了三种信息:

    • 函数名称:记录了可执行文件中导出函数的名称,在其他程序中调用时需要用到这个名称。
    • 函数地址:记录了可执行文件中导出函数的地址,使用时需要调用该函数的地址。
    • 函数序号:记录了每个导出函数的序号,可以通过序号直接调用函数。

    导出函数的DLL文件中,导出信息被保存在导出表,导出表就是记载着动态链接库的一些导出信息。通过导出表,DLL文件可以向系统提供导出函数的名称、序号和入口地址等信息,以便Windows装载器能够通过这些信息来完成动态链接的整个过程。

    导出函数存储在PE文件的导出表里,导出表的位置存放在PE文件头中的数据目录表中,与导出表对应的项目是数据目录中的首个IMAGE_DATA_DIRECTORY结构,从这个结构的VirtualAddress字段得到的就是导出表的RVA值,导出表同样可以使用函数名或序号这两种方法导出函数。

    导出表的起始位置有一个IMAGE_EXPORT_DIRECTORY结构与导入表中有多个IMAGE_IMPORT_DESCRIPTOR结构不同,导出表只有一个IMAGE_EXPORT_DIRECTORY结构,该结构定义如下:

    typedef struct _IMAGE_EXPORT_DIRECTORY
    {
        DWORD   Characteristics;       // 保留,恒为0x00000000
        DWORD   TimeDateStamp;         // 文件的产生时间戳
        WORD    MajorVersion;          // 主版本号
        WORD    MinorVersion;          // 次版本号
        DWORD   Name;                  // 指向文件名的RVA
        DWORD   Base;                  // 导出函数的起始序号
        DWORD   NumberOfFunctions;     // 导出函数总数
        DWORD   NumberOfNames;         // 以名称导出函数的总数
        DWORD   AddressOfFunctions;    // 导出函数地址表的RVA
        DWORD   AddressOfNames;        // 函数名称地址表的RVA
        DWORD   AddressOfNameOrdinals; // 函数名序号表的RVA
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上面的_IMAGE_EXPORT_DIRECTORY 结构如果总结成一张图,如下所示:

    在上图中最左侧AddressOfNames结构成员指向了一个数组,数组里保存着一组RVA,每个RVA指向一个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals中的结构成员,该对应项存储的正是函数的唯一编号并与AddressOfFunctions结构成员相关联,形成了一个导出链式结构体。

    获取导出函数地址时,先在AddressOfNames中找到对应的名字MyFunc1,该函数在AddressOfNames中是第1项,然后从AddressOfNameOrdinals中取出第1项的值这里是1,然后就可以通过导出函数的序号AddressOfFunctions[1]取出函数的入口RVA,然后通过RVA加上模块基址便是第一个导出函数的地址,向后每次相加导出函数偏移即可依次遍历出所有的导出函数地址,代码如下所示:

    int main(int argc, char * argv[])
    {
        BOOL PE = IsPeFile(OpenPeFile("c://pe/lyshark.dll"), 0);
    
        if (PE == TRUE)
        {
            // 0. 获取到ImageBase镜像基地址
            DWORD ImageBase = NtHeader->OptionalHeader.ImageBase;
    
            // 1. 从数据目录表的下标为 0 的项找到rva
            DWORD rav = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
    
            // 2. 找到导入表结构体
            auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(RVAtoFOA(rav) + GlobalFileBase);
    
            // 3. 获取有名字的个数和函数总个数
            DWORD NameCount = ExportTable->NumberOfNames;
            DWORD FunctionCount = ExportTable->NumberOfFunctions;
    
            // 4. 获取三张表,分别是 地址表,名称表,序号表,其中序号表是WORD
            DWORD* Addr_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfFunctions) + GlobalFileBase);
            DWORD* Name_Table = (DWORD*)(RVAtoFOA(ExportTable->AddressOfNames) + GlobalFileBase);
            WORD* Id_Table = (WORD*)(RVAtoFOA(ExportTable->AddressOfNameOrdinals) + GlobalFileBase);
    
            printf("序号 \t 导出RVA地址 \t 导出VA地址 \t 导出FOA地址 \t 导出函数 \t \n");
    
            // 5. 遍历地址表
            for (DWORD i = 0; i < FunctionCount; ++i)
            {
                bool HaveName = FALSE;
    
                // 6. 判断是否有名字,有名字的话,下标会存在序号表中
                for (DWORD j = 0; j < NameCount; ++j)
                {
                    // 如果有名字则执行此处
                    if (i == Id_Table[j])
                    {
                        HaveName = TRUE;
                        // 对应序号表下标的名称表内保存的是名字
                        CHAR* Name = (CHAR*)(RVAtoFOA(Name_Table[j]) + GlobalFileBase);
                        printf("%5d \t %10p \t 0x%08X \t 0x%08X \t %-35s \n",
                            i + ExportTable->Base, Addr_Table[i], ImageBase + Addr_Table[i], RVAtoFOA(Addr_Table[i]), Name);
                        break;
                    }
                }
                // 如果全部找完还没有名字
                if (HaveName == FALSE)
                {
                    printf("%5d \t %10p \t 0x%08X \t 0x%08X \t None \n",
                        i + ExportTable->Base, Addr_Table[i], ImageBase + Addr_Table[i], RVAtoFOA(Addr_Table[i]));
                }
            }
        }
        else
        {
            printf("非标准程序 \n");
        }
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    运行如上程序片段,则会输出lyshark.dll动态链接库里面所有的导出函数,其输出效果如下图所示;

  • 相关阅读:
    selenium+python自动化安装驱动 碰到的问题
    一周万星的文本转语音开源项目「GitHub 热点速览」
    PC_机器数_定点负数的原码_补码_反码在结构上的关系
    HyperLynx(三十二)高速串行总线仿真(四)
    C语言二维数组定义、赋值、按要求遍历操作、输出以及函数调用
    C++多线程学习10 promise和future多线程异步传值
    Kotlin 语言基础学习
    备份系统运行数据采集及分析方法
    概率路图法(PRM)路径规划算法简述
    【计算机视觉】图像的获取和表示——图像传感器技术|主要参数解析、成像原理剖析、传感器处理
  • 原文地址:https://blog.csdn.net/lyshark_csdn/article/details/132732136