导出表

exe程序中通常会使用动态链接库dll中的函数;
dll相当于一个独立的模块,dll中的代码并不会编译到exe程序中;
这就产生了一个问题:exe怎么知道dll中的代码在什么位置;
这就需要dll提供一个清单,这个清单中能清晰说明有多少个函数、它们的名字、地址;
导出表就是这样的一个清单;
 
exe和dll都可以提供函数给其它模块使用;
如果要这么做就必须提供一个清单,也就是导出表;
一般情况下exe都没有导出表,因为exe通常不需要给其它模块提高函数,但这不是绝对的;
 
1.定位导出表
数据目录项的第一个结构,就是导出表.                    
typedef struct _IMAGE_DATA_DIRECTORY {                    
    DWORD   VirtualAddress;                    
    DWORD   Size;                    
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;         
VirtualAddress  导出表的RVA                    
Size 导出表大小      ->size只是参考,导出表有自己的方式约定自己的大小;修改了size并不影响程序;
 
2、导出表结构                                    
上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表.                                    
如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位                                    
必须要先将该RVA转换成FOA.                                    
真正的导出表结构如下:                                    
typedef struct _IMAGE_EXPORT_DIRECTORY {                                    
    DWORD   Characteristics;                // 未使用                    
    DWORD   TimeDateStamp;                // 时间戳                    
    WORD    MajorVersion;                // 未使用                    11
    WORD    MinorVersion;                // 未使用                    12
    DWORD   Name;                // 指向该导出表文件名字符串                    13
    DWORD   Base;                // 导出函数起始序号                    15
    DWORD   NumberOfFunctions;                // 所有导出函数的个数                    17
    DWORD   NumberOfNames;                // 以函数名字导出的函数个数                    
    DWORD   AddressOfFunctions;     // 导出函数地址表RVA                                    
    DWORD   AddressOfNames;         // 导出函数名称表RVA                                    
    DWORD   AddressOfNameOrdinals;  // 导出函数序号表RVA                                    
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;              
 
地址表AddressOfFunctions    ->由导出函数的个数NumberOfFunctions决定;
名字表NumberOfNames    ->由NumberOfNames决定,因为可能有的函数用隐藏函数名的方式导出;
序号表AddressOfNameOrdinals    ->和名字表一样大,也是由NumberOfNames决定;
 
导出函数有两种方式:
    1】名字导出;
        ->遍历名字表,用名字表中的地址找字符串,与目标字符串比对;
        ->找到字符串一样的之后,得到该处的索引;
        ->按照相同的索引号从序号表中找序号值;
        ->用序号值为索引,从地址表中找到目标函数的地址;
    2】序号导出;
        ->用目标序号-BASE,得到一个值;
        ->直接用这个值为索引,从地址表中找函数的地址;
        ->不需要查序号表;
    
3、AddressOfFunctions说明                               
该表中元素宽度为4个字节                                
该表中存储所有导出函数的地址                                
该表中个数由NumberOfFunctions决定                                
该表项中的值是RVA, 加上ImageBase才是函数真正的地址                                
定位:IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存储的是该表的RVA 需要先转换成FOA                                
 
4、AddressOfNames说明                
该表中元素宽度为4个字节                
该表中存储所有以名字导出函数的名字的RVA                
该表项中的值是RVA, 指向函数真正的名称                
特别说明:            
    1、函数的真正的名字在文件中位置是不确定的            
    2、但函数名称表中是按名字排序的            
    也就是说,A开头的函数在AddressOfNames排在最前面.            
    但AXXXXXX这个真正的名字,可能排在BXXXXX后面            
    3、如果想打印名字,要先将AddressOfNames转换为FOA     
 
5、AddressOfNameOrdinals说明       
该表中元素宽度为2个字节                
该表中存储的内容 + Base = 函数的导出序号  
     
 
6.总结
1)为什么要分成3张表?                                
    函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开. 
    比如有些函数可以用隐藏函数名的方式导出;
                               
2)函数地址表是不是一定大于函数名称表?                                
    未必,一个相同的函数地址,可能有多个不同的名字.  
 
3)如何根据函数的名字获取一个函数的地址?                
 
4)如何根据函数的导出序号获取一个函数的地址?                
 
