从PE结构到LoadLibrary

从PE结构到LoadLibrary

PE是Windows平台主流可执行文件格式,.exe , .dll, .sys, .com文件都是PE格式

32位的PE文件称为PE32,64位的称为PE32+,PE文件格式在winnt.h头中有着详细的定义,PE文件头包含了一个程序在运行时需要的所有信息,包括了如何将文件加载到内存、开辟多大的堆栈空间、调用哪些DLL以及相关函数、从何处开始运行,这些信息都以结构体的形式存储在PE头中

PE文件包括四个组成部分:

  • MS-DOS(Disk Operation System) 头

  • NT头(New Technology)

  • Section table表中包含了所有的 section头

  • 所有的section实体(段实体)

image-20241022172019315

实现LoadLibrary,即把PE文件加载到内存中需要经过四步

  • 判定输入文件是否是PE格式

  • 将PE文件按照内存映像结构分块放在内存中

  • 在IAT(导入地址表)中,填入其依赖的导入函数地址

  • 利用重定位表修复需要重定位的值

LoadLibrary 是 Windows API 中的一个函数,用于动态加载 DLL文件到当前进程的地址空间,并返回一个句柄以供后续操作。

1 检查PE格式

PE文件加载,首先要检查的是该文件是否为PE格式,还需要检查该PE文件是否为DLL

  • 对于PE格式的检测,需要检查的部分是MS-DOS头中“MZ” 关键字和NT头中“PE/0/0”关键字
  • 对于DLL的检测,则需要检查NT头中的IMAGE_FILE_HEADER的Characteristics字段下IMAGE_FILE_DLL信息位
  1. MS-DOS头

    MS-DOS头是微软为了考虑PE文件对DOS文件的兼容性而添加的。 大多数情况下由编译器自动生成,通常把DOS MZ头与DOS stub(模拟对象接口)合称为DOS文件头,IMAGE_DOS_HEADER结构体如下图 ,MAGE_DOS_HEADER结构体共64字节,其中两个字段比较重要,分别是e_magic和e_lfanew

    image-20241022210054943

    e_magic需要被设置为0x5A4D,其ASCII值为“MZ” ,为DOS签名,标志着DOS头的开始

    e_lfanew字段是NT头的相对偏移,其指出NT头的文件偏移位置,共占用四个字节,位于文件开始偏移 0x3C字节中

  2. NT头

    在DOS stub后的是NT头 (IMAGE_NT_HEADERS)

    image-20241022210227149

    Signature 在一个有效的PE文件中,Signature字段必须被设置为 0x00004550,对应于ASCII字符“PE\0\0” 。

    image-20241022210254349

    IMAGE_FILE_HEADER IMAGE_FILE_HEADER结构包含了PE文件的基本信息,最重要的是其中一个字段指出了IMAGE_OPTIONAL_HEADER 的大小

    image-20241022211709827

    其中Machine,NumberOfSections, SizeOfOptionalHeader,Characteristics如果出现错误, 将导致该PE文件无法正常执行

    1. Machine 该字段说明了可执行文件的目标CPU类型,每类CPU都有唯一的Machine码
    image-20241022211802044
    1. NumberOfSections 该字段说明了在这个PE文件中节区(Section)的数目
    2. TimeDateStamp 该字段说明了该PE文件是何时被创建的
    3. PointerToSymbolTable 该字段说明了COFF符号表(基本用不到)的文件偏移位置, COFF符号表在PE文件中较为少见,通常其值为0
    4. NumberOfSymbols 如果存在COFF符号表,该字段说明了其中的符号数目
    5. SizeOfOptionalHeader 该字段说明了紧跟在IMAGE_FILE_HEADER后的数据大小 对于32位文件,该字段通常为0x00E0,对于64位文件, 该字段通常为0x00F0
    6. Characteristics 该字段用于标识文件的属性,文件是否可执行,是否为 DLL等文件信息,这些信息以比特位的方式组合起来

    IMAGE_OPTIONAL_HEADER:

    IMAGE_OPTIONAL_HEADER虽然叫做可选头,但是仅有IMAGE_FILE_HEADER并不足以定义PE文件的属性 IMAGE_OPTIONAL_HEADER定义了更多的PE文件的属性,两者结合起来描述了一个完整的PE文件

    1. Magic 当IMAGE_OPTIONAL_HEADER为IMAGE_OPTIONAL_HEADER32 (32位)时,Magic为0x10B。当其为 IMAGE_OPTIONAL_HEADER64时,Magic为0x20B。
    2. AddressOfEntryPoint 该字段的值为相对虚拟地址(加载到内存的地址),该值表明了程序最先执行的代码的启始地址,即程序入口点。
    3. ImageBase 该字段表明了PE文件被加载进内存时,文件将被优先装入的虚拟内存的地址。对于EXE来说,ImageBase通常为0x00400000; 对于DLL来说,ImageBase通常为0x10000000。装载后,EIP = ImageBase + AddressOfEntryPoint。
    4. SectionAlignmentFileAlignment PE文件的Body部分划分为若干节区,FileAlignment制定了节区在文件系统中的最小单位,SectionAlignment则指定了节区在内存中的最小单位,磁盘文件或内存的节区大小必定为FileAlignment或SectionAlignment的整数倍
    5. SizeOfImage 加载PE文件时,SizeOfImage指定了PEImage在虚拟内存中所占的空间大小
    6. SizeOfHeaders 该字段表明了整个PE文件头部的大小,该值必须是 FileAlignment的整数倍
    7. Subsystem 该字段用于区分系统驱动文件与普通可执行文件。
    8. NumberOfRvaAndSizes 该字段表明了下面出现的DataDirectory数组的个数。一般来说该值为16。
    9. DataDirectory 由IMAGE_DATA_DIRECTORY结构体构成的数组,数组的每项都有不同的意义
  3. PE格式检查:

    PE格式检查主要针对于MS-DOS头和NT头。要求MS- DOS头和NT头的签名与规定相同。其中MS-DOS头的签名为0x4D5A即ASCII码的”MZ” 。

    通过MS-DOS头中的e_lfanew成员变量找到NT头。检查NT头签名为0x50450000即ASCII的”PE\0\0”

    根据NT头中FileHeader中的Characteristics中的 IMAGE_FILE_DLL位可以判断该PE文件是否为DLL。

