zoukankan      html  css  js  c++  java
  • PE 导出表

    知识点:

    1、exe程序一般只有导入表,但并不是一定,有可能也有导出表
    2、dll程序一般导出表和导入表都有

    一个可执行程序是由一堆PE文件构成的!

    如下图可解释:

    我加载的是一个.exe,但是后面还有需要的.dll文件,大家都知道.dll也有PE文件,这里也就又有一个问题了,为什么要引入这么多的.dll文件呢?

    因为一个exe还需要使用这些.dll中所提供的函数,这些dll中就有相应的导出表,然后exe用LoadLibrary动态加载,最后通过GetProcAddress到获取函数的地址!


    导出表的结构

    在扩展PE头是一个名为_IMAGE_OPTIONAL_HEADER的结构体

    其中存在一个结构体数组为IMAGE_DATA_DIRECTORY,个数有16个,总占128字节

    其中关于导出表的结构体的名称为:导出表IMAGE_DIRECTORY_ENTRY_EXPORT

    该结构体数组中第一个成员结构体如下:

    typedef struct _IMAGE_DATA_DIRECTORY {
        DWORD   VirtualAddress; //虚拟地址,存储当前导出表的地址
        DWORD   Size; //存储 当前导出表的大小
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
    

    那么蓝颜色的就是该结构体数组

    那么IMAGE_DIRECTORY_ENTRY_EXPORT的结构体就是前八个字节,如下:

    存储导出表的结构体 80 AD 02 00 78 01 00 00

    存储导出表的虚拟地址为:0002AD80

    存储导出表的大小为:00000178

    这里的文件对齐和内存对齐的大小是一样的

    注意:如果不一样还需要先将RVA地址转换为FOA的地址

    导出表的结构体如下:

    typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics; //未使用
        DWORD   TimeDateStamp;  //时间戳
        WORD    MajorVersion;  //未使用
        WORD    MinorVersion;  //未使用
        DWORD   Name; //指向该导出表文件名字符串
        DWORD   Base; //导出函数起始序号
        DWORD   NumberOfFunctions; //所有导出函数的个数
        DWORD   NumberOfNames; //以函数名字导出的函数个数
        DWORD   AddressOfFunctions;     // 子表:导出函数地址表RVA
        DWORD   AddressOfNames;         // 子表:导出函数名称表RVA
        DWORD   AddressOfNameOrdinals;  // 子表:导出函数序号表RVA
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    

    该结构体占103个字节,但是现在只看到了40字节,那还有63个字节呢?

    其实这里面还有几个成员是指针,比如:

    Name:指向的地址是该导出表文件的字符串

    AddressOfFunctions:子表,导出函数地址表 RVA

    AddressOfNames:子表,导出函数名称表 RVA

    AddressOfNameOrdinals:子表,导出函数序号表 RVA

    所以可以认为IMAGE_DIRECTORY_ENTRY_EXPORT中的Size可以忽视了

    那么现在跟到0x0002AD80中查看_IMAGE_EXPORT_DIRECTORY导出表的结构体:

    Name为如下:CE AD 02 00

    NumberOfFunctions:所有导出函数的个数有五个

    NumberOfNames:以函数名字导出的函数个数三个

    AddressOfFunctions:导出函数地址表 RVA

    AddressOfNames:导出函数名称表 RVA

    AddressOfNameOrdinals:导出函数序号表 RVA

    理解图:

    如何寻找导出函数的地址

    第一种寻找导出的函数地址的方法: 通过导出函数名称表,再对应到导出函数序号表,最后从导出函数地址表中寻找!

    如下图对应:

    实现代码:

    	//1、导出函数名称表来寻找导出函数地址表,AddressOfNames是一个指向函数名称的地址
    	RVA_TO_FOA(pFileBuffer,pExportDirectory->AddressOfNames,&FOA);
    	//printf("pExportDirectory->AddressOfNames导出函数名称表: 0x%x
    ",FOA);
    
    	//2、再加上pFileBuffer,转换为文件地址,得到函数名称存储的地方的首地址,当前的首地址是内存中的地址,也需要进行转换
    	AddressOfNamesTable = (PVOID)(*(PDWORD)((DWORD)pFileBuffer+(DWORD)FOA));
    	RVA_TO_FOA(pFileBuffer,(DWORD)AddressOfNamesTable,&FOA);
    	AddressOfNamesTable = (PVOID)FOA;
    	AddressOfNamesTable = (PVOID)((DWORD)pFileBuffer + (DWORD)AddressOfNamesTable);
    	printf("
    ");
    	printf("
    ");
    	//3、得到函数名称表的文件地址,每个函数的名称 占四个字节,然后进行遍历判断
    	for(j=0;j<pExportDirectory->NumberOfNames;j++){
    		//(PDWORD)((DWORD)AddressOfNamesTable + 4*j);
    		//获取当前函数名称表中的函数名称,然后循环判断
    		strcpy(FunName,(PVOID)((DWORD)AddressOfNamesTable + (strlen(AddressOfNamesTable)+1)*j));
    		if(0 == memcmp((PDWORD)((DWORD)AddressOfNamesTable + (DWORD)(strlen(AddressOfNamesTable)+1)*j),(PDWORD)FunName,strlen(FunName))){
    			//4、找到序号表AddressOfNameOrdinals下标所对应的的值,序号表中每个成员占2字节 word类型
    			RVA_TO_FOA(pFileBuffer,pExportDirectory->AddressOfNameOrdinals,&FOA);
    			AddressOfNameOrdinalsNumber = *(PWORD)((DWORD)FOA + (DWORD)pFileBuffer + (DWORD)j*2);
    			//5、通过序号表中下标对用的值去导出函数地址表AddressOfFunctions中寻找 该值下标对应的值
    			RVA_TO_FOA(pFileBuffer,pExportDirectory->AddressOfFunctions,&FOA);
    			printf("函数序号: %d	",AddressOfNameOrdinalsNumber);
    			printf("函数名称为: %s	",FunName);
    			printf("导出函数地址表的地址为:0x%.8x
    ",*(PDWORD)(PVOID)((DWORD)FOA + (DWORD)pFileBuffer + AddressOfNameOrdinalsNumber*4));
    		}
    	}
    

    第二种寻找 导出的函数地址 的方法: 通过导出函数序号表

    导出函数序号表中的值 减去 _IMAGE_EXPORT_DIRECTORY中base的值 然后从导出函数地址表的下标寻找!

  • 相关阅读:
    elasticsearch 安装,以及遇到的问题总结
    Linux/unix 查看端口占用
    openresty 安装
    Hadoop 系列文章(三) 配置部署启动YARN及在YARN上运行MapReduce程序
    Hadoop 系列文章(二) Hadoop配置部署启动HDFS及本地模式运行MapReduce
    Hadoop 系列文章(一) Hadoop 的安装,以及 Standalone Operation 的启动模式测试
    Linux 防火墙相关
    Linux 修改默认的 yum 源
    普通用户开放 sudo 权限
    大数据所有环境变量
  • 原文地址:https://www.cnblogs.com/zpchcbd/p/12323820.html
Copyright © 2011-2022 走看看