从PE结构到LoadLibrary

news2024/12/21 3:45:13

从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.coloradmin.cn/o/2242253.html

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

相关文章

AntFlow:一款高效灵活的开源工作流引擎

AntFlow 是一款功能强大、设计优雅的开源工作流引擎,其灵感来源于钉钉的工作流设计理念,旨在为企业和开发者提供灵活、高效的工作流解决方案。AntFlow 支持复杂的业务流程管理,具有高度可定制性,且拥有现代化的前端设计&#xff0…

智慧安防丨以科技之力,筑起防范人贩的铜墙铁壁

近日,贵州省贵阳市中级人民法院对余华英拐卖儿童案做出了一审宣判,判处其死刑,剥夺政治权利终身,并处没收个人全部财产。这一判决不仅彰显了法律的威严,也再次唤起了社会对拐卖儿童犯罪的深切关注。 余华英自1993年至2…

python机器人Agent编程——多Agent框架的底层逻辑(上)

目录 一、前言二、两个核心概念2.1 Routines(1)清晰的Prompt(2)工具调用json schema自动生成(3)解析模型的toolcall指令(4)单Agent的循环决策与输出 PS.扩展阅读ps1.六自由度机器人相…

【大语言模型】ACL2024论文-14 任务:不可能的语言模型

【大语言模型】ACL2024论文-14 任务:不可能的语言模型 目录 文章目录 【大语言模型】ACL2024论文-14 任务:不可能的语言模型目录摘要研究背景问题与挑战如何解决创新点算法模型实验效果重要数据与结论推荐阅读指数和推荐理由 后记 任务:不可能…

redis linux 安装

下载解压 https://download.redis.io/releases/ tar -zvxf ----redis-7.4.1编译 进入目录下 # redis 依赖c yum install gcc-cmake可能会有问题,所以记得换源# 安装到 /usr/local/redis make PREFIX/usr/local/redis installcd src ./redis-serverredis.confi…

计算机毕业设计Hadoop+大模型空气质量预测 空气质量可视化 空气质量分析 空气质量爬虫 Spark 机器学习 深度学习 Django 大模型

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

云原生之运维监控实践-使用Telegraf、Prometheus与Grafana实现对InfluxDB服务的监测

背景 如果你要为应用程序构建规范或用户故事,那么务必先把应用程序每个组件的监控指标考虑进来,千万不要等到项目结束或部署之前再做这件事情。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章,当…

HTML+CSS+JavaScript

一、HTML 1、什么是HTML HTML(Hyper Text Markup Language)也叫超文本标记语言,什么意思呢? 超文本:普通文本语言没有什么特殊功能,而超文本,是表示一种比文本语言功能更强大的语言&#xff0c…

Dropout 和 BatchNorm 在训练和验证中的差异

文章目录 1. Dropout1.1 作用1.2 训练和验证的差异1.3 示例 2. Batch Normalization (BatchNorm)2.1 作用2.2 训练和验证时的差异2.3 示例 3. 总结4. 实际使用建议 在神经网络中,Dropout 和 Batch Normalization (BatchNorm) 是常见的层,其行为在 训练阶…

SQL Server 查询设置 - LIKE/DISTINCT/HAVING/排序

目录 背景 一、LIKE - 模糊查询 1. 通配符 % 2. 占位符 _ 3. 指定集合 [] 3.1 表示否定 ^ 3.2 表示范围 - 4. 否定 NOT 二、DISTINCT - 去重查询 三、HAVING - 过滤查询 四、小的查询设置 1. ASC|DESC - 排序 2. TOP - 限制 3. 子查询 4. not in - 取补集&…

【算法速刷(10/100)】LeetCode —— 23. 合并 K 个升序链表

按照最朴素的方法,每轮都对所给列表进行一次遍历,O(n)的复杂度获得值最小的节点,并将其上的链表指针后移一位,一旦为空则剔除数组。数组为空时结束循环。 这样写时间复杂度较高,因为涉及到枚举最小值节点,数…

【C语言】四种方法交换两个数(免费复制)

方法一&#xff1a; 通过中间变量t直接交换。 #include<stdio.h> //方法一 int main() {int a,b,t;printf("请输入a和b的值&#xff1a;\n");scanf("%d %d",&a,&b);printf("交换前&#xff1a;a%d,b%d\n",a,b);ta;ab;bt;printf…

WebSocket简易聊天室实现(有详细解释)

完整代码 Arata08/online-chat-demo 服务端: 1.编写配置类&#xff0c;扫描有 ServerEndpoint 注解的 Bean import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.s…

数据分析——Python绘制实时的动态折线图

最近在做视觉应用开发&#xff0c;有个需求需要实时获取当前识别到的位姿点位是否有突变&#xff0c;从而确认是否是视觉算法的问题&#xff0c;发现Python的Matplotlib进行绘制比较方便。 目录 1.数据绘制2.绘制实时的动态折线图3.保存实时数据到CSV文件中 import matplotlib.…

i春秋-Hash

练习平台地址 竞赛中心 题目描述 题目内容 啥也没有就一个标签跳转 点击后的确发生了跳转 观察到url中有key和hash两个值&#xff0c;猜测hash是key的hash 查看源代码发现确实是 $hashmd5($sign.$key);the length of $sign is 8 解密得到$sign应该为kkkkkk01 构造122的hash i…

举例理解LSM-Tree,LSM-Tree和B+Tree的比较

写操作 write1&#xff1a;WAL 把操作同步到磁盘中WAL做备份&#xff08;追加写、性能极高&#xff09; write2&#xff1a;Memtable 完成WAL后将(k,v)数据写入内存中的Memtable&#xff0c;Memtable的数据结构一般是跳表或者红黑树 内存内采用这种数据结构一方面支持内存…

论文PDF页面无法下载PDF

问题&#xff1a;通常在下载学术论文时&#xff0c;网页命名是PDF页面&#xff0c;但是无法下载PDF&#xff0c;下载的是html网页 解决&#xff1a; mac&#xff1a;按F12打开开发者界面 然后点击源代码/来源选项 然后打开下图所在位置&#xff0c;鼠标右键复制链接&#xff…

ORA-01092 ORA-14695 ORA-38301

文章目录 前言一、MAX_STRING_SIZE--12C 新特性扩展数据类型 varchar2(32767)二、恢复操作1.尝试恢复MAX_STRING_SIZE参数为默认值2.在upgrade模式下执行utl32k.sql 前言 今天客户发来一个内部测试库数据库启动截图报错&#xff0c;描述是“上午出现服务卡顿&#xff0c;然后重…

ElasticSearch学习笔记二:使用Java客户端

一、前言 在上一篇文章中&#xff0c;我们对ES有了最基本的认识&#xff0c;本着实用为主的原则&#xff0c;我们先不学很深的东西&#xff0c;今天打算先学习一下ES的Java客户端如何使用。 二、创建项目 1、普通Maven项目 1、创建一个Maven项目 2、Pom文件 <dependenc…

[Linux]多线程详解

多线程 1.线程的概念和理解1.1线程的优点1.2线程的缺点1.3线程的设计1.4线程 VS 进程 2.线程控制2.1线程等待2.2 线程终止2.3 线程分离 3.线程互斥3.1背景3.2抢票代码演示3.3保护公共资源&#xff08;加锁&#xff09;3.3.1创建锁/销毁锁3.3.2申请锁/尝试申请锁/解锁 3.4解决抢…