2 内存映像结构

将PE文件按照内存映像结构分块放在内存中 ,将PE文件从硬盘中映射到内存映像结构

  1. 最小基本单元

    计算机中,为了提高处理文件过程中,内存的效率, 使用“最小基本单元”这一概念,PE文件映射到内存后节区的起始位置应该在最小基本单元的倍数上,在最小基本单元中空余的空间填NULL

    image-20241023152925754
  2. 程序处理

    首先将PE文件的MS-DOS头,NT头以及节区头拷贝到开辟的内存空间的首地址处。

    下面的代码中pFileBuf存储了从硬盘中读取的PE数据, pFileBuf_New为依据SizeofImage开辟的新内存空间,头部大小可由可选头中的SizeOfHeaders成员变量获得

    image-20241023153035096

    Windows提供了一个宏IMAGE_FIRST_SECTION,可以根据NT头直接返回第一个节区头的指针

    由每个节区头中的PointerToRawData(指针指向的原始数据),VirtualAddress以及SizeOfRawData成员变 量,可以获知每个节区的数据在pFileBuf中的首地址, 该数据应该被放在pFileBuf_New的地址加上VirtualAddress

    image-20241023153121795

  3. RVA&VA

    RVA是在PE文件中为了避免使用确定的内存地址, 出现了相对虚拟地址(RVA),RVA是内存中相对于PE文件装入地址的偏移位置, 是一个“相对地址” ,或称为“偏移量” ,VA指的是进程装入内存后实际的内存地址,被称为虚拟地址,VA=Image Base + RVA

    其中基地址是PE文件通过Windows加载器装入内存后,该模块的初始内存地址就被称为基地址

  4. 从文件偏移到相对虚拟地址

    在以上小节的地址计算中,都是在文件映射到内存之后进行的,但是PE文件在存储时为了减少体积,FileAlignment 通常小于SectionAlignment

    当文件被映射到内存中后,同一数据在文件中的偏移量与在内存中的偏移量是不一样的,这样就存在文件偏移地址(RAW)到相对虚拟地址(RVA)之间的转换

    如果需要对存储在硬盘中的PE文件进行操作,需要将RVA转换为RAW

    由于应用程序的映射是以节区为单位做的映射,一个节区内数据的地址相对于节区的地址是不变的, 因此只需要计算各节区在磁盘与内存中起始地址的差值即可,notepad

    image-20241023153544369image-20241023153616964

    在计算某虚拟地址对应的文件偏移时,应首先查看其属于哪一节区,找到相应的差值后再进行转换, 以上述notepad为例,如给定一虚拟地址0x402854, ImageBase为0x400000,要求计算其文件偏移地址

    image-20241023153716578

