PE解释器之PE文件结构(二)

接下来的内容是对IMAGE_OPTIONAL_HEADER32中的最后一个成员DataDirectory,虽然他只是一个结构体数组,每个结构体的大小也不过是个字节,但是它却是PE文件中最重要的成员。PE装载器通过查看它才能准确的找到某个函数或某个资源。

一:IMAGE_DATA_DIRECTORY——数据目录结构

typedef struct _IMAGE_DATA_DIRECTORY {DWORD VirtualAddress;                   /**指向某个数据的相对虚拟地址   RAV  偏移0x00**/DWORD Size;                             /**某个数据块的大小                 偏移0x04**/
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

此数据目录表结构中有俩个成员VirtualAddress和Size,这俩成员的含义比较简单,前者指定了数据块的相对虚拟地址(RVA)Size则指定了该数据块的大小,有时并不是该类型数据的总大小,可能只是该数据类型一个数据项的大小。这俩成员成为定位各种表的关键,所以一定要了解知道每个数组元素所指向的数据类型,请看下表:

//定位目录项的方法(以导出表为例):    所有操作都在FileBuffer状态下完成//1、指向相关内容
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)(FileAddress);
PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pFileHeader + sizeof(IMAGE_FILE_HEADER));//2、获取导出表的地址(目录项的第0个成员)
DWORD ExportDirectory_RAVAdd = pOptionalHeader->DataDirectory[0].VirtualAddress;
DWORD ExportDirectory_FOAAdd = 0;
//    (1)、判断导出表是否存在
if (ExportDirectory_RAVAdd == 0)
{printf("ExportDirectory 不存在!\n");return ret;
}
//    (2)、获取导出表的FOA地址    转换函数看上一章作业提示
ret = RVA_TO_FOA(FileAddress, ExportDirectory_RAVAdd, &ExportDirectory_FOAAdd);
if (ret != 0)
{printf("func RVA_TO_FOA() Error!\n");return ret;
}//3、指向导出表
PIMAGE_EXPORT_DIRECTORY ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)FileAddress + ExportDirectory_FOAAdd);

二:IMAGE_EXPORT_DIRECTORY——导出表


typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD   Characteristics;        //         未使用,总为0DWORD   TimeDateStamp;          //         文件创建时间戳WORD    MajorVersion;           //         未使用,总为0WORD    MinorVersion;           //         未使用,总为0DWORD   Name;                   // **重要   指向一个代表此 DLL名字的 ASCII字符串的 RVADWORD   Base;                   // **重要   函数的起始序号DWORD   NumberOfFunctions;      // **重要   导出函数地址表的个数DWORD   NumberOfNames;          // **重要   以函数名字导出的函数个数DWORD   AddressOfFunctions;     // **重要   导出函数地址表RVADWORD   AddressOfNames;         // **重要   导出函数名称表RVADWORD   AddressOfNameOrdinals;  // **重要   导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

导出表由前面的目录表找到相对应的位置,其中导出表是什么?


导出表包含以下主要信息:
1. **导出函数的名称和地址: 列出了该可执行文件中导出的函数或符号的名称和相应的内存地址。
2. **导出函数的序号:每个导出函数分配的唯一序号,方便通过序号进行引用。
3. **导出函数的起始地址:指定导出函数的实际执行代码在内存中的起始地址。
4. **导出函数的外部名称:如果函数具有外部别名,该别名也会在导出表中记录。
导出表在动态链接库(DLL)中尤为重要,因为其他程序或模块可以通过导出表来动态链接到DLL中的函数,实现模块间的交互和共享代码。对于PE文件的分析和调试,导出表是一个关键的信息来源,可用于理解文件的功能和与其他模块的交互关系。

导出表我们需要注意标注的地方

