什么是导出表

当dll文件提供函数给其他模块函数使用或者,_declspec(dllexport) 函数 声明导出。

用以记录本模块能够给其他模块提供的函数的信息。

函数名 函数地址 函数的序号

程序运行的时候,会检查主模块的导入表,看用了哪些其他模块,就会将此模块加载到进程空间中。加载进来之后,分析INT(IAT)得到函数名称,用这个名称去导出表中找到函数地址的RVA,RVA+模块基址,就是真正的函数地址,将此函数地址填充到IAT中,从而完成加载后的IAT功能。

一个PE文件,可以没有导出表的,比如exe文件,一般都没有。

如下图。

序号表是一个 WORD 2字节的数组

通过遍历这个数组的值 对应着地址表的下表 : 通过序号表的值获取地址表的地址,然后因为序号表和名称表是一一对应的所以说可以直接获取名称表,注意有些函数只有序号没有名称

image.png

使用代码解析

请输入图片描述

#include <iostream>
#include <Windows.h>
#define path L"C:\\Users\\Administrator\\source\\repos\\测试\\Debug\\kernel32.dll"
char* buf;

PIMAGE_DOS_HEADER GET_DOS(char* buf);
PIMAGE_NT_HEADERS GET_NT(char* buf);
BOOL IS_PEFILE(char* buf);

BOOL IS_PEFILE(char* buf) 
{
    if (GET_DOS(buf)->e_magic == IMAGE_DOS_SIGNATURE && GET_NT(buf)->Signature == IMAGE_NT_SIGNATURE) 
    {
        printf("是PE文件\n");
        return TRUE;
    }
    else 
    {
        printf("不是PE文件\n");
        return FALSE;
    }

}
DWORD RvaToFoa(char* lpImage, DWORD dwRva)
{
    //1 获取区段表的起始位置
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpImage;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + lpImage);
    PIMAGE_SECTION_HEADER pHeader = IMAGE_FIRST_SECTION(pNt);

    if (dwRva < pNt->OptionalHeader.SizeOfHeaders)
    {
        return dwRva;
    }
    //2 循环判断RVA落在了哪个区段中
    for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
    {
        DWORD dwSectionRva = pHeader[i].VirtualAddress;
        DWORD dwSectionEndRva = dwSectionRva + pHeader[i].SizeOfRawData;
        DWORD dwSectionFOA = pHeader[i].PointerToRawData;
        if (dwRva >= dwSectionRva && dwRva < dwSectionEndRva)
        {
            pHeader[i].VirtualAddress;
            DWORD dwFOA = dwRva - dwSectionRva + dwSectionFOA;
            return dwFOA;
        }
    }
    return -1;
}
PIMAGE_DOS_HEADER GET_DOS(char* buf)
{
    return PIMAGE_DOS_HEADER(buf);

}
PIMAGE_NT_HEADERS GET_NT(char* buf) 
{
    return (PIMAGE_NT_HEADERS)(GET_DOS(buf)->e_lfanew+buf);
}

PIMAGE_SECTION_HEADER GET_SECTION(char* buf)
{
    PIMAGE_NT_HEADERS pNt = GET_NT(buf);
    return IMAGE_FIRST_SECTION(pNt);
};


//枚举区段表
BOOL EnumSection(char* buf) 
{
    PIMAGE_SECTION_HEADER pSection = GET_SECTION(buf);
    //解析
    for (int i = 0;i < GET_NT(buf)->FileHeader.NumberOfSections;i++)
    {
        printf("【区块NAME        】IMAMEG_NAME:%s\n", pSection[i].Name);
        printf("【装载到内存RVA        】VirtualAddress:0x%08X\n", pSection[i].VirtualAddress);
        printf("【装载到内存大小    】SizeofRawData:0x%d\n", pSection[i].SizeOfRawData);
        printf("【在磁盘文件中RVA    】PointerToRawData:0x%08X\n", pSection[i].PointerToRawData);
        printf("【属性            】Characteristice:0x%08X\n\n", pSection[i].Characteristics);
    }

    return TRUE;
}
//枚举导出表
BOOL EnumExport(char* buf)
{
    PIMAGE_NT_HEADERS pNt = GET_NT(buf);
    DWORD ExportRva = RvaToFoa(buf, pNt->OptionalHeader.DataDirectory[0].VirtualAddress);
    //                                                                    VirtualAddress 是加载到内存之后的相对地址需要对其转换
    PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(ExportRva + buf);

    //表的总大小            导出表函数总数 + 导出表名称总数
    DWORD dwSize = pExport->NumberOfFunctions + pExport->NumberOfNames;
    //地址表
    PDWORD dwAddrTab = (PDWORD)(buf + RvaToFoa(buf, pExport->AddressOfFunctions));
    //名称表
    PDWORD dwNameTab = (PDWORD)(buf + RvaToFoa(buf, pExport->AddressOfNames));
    //序号表
    PWORD dwNumTab = (PWORD)(buf + RvaToFoa(buf, pExport->AddressOfNameOrdinals));

    for (int i = 0;i < dwSize;i++) 
    {
        //                                    序号表
        for (int num = 0;num < pExport->NumberOfFunctions;num++)
        {
            //判断地址是否有效
            if (dwAddrTab[i] == 0)
            {
                continue;
            }
            //------------------------------------检测是是否有名称
            int j = 0;
            int nSign = FALSE;
            for (;j < pExport->NumberOfNames;j++)
            {
                if (i == dwNumTab[j])        //序号表里 存储名称表的下表     名称表和地址表下表是一样的
                {
                    nSign = TRUE;
                    break;
                }
            }
            //----------------------------------
            if (nSign == TRUE) 
            {
                //名称表存储的是RVA
                DWORD dwFunNameFoa = RvaToFoa(buf, dwNameTab[j]);
                char* pFunName = buf + dwFunNameFoa;
                printf("0x%08X = %s\n", dwAddrTab[i], pFunName);
                break;
            }
            else 
            {
                //什么都没找到那就是只有序号
                //printf("%x\n", dwNumTab[i]);
    
            }
        }

    }
    return TRUE;
}

int main()
{
    DWORD dwrealSize;
    // 打开文件获取句柄
    HANDLE hFile = CreateFile(path, GENERIC_ALL, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    //获取文件大小
    DWORD dwFileSize = GetFileSize(hFile, NULL);

    //申请空间并初始化
    buf = new char[dwFileSize] {0};

    //读取文件
    ReadFile(hFile, buf, dwFileSize, &dwrealSize, NULL);

    //判断是否是PE文件
    IS_PEFILE(buf);
    //枚举区段
    //EnumSection(buf);
    //枚举导出表
    EnumExport(buf);
    CloseHandle(hFile);
    delete[] buf;

}
最后修改:2021 年 03 月 01 日 05 : 23 PM