3 基址重定位

重定位表是PE文件中用于支持代码在内存中动态加载到不同地址的一种机制。它解决了代码中的绝对地址在加载到不同内存地址时需要调整的问题。

由第二节中对可选头的描述可知,可选头的 ImageBase成员变量描述了程序在装入内存时优先装入的地址,在生成PE文件时,EXE文件优先装入的地址是 0x400000,DLL文件优先装入的地址是0x10000000

image-20241023154259443

在xp中没有地址随机化,EXE会被默认装入基址处,通常不需要进行基址重定位,一个可执行程序要加载的DLL有很多,不能都加载在0x10000000处 ,当地址已经被占用时,就需要加载到未被占用的空间中,此时,程序中的一些绝对地址访问过程中,就会访问或跳转到别的地址空间中,而不是访问或转到预期的位置,重定位表就是为此而产生的

在vista及以上的操作系统中,开启了地址随机化保护,EXE也会被加载到别的地址,因此EXE也有了重定位表

基址重定位结构定义

重定位表由许多重定位块串接组成,每个重定位块中存放着4KB大小PE文件内容的重定位信息

重定位块开头以IMAGE_BASE_RELOCATION开始

image-20241023154523117

  1. VirtualAddress 声明了该组重定位数据开始的相对虚拟地址,各项重定位地址与该值相加,才是需要进行重定位的相对虚拟地址
  2. SizeOfBlock 声明了该组重定位数据的大小,其中包含了 IMAGE_BASE_RELOCATION ,在IMAGE_BASE_RELOCATION之后紧随的是TypeOffset数组, 数组每项大小为两个字节,高四位代表重定位类型,低十二位值为重定位地址,最终所有的重定位块以一个VirtualAddress为0的 IMAGE_BASE_RELOCATION结构结束
image-20241023154630496

程序处理:

重定位表在PE文件中往往单独分为1个节区,名称为“.reloc” ,可以由可选头中DataDirectory中的BASERELOC Directory成员找到

整个代码共有两个循环,第一个循环遍历每个重定位块,第二个循环遍历每个重定位块中的重定位信息。根据每个重定位信息的高四位确定其是否需要重定位,需要重定位时,根据程序预期存储位置 ImageBase以及当前程序存储位置m_pFileBuf对其地址进行修正

4 导入表

导入表用于描述一个模块(EXE 或 DLL)在运行时需要从其他模块(通常是 DLL)中调用的函数或变量。

在编写程序时,会使用到大量的库函数,由于动态链接的存在,这些函数并不会都编写进二进制文件中,而是在函数调用处填入对应的导入表地址

当程序加载到内存中后,Windows加载器才将相关的DLL装入,并将调用输入函数的指令和函数实际所在地址关联起来

调用VirtualAlloc函数时,依据二进制查看得到 VirtualAlloc地址为0x47d1d8