AddressOfFunctions
  这个值是一个4字节的RVA地址,他可以用来定位导出表中所有函数的地址表,这个地址表可以当作一个成员宽度为4的数组进行处理,它的长度由NumberOfFunctions进行限定,地址表中的成员也是一个RVA地址,在内存中加上ImageBase后才是函数真正的地址。
  AddressOfNames
  这个值是一个4字节的RVA地址,他可以用来定位导出表中所有函数的名称表,这个名称表也可以当作一个成员宽度为4的数组进行处理,它的长度由NumberOfNames进行限定,名称表的成员也是一个RVA地址,在FIleBuffer状态下需要进行RVA到FOA的转换才能真正找到函数名称。
  AddressOfNameOrdinals
  这个值是一个4字节的RVA地址,他可以用来定位导出表中所有函数的序号表,这个序号表可以当作一个成员宽度为2的数组进行处理,它的长度由NumberOfNames进行限定,名称表的成员是一个函数序号,该序号用于通过名称获取函数地址。
  NumberOfFunctions
  注意,这个值并不是真的函数数量,他是通过函数序号表中最大的序号减去最小的序号再加上一得到的,例如:一共导出了3个函数,序号分别是:0、2、4,NumberOfFunctions = 4 - 0 + 1 = 5个。

导出表结构图:

通过导出表查找函数地址的两种方法:

  1、通过函数名查找函数地址:

               (1)、首先定位函数名表,然后通过函数名表中的RVA地址定位函数名,通过比对函数名获取目标函数名的在函数名表中的索引。
    (2)、通过获取函数名表的索引获取函数序号表中对应索引中的函数序号。
    (3)、通过把该序号当作函数地址表的下标,就可以得到该下标中的函数地址。

  2、通过函数序号查找函数地址:

(1)首先计算出函数地址表的索引:index = 目标函数的函数序号 - 导出表的基地址(Base).

  (2)   通过计算出的索引就可以在函数地址表中获取到目标序号的函数地址

注意:相比于通过函数名字,通过序号获取函数地址不需要使用函数名称表和函数序号表就可以直接获取函数地址,实现上相对来说比较方便。

三:IMAGE_BASE_RELOCATION——重定位表