7.打印导出表信息
RVA转FOA:
//将内存偏移转换为文件偏移                                                                
DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva){
    //定义文件头
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos头指针
    PIMAGE_NT_HEADERS ntHeader = NULL;        //nt头指针
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe头指针
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可选pe头指针
    PIMAGE_SECTION_HEADER sectionHeader = NULL;    //节表指针
 
    //找到文件头
    dosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    ntHeader = (PIMAGE_NT_HEADERS) ((DWORD)pFileBuffer + dosHeader->e_lfanew);
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)ntHeader + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    sectionHeader = (PIMAGE_SECTION_HEADER) ((DWORD)opHeader + peHeader->SizeOfOptionalHeader);
 
    //1.判断是哪个节
    int sec = -1;
    for(int i=0;i<peHeader->NumberOfSections;i++){
        DWORD va = (sectionHeader+i)->VirtualAddress;
        DWORD size = (sectionHeader+i)->Misc.VirtualSize;
        if(dwRva >= va && dwRva<=(va+size) ){
            sec = i;
            printf("在第%d个节\n",sec);
            break;
        }    
    }
    if(sec<0){
        printf("内存偏移不在任何一个节\n");
        return 0;
    }
 
    //2.转换
    DWORD secOffset = dwRva - (sectionHeader + sec)->VirtualAddress;
    DWORD foa = (sectionHeader + sec)->PointerToRawData + secOffset;
 
    return foa;
}
 
输出导出表:
#include "stdafx.h"
#include "PeTool.h"
 
#define SRC "C:\\Users\\Administrator\\Desktop\\DllHello.dll"
 
void printExportDir(){
    //定义头结构指针
    PIMAGE_DOS_HEADER dosHeader = NULL;        //dos头指针
    PIMAGE_FILE_HEADER peHeader = NULL;        //pe头指针
    PIMAGE_OPTIONAL_HEADER32 opHeader = NULL;    //可选pe头指针
    PIMAGE_DATA_DIRECTORY dataDir = NULL;        //数据目录指针
    PIMAGE_EXPORT_DIRECTORY exportDir = NULL;    //导出表指针
 
    //1.读取文件到缓冲区
    LPVOID pFileBuffer = NULL;
    DWORD fileSize = ReadPEFile(SRC, &pFileBuffer);
    if(!fileSize){
        printf("读取文件失败\n");
        return;
    }
 
    //2.初始化头指针
    dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
    peHeader = (PIMAGE_FILE_HEADER) ((DWORD)dosHeader + dosHeader->e_lfanew + 4);
    opHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
    dataDir = opHeader->DataDirectory;
    exportDir = (PIMAGE_EXPORT_DIRECTORY) ((DWORD)pFileBuffer + RvaToFileOffset(pFileBuffer,dataDir->VirtualAddress));
    if(!exportDir){
        printf("该文件没有导出表\n");
        free(pFileBuffer);
        return;
    }
    //3.输出信息
    LPSTR expName = (LPSTR) ((DWORD)pFileBuffer + exportDir->Name);    //注意导出表中的name保存的是:dll名的地址相对于首地址的偏移
    printf("导出表文件名Name:%s\n",expName);
    printf("导出函数起始序号Base:%d\n",exportDir->Base);
    printf("所有导出函数的个数NumberOfFunctions:%d\n",exportDir->NumberOfFunctions);
    printf("以函数名导出的函数个数NumberOfNames:%d\n",exportDir->NumberOfNames);
    printf("地址表内存偏移AddressOfFunctions:%x\n",exportDir->AddressOfFunctions);
    printf("名字表内存偏移AddressOfNames:%x\n",exportDir->AddressOfNames);
    printf("序号表内存偏移AddressOfNameOrdinals:%x\n",exportDir->AddressOfNameOrdinals);
 
    printf("***************地址表*********************\n");
    LPDWORD pFunAddr = (LPDWORD)(exportDir->AddressOfFunctions);
    for(int i=0;i<exportDir->NumberOfFunctions;i++){
        DWORD funAddr = *(LPDWORD)((DWORD)pFileBuffer + (DWORD)(pFunAddr+i));
        printf("函数地址[%d]-%x:%x\n", i, pFunAddr+i, funAddr);    
    }
    printf("*************名字表****************\n");
    LPDWORD pNameAddr = (LPDWORD)((DWORD)pFileBuffer + exportDir->AddressOfNames);
    for(int j=0;j<exportDir->NumberOfNames;j++){
        DWORD nameOffset = *(pNameAddr + j);
        LPSTR funName = (LPSTR)((DWORD)pFileBuffer + nameOffset);
        printf("函数名[%d]-%x:%s\n", j, nameOffset, funName);
    }
    printf("*********序号表***************\n");
    LPWORD pOrdinals = (LPWORD) (exportDir->AddressOfNameOrdinals);
    for(int l=0;l<exportDir->NumberOfNames;l++){
        WORD ord = ((LPWORD)((DWORD)pFileBuffer + (DWORD)pOrdinals))[l];
        printf("序号[%d]-%x:%d\n",l, pOrdinals+l,ord);
    }
    
    //释放内存
    free(pFileBuffer);
 
}
 
int main(int argc, char* argv[])
{
    printExportDir();
    getchar();
}
 
 
结果:
       
 
  
 
posted @ 2019-10-31 16:40  L丶银甲闪闪  阅读(883)  评论(0编辑  收藏  举报