image-20241023203318569

查看0x47d1d8处,如下图所示,其值在IDA中为未知值,这是因为该PE文件尚未装入内存中,没有在导入地址表中填写相应的地址

image-20241023203447151

当其执行后,该处的值会由Windows加载器填写, 如图所示,其值已变为0x74cf6970

image-20241023203510220

在程序装入内存时,PE加载器完成了这些工作,同样,编写一个自己的LoadLibrary也需要在导入表中填入相应的函数地址

IMAGE_IMPORT_DESCRIPTOR

PE文件头的可选映像头中,数据目录表的第二成员指向导入表,导入表由IMAGE_IMPORT_DESCRIPTOR(IID) 数组构成。

每个被PE文件导入的DLL都有一个与之对应的IID

IID中并无字段表明IID数组的长度大小,该数组最后的一个单元为NULL,由此可以计算出IID数组的项数

image-20241023204107558

  1. OriginalFirstThunk(Characteristics) 包含指向导入名称表的RVA
  2. TimeDataStamp 一个32位的时间标志
  3. ForwarderChain 当程序引用一个DLL中的API,而这个API又引用别的DLL 的API时使用
  4. Name DLL名字的指针。名称字符串以\0结尾
  5. FirstThunk 包含指向导入地址表的RVA

导入名称表(INT)的结构:

image-20241023204234055

  1. int 该字段表明本函数在DLL中的导出表序号。
  2. Name 该字段为函数名,是一个ASCII字符串,以NULL结尾

导入地址表(IAT)中填写对应函数的虚拟地址

image-20241023204319254

程序处理

与基址重定位表类似的,从可选头中的DataDirectory中的IMPORT Directory成员找到

使用LoadLibrary载入所有关联的DLL,使用 GetProcAddress获取所有函数地址,填入IAT中

程序共有两个循环,第一个循环为遍历所有需要导入的DLL,第二个循环为遍历每个DLL中需要导入的函数。将获取到的函数地址填入IAT表中即可

5 导出表

导出表用于描述一个模块(通常是 DLL)向其他模块提供的函数或变量。通过导出表,其他模块可以找到并使用 DLL 中的功能。

上面LoadLibrary已经完成了,接下来需要完成的是与LoadLibrary配套的GetProcAddress

仅仅把DLL加载到内存中是不够的,无法得到DLL导出函数的地址,这个DLL就是无效的

在DLL中,DataDirectory比EXE中多了一项 EXPORTDirectory,通过EXPORT Directory可以找到 DLL中的导出地址表

IMAGE_EXPORT_DIRECTORY

image-20241023204504158
  1. Characteristics 未定义,为0

  2. TimeDateStamp 该字段表明输出表创建时间

  3. MajorVersion 该字段表明输出表的主版本号,未使用,值为0

  4. MinorVersion 该字段表明输出表的次版本号,未使用,值为0

  5. Name 该字段指向DLL名称字符串地址

  6. Base 该字段包含用于这个可执行文件输出表的起始序数值

  7. NumberOfFunctions 该字段表明导出地址表(EAT)中的条目数量

  8. NumberOfNames 输出函数名称表的条数数量,该值小于或等于NumberOfFunctions。当函数只通过序数输出时会出现NumberOfNames小于NumberOfFunctions

  9. AddressOfFunctions 该字段指向EAT地址,EAT中存储了所有导出函数的相对虚拟地址

  10. AddressOfNames 该字段指向导出名称表(ENT)地址,ENT中存储了所有函数名称字符串的相对虚拟地址

  11. AddressOfNameOrdinals 该字段指向导出序数表地址,导出序数表中存储了所有导出函数的序数

image-20241023204724424

程序处理

从可选头中的DataDirectory中的EXPORT Directory成员找到IMAGE_EXPORT_DIRECTORY地址

从ENT中取出函数名,与要取的函数名进行对比, 一致时从EAT中得到函数地址与内存中的DLL基址相加,即得到了函数真实的地址