typedef struct _IMAGE_BASE_RELOCATION {DWORD   VirtualAddress;            重定位数据所在页的RVADWORD   SizeOfBlock;               当前页中重定位数据块的大小
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

        重定位表简介:正如我们知道的,在程序运行时系统首先会给程序分配一个4gb的虚拟内存空间,低2g空间用于存放EXE文件和DLL文件,高2g空间则是用于取得程序使用。系统随后就会将EXE文件第一个贴入低2g空间占据文件指定的imageBase,所以EXE文件有时会木有重定位表。贴完EXE文件后接下来就会将大量程序使用的DLL文件贴入虚拟空间,然后这些dll文件和imagebase可能发生冲突,所以有些dll文件不能贴入指定的地址,但是为了让程序正常运行,需要重新给它分配一个地址,由此产生重定位表。

        重定位表就是记录这些需要修正的地址,在imagebase发生改变时就会就行修正重定位表

修正方法:

        需要重定位的地址 - 以前的基址 + 当前的基址

 VirtualAddress
  这个虚拟地址是一组重定位数据的开始RVA地址,只有重定位项的有效数据加上这个值才是重定位数据真正的RVA地址。
  SizeOfBlock
  它是当前重定位块的总大小,因为VirtualAddress和SizeOfBlock都是4字节的,所以(SizeOfBlock - 8)才是该块所有重定位项的大小,(SizeOfBlock - 8) / 2就是该块所有重定位项的数目。
  重定位项
  重定位项在该结构中没有体现出来,他的位置是紧挨着这个结构的,可以把他当作一个数组,宽度为2字节,每一个重定位项分为两个部分:高4位和低12位。高4位表示了重定位数据的类型(0x00没有任何作用仅仅用作数据填充,为了4字节对齐。0x03表示这个数据是重定位数据,需要修正。0x0A出现在64位程序中,也是需要修正的地址),低12位就是重定位数据相对于VirtualAddress的偏移,也就是上面所说的有效数据。之所以是12位,是因为12位的大小足够表示该块中的所有地址(每一个数据块表示一个页中的所有重定位数据,一个页的大小位0x1000)。

注:如果修改了EXE文件的ImageBase,就要手动修复它的重定位表,因为系统会判断程序载入地址和ImageBase是否一致,如果一致就不会自动修复重定位表,双击运行时就会报错。

重定位表结构:

通过重定位表找到需要修正的数据:

四:IMAGE_IMPORT_DESCRIPTOR——导入表

typedef struct _IMAGE_IMPORT_DESCRIPTOR {union {DWORD   Characteristics;DWORD   OriginalFirstThunk;             //导入名称表(INT)的RVA地址} DUMMYUNIONNAME;DWORD   TimeDateStamp;                      //时间戳多数情况可忽略  如果是0xFFFFFFFF表示IAT表被绑定为函数地址DWORD   ForwarderChain;DWORD   Name;                               //导入DLL文件名的RVA地址DWORD   FirstThunk;                         //导入地址表(IAT)的RVA地址
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

         

导入表简介:PE文件使用来自于其他DLL的代码或数据是,称作导入(或者输入)。当PE文件装入时,Windows装载器的工作之一就是定位所有被输入的函数和数据,并且让正在被装入的问渐渐可以使用这些地址。这个过程就是通过PE文件的导入表来完成的,导入表中保存的是函数名和其驻留的DLL名等动态链接所需的信息。

  OriginalFirstThunk
  这个值是一个4字节的RVA地址,这个地址指向了导入名称表(INT),INT是一个IMAGE_THUNK_DATA结构体数组,这个结构体的最后一个成员内容为0时数组结束。这个数组的每一个成员又指向了一个IMAGE_IMPORT_BY_NAME结构体,这个结构体包含了两个成员函数序号和函数名,不过这个序号一般没什么用,所以有的编译器会把函数序号置0。函数名可以当作一个以0结尾的字符串。(注:这个表不在目录项中。)

  Name
  DLL名字的指针,是一个RVA地址,指向了一个以0结尾的ASCII字符串。

  FirstThunk
  这个值是一个4字节的RVA地址,这个地址指向了导入地址表(IAT),这个IAT和INT一样,也是一个IMAGE_THUNK_DATA结构体数组,不过它在程序载入前和载入后由两种状态,在程序载入前它的结构和内容和INT表完全一样,但却是两个不同的表,指向了IMAGE_IMPORT_BY_NAME结构体。在程序载入后,他的结构和INT表一样,但内容就不一样了,里面存放的都是导入函数的地址。(注:这个表在目录项中,需要注意。)

EXE文件载入后对应的导入表结构图:

IMAGE_THUNK_DATA——INT、IAT的结构体定义如下:typedef struct _IMAGE_THUNK_DATA32 {union {DWORD ForwarderString;      // PBYTEDWORD Function;             // PDWORDDWORD Ordinal;DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;//注:这个结构体是联合类型的,每一个成员都是4字节,所以为了编程方便,完全可以用一个4字节的数组取代它。IMAGE_IMPORT_BY_NAME 结构体定义如下:typedef struct _IMAGE_IMPORT_BY_NAME {WORD    Hint;CHAR   Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;//注:这个结构体由两个成员组成,大致一看它的大小是3个字节,其实它的大小是不固定的,
//    因为无法判断函数名的长度,所以最后一个成员是一个以0结尾的字符串。

 EXE文件载入前对应的导入表结构图:

 五:IMAGE_BOUND_IMPORT_DESCRIPTOR——绑定导入表

IMAGE_BOUND_IMPORT_DESCRIPTOR的结构体定义如下:typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {DWORD   TimeDateStamp;                    //时间戳WORD    OffsetModuleName;                 //DLL名的地址偏移WORD    NumberOfModuleForwarderRefs;      //该结构后IMAGE_BOUND_FORWARDER_REF数组的数量
// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR,  *PIMAGE_BOUND_IMPORT_DESCRIPTOR;

 

绑定导入表简介:绑定导入是一个文件快速启动的技术,但是只能起到辅助的效果,它的存在只会影响到PE文件的加载过程,并不会影响PE文件的运行结果,这也就是说把绑定导入的信息从PE文件中清除后对这个PE文件的运行结果没有任何影响。从导入表部分我们可以知道,FirstThunk这个成员指向了IAT表,在程序加载时加载器会通过INT表来修复IAT表,使里面存放上对应函数的地址信息,但是如果导入的函数太多在加载过程中就会使程序启动变慢,绑定导入就是为了减少IAT表的修复时间。它会在程序加载前修复IAT表,然后在PE文件中声明绑定导入的数据信息,让操作系统知道这些事情已经提前完成。这就是绑定导入表的作用。

  TimeDateStamp
  这个时间戳相对来说还是比较重要的,因为这个值只有和导入DLL的IMAGE_FILE_HEADER中的TimeDateStamp值相同才能起到绑定导入的效果,如果不一致加载器就会重新计算IAT表中的函数地址。(由于DLL文件的版本不同或者DLL文件的ImageBase被重定位时,IAT绑定的函数的地址就会发生变化)

  OffsetModuleName
  这个偏移不是RVA页不是FOA,所以模块名的定位与之前的方法不同,它的定位方式是以第一个IMAGE_BOUND_IMPORT_DESCRIPTOR的地址为基址,加上OffsetModuleName的值就是模块名所在的地址了,这个模块名是以0结尾的ASCII字符串。

  NumberOfModuleForwarderRefs
  这个值是在IMAGE_BOUND_IMPORT_DESCRIPTOR结构后跟随的IMAGE_BOUND_FORWARDER_REF结构的数量。在每一个IMAGE_BOUND_IMPORT_DESCRIPTOR结构后都会跟随着大于等于0个IMAGE_BOUND_FORWARDER_REF结构,然后在其后面又会跟上绑定表结构体,直至全部用0填充的绑定表结构。

IMAGE_BOUND_IMPORT_DESCRIPTOR的结构体定义如下:typedef struct _IMAGE_BOUND_FORWARDER_REF {DWORD   TimeDateStamp;               //时间戳WORD    OffsetModuleName;            //DLL名的地址偏移WORD    Reserved;                    //保留
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
//注:
//    该结构中的成员和绑定导入表的成员含含义一致,所以不再过多叙述。
//    由于IMAGE_BOUND_IMPORT_DESCRIPTOR和IMAGE_BOUND_FORWARDER_REF的大小结构相同,所以可以相互转型,方便编程。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/241781.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt学习:实战 读取txt文件+定时器点名

目录 目标 步骤 头文件 配置ui界面 在.h里定义槽函数和字符串链表和定时器指针 在构造函数里读取txt文件并初始化定时器 开始按钮点击函数 开始定时器 停止按钮点击函数 关闭定时器 定时器槽函数 目标 两个按钮,一个开始点名,一个停止点名一个…

用Go plan9汇编实现斐波那契数列计算

斐波那契数列是一个满足递推关系的数列,如:1 1 2 3 5 8 ... 其前两项为1,第3项开始,每一项都是其前两项之和。 用Go实现一个简单的斐波那契计算逻辑 func fib(n int) int {if n 1 || n 2 {return 1}return fib(n-1) fib(n-2) …

C# 获取QQ会话聊天信息

目录 利用UIAutomation获取QQ会话聊天信息 效果 代码 目前遇到一个问题 其他解决办法 利用UIAutomation获取QQ会话聊天信息 效果 代码 AutomationElement window AutomationElement.FromHandle(get.WindowHwnd); AutomationElement QQMsgList window.FindFirst(Tr…

【算法分析与设计】H指数

📝个人主页:五敷有你 🔥系列专栏:并发编程 ⛺️稳中求进,晒太阳 题目 给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维…

网络原理--http

目录 一、 DNS(应用层协议) 1、域名概念 2、维护ip地址和域名之间的映射(域名解析系统) 3、DNS系统(服务器) 4、如何解决DNS服务器高并发问题 二、HTTP(应用层协议) 1、htt…

Redis实战之-分布式锁-redission

一、分布式锁-redission功能介绍 基于setnx实现的分布式锁存在下面的问题: 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都…

ROS2手册的离线编译安装

ROS开发中经常要查询相关API,把文档下载到本地离线使用方便快捷,极大提高开发效率 下载ROS2文档 git clone https://github.com/ros2/ros2_documentation.gitcd ros2_documentation安装sphinx pip install Sphinx配置sphinx sphinx-quickstart按提示…

DolphinScheduler-3.2.0集群部署教程

本文目录 1.集群部署方案(2 Master 3 Worker)2.前置准备工作3.端口说明4.DS集群部署1.时间同步2.配置用户、权限3.配置集群免密登陆4.ZK集群启动5.初始化数据库1.创建数据库、用户、授权2.解压缩安装包3.添加MySQL驱动至libs目录 6.配置文件修改1.dolphinscheduler_env.sh 配置…

DBA技术栈MongoDB:简介

1.1 什么是MongoDB? MongoDB是一个可扩展、开源、表结构自由、用C语言编写且面向文档的数据库,旨在为Web应用程序提供高性能、高可用性且易扩展的数据存储解决方案。 MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当…

Debian 11.8.0 安装图解

引导和开始安装 这里直接回车确认即可,选择图形化安装方式。 选择语言 这里要区分一下,当前选中的语言作为安装过程中安装器所使用的语言,这里我们选择中文简体。不过细心的同学可能发现,当你选择安装器语言之后,后续安…

阿里云ECS(CentOS镜像)安装docker

目录 1.前置条件 2.连接至ECS 3.yum软件包更新 4.安装docker前置所需软件包 5.添加docker 官方的 yum 软件源 6.安装docker 7.检测是否成功 8.配置阿里云镜像加速器 1.前置条件 在看本文前保证未安装过docker,或者安装过但是清理干净 如果多次安装失败过,…

【机组】算术逻辑单元带进位运算实验的解密与实战

​🌈个人主页:Sarapines Programmer🔥 系列专栏:《机组 | 模块单元实验》⏰诗赋清音:云生高巅梦远游, 星光点缀碧海愁。 山川深邃情难晤, 剑气凌云志自修。 ​ 目录 🌺一、 实验目…

小白水平理解面试经典题目LeetCode 121 Best Time to Buy and Sell Stock

121 Best Time to Buy and Sell Stock (买卖股票的最佳时机) 你好,2024年的第一个月,又是秋风萧瑟天气凉,草木摇落露为霜。.。。在这个特殊的时代,作为我们普通的一个打工人,我们用这道题,开启对这个不符合…

DolphinDB学习(0):DolphinDB基本概述

DolphinDB的学习难度不小,主要是写法比较多,官方示例是一次性给一大堆代码,在没有成体系的学习基础的前提下,总有种力不从心的感觉,所以博主汇总这一个系列的文章,尝试从最简单的基础常规操作开始&#xff…

ASP.NET Core 对象池化技术

写在前面 Microsoft.Extensions.ObjectPool 是 ASP.NET Core 基础结构的一部分,当对象的初始化成本较高,并且可能被频繁使用时,才适合采用对象池技术;被ObjectPool管理的对象不会进入垃圾回收,使用时通过由实例对象实…

自动化测试:5分钟了解Selenium以及如何提升自动化测试的效果

在快节奏的技术世界里,自动化测试已经成为确保 Web 应用程序质量和性能的重要手段。自动化测试不仅加快了测试过程,还提高了测试的重复性和准确性。Selenium,作为领先的自动化测试工具之一,为测试人员提供了强大的功能来模拟用户在…

采集B站up主视频信息

一、网页信息(示例网址:https://space.bilibili.com/3493110839511225/video)

2018年认证杯SPSSPRO杯数学建模A题(第二阶段)海豚与沙丁鱼全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 基于聚类分析的海豚捕食合作策略 A题 海豚与沙丁鱼 原题再现: 沙丁鱼以聚成大群的方式来对抗海豚的捕食。由于水下光线很暗,所以在距离较远时,海豚只能使用回声定位方法来判断鱼群的整体位置,难…

多维时序 | Matlab实现CNN-BiLSTM-Mutilhead-Attention卷积双向长短期记忆神经网络融合多头注意力机制多变量时间序列预测

多维时序 | Matlab实现CNN-BiLSTM-Mutilhead-Attention卷积双向长短期记忆神经网络融合多头注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现CNN-BiLSTM-Mutilhead-Attention卷积双向长短期记忆神经网络融合多头注意力机制多变量时间序列预测效果一览基本介绍程序设计…

vue中引入sass、scss

常规步骤 1. 创建项目 使用vue cli 脚手架工具创建项目 vue create xxxx2. 创建全局样式文件 全局样式变量 路径:/assets/styles/variables.scss //flex 布局变量 $--flex-direction: ("row", "column"); $--flex-position: ("start"…