至此,自制的GetProcAddress也已经完成,与之前编写的LoadLibrary配合,就可以实现在内存中加载DLL并获取函数地址

总结对比

功能导入表(Import Table)导出表(Export Table)
作用描述模块需要调用的外部函数和变量描述模块提供给其他模块的函数和变量
包含内容DLL 名称、函数名称或序号、IAT函数名称、地址、序号
用途解析外部依赖关系,加载和绑定外部模块提供函数或变量接口供外部模块调用
位置依赖的模块中不存在,位于当前模块位于模块本身内部

导入表:一个 EXE 调用系统函数如 MessageBox,导入表会记录 user32.dll 的名称和 MessageBox 函数。

导出表:一个 graphics.dll 提供 DrawLineDrawCircle 两个函数,导出表记录了这些函数的名称和地址,供其他模块调用。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

聚类分析 | MSADBO优化Spectral谱聚类优化算法

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于改进正弦算法引导的蜣螂优化算法(MSADBO)优化Spectral谱聚类,matlab代码,直接运行! 创新独家,先用先发,注释清晰,送MSADBO参考文献!优化参数 优化后的带…

【C++】构造与析构函数

目录: 一、 This指针  (一)使用方法: 二、类的默认成员函数 三、构造函数  (一)构造函数的特点 四、析构函数 (一)析构函数的特点 正文 一、 This指针 在c语言中我们调用函…

2021TCSVT,VDM-DA:面向无源数据域自适应的虚拟域建模

原文标题:VDM-DA: Virtual Domain Modeling for Source Data-free Domain Adaptation 中文标题:VDM-DA:面向无源数据域自适应的虚拟域建模 1、Abstract 领域适应旨在利用标签丰富的领域(源领域)来帮助标签稀缺领域&…

MySQL数据库1——数据库概论

一.数据库概论 1.数据库 数据库(DataBase,DB):是长期存储在计算机内、有组织的、统一管理的相关数据的集合。简单来说,它就是一个存储各种数据的仓库,且存储过程不是随便而是有组织的。 数据库管理系统&am…

英伟达Tensor Core技术架构原理分析

英伟达Tensor Core架构技术原理 英伟达的Tensor Core架构是一种专为加速人工智能、深度学习、高性能计算(HPC)等领域中的矩阵运算和张量运算而设计的硬件单元。自首次在Volta架构中引入以来,Tensor Cores已成为NVIDIA高端GPU的核心特性,并在后续的Turing…

Linux基础5-进程控制1(fork创建子进程,写时拷贝,进程退出)

上篇文章:Linux基础4-进程5(程序地址空间详解)-CSDN博客 本篇重点:fork,写实拷贝,进程退出 目录 一. fork创建子进程 1.1 fork用法 1.2 fork返回值有两个的理解 二. 进程退出码 2.1 进程退出码 2.2 进…

用python中的tkinter包实现进度条

python中的tkinter包是一种常见的设计程序的GUI界面用的包。本文主要介绍这里面的一个组件:进度条(Progressbar)。Tkinter Progressbar里面对进度条组件已经做了一定的介绍,但比较抽象。本文以另一种方式介绍这个组件及其常用用法…

20.使用标准差剔除异常值

标准差剔除异常值 1. 方法2. 示例代码2.1 数据读取与清洗2.2 分段读取数据并绘图2.3 解释2.4 outliers2.5 结果展示 我有个记录数据采集后格式是step_rewards.txt 的文档,里面只有一列数据,10*10000行数据,没有表头,分别代表奖励数…

中科蓝讯修改蓝牙名字:【图文讲解】

本文以市面上一款公板公模的畅销产品为例。K12蓝牙音响。 音响用的芯片是:AB5768E MIC用的芯片是:AB5769A 硬件框架图如下: 芯片修改名字,通过下载工具中的配置进行修改。选择蓝牙配置,然后会出现蓝牙名字&#xff…

旅行包发霉怎么处理 除霉及防霉解决方法

近些年听到很多箱包工厂的心声,为什么箱包旅行包每年都会出现长霉请况呢?长霉了,也就是长毛了,长出那些病菌、真菌、细菌等微生物。经ihaoer防霉人士介绍旅行包长霉处理方法如下: 旅行包长霉的因素 一、旅行包储存在阴…

14、交互补充

1、元素的三大系列 1.1、offset系列 1.1.1、offset初相识 使用offset系列相关属性可以动态的得到该元素的位置(偏移)、大小等 获得元素距离带有定位祖先元素的位置获得元素自身的大小(宽度高度)注意:返回的数值都不…

【IEEE出版、八大高校联合举办、稳定EI检索】第四届人工智能与智能制造国际研讨会(AIIM 2024,12月20-22日)

第四届人工智能与智能制造国际研讨会(AIIM 2024) The 4th International Symposium on Artificial Intelligence and Intelligent Manufacturing 2024年12月20-22日 中国成都 重要信息 大会官网:www.isaiim.com 大会时间:202…

朴素贝叶斯算法探讨与实践

引言 和撰写博文[1]的缘由一样,本文是想要在所创设的专栏[2]里把所谓的十大机器学习算法[3]全部过一遍。 朴素贝叶斯算法是传统机器学习里的一种可以被用来进行分类的算法,本文将对其原理进行说明,并基于原理给出一个基于该算法的分类实践。…

《数据在内存中的存储》

内存函数 1. 整数在内存中的存储 (1)旧识回顾: 之前在学到操作符的时候,我们就学过了下面的内容: 整数的二进制的表示方式有三种,原码、反码、补码 有符号的整数,三种表示方式均有符号位和数…

【路径规划】粒子群算法、遗传算法、差分进化算法、灰狼优化算法、麻雀优化算法(PSO、GA、DE、GWO、SSA)路径规划

摘要 本文探讨了多种智能优化算法在路径规划中的应用,包括粒子群算法(PSO)、遗传算法(GA)、差分进化算法(DE)、灰狼优化算法(GWO)和麻雀优化算法(SSA&#x…

CSS基础知识05(弹性盒子、布局详解,动画,3D转换,calc)

目录 0、弹性盒子、布局 0.1.弹性盒子的基本概念 0.2.弹性盒子的主轴和交叉轴 0.3.弹性盒子的属性 flex-direction row row-reverse column column-reverse flex-wrap nowrap wrap wrap-reverse flex-dirction和flex-wrap的组合简写模式 justify-content flex-s…

如何搭建一台邮箱服务器,配置满分邮箱

如何搭建一台邮箱服务器,配置满分邮箱 搭建一台个人邮箱服务器听上去非常有技术含量,但只要准备工作充分,并且选择合适的软件,配置满分的邮箱,其实并没有想象中那么困难。在这篇文章中,我们将介绍搭建邮箱服务器的 必备…

DevOps工程技术价值流:打造卓越项目协作的优化宝典

一、引言 解锁项目协作的无限潜力,覆盖全链路实现流畅高效。 在当今瞬息万变的商业环境中,项目协作的效率和效果直接关系到企业的竞争力和市场响应速度。DevOps工程技术价值流中的项目协作优化,不仅是技术层面的革新,更是团队协…

【JAVA毕业设计】基于Vue和SpringBoot的冬奥会科普平台

本文项目编号 T 610 ,文末自助获取源码 \color{red}{T610,文末自助获取源码} T610,文末自助获取源码 目录 一、系统介绍二、数据库设计三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状 六、核心代码6.1 查询项目类型6.…

基于的图的异常检测算法OddBall

OddBall异常检测算法出自2010年的论文《OddBall: Spotting Anomalies in Weighted Graphs》,它是一个在加权图(weighted graph)上检测异常点的算法,基本思路为计算每一个点的一度邻域特征,然后在整个图上用这些特征拟合出一个函数&#xff0c…