5.1.PE文件结构

1、什么是可执行文件?

可执行文件(executable fle)指的是可以由操作系统进行加载执行的文件。

可执行文件的格式:

Windows平台:

PE(Portable Executable)文件结构

Linux平台:

ELF(Executable and Linking Format)文件结构

哪些领域会用到PE文件格式:

<1>病毒与反病毒

<2>外挂与反外挂

<3>加壳与脱壳(保护与破解)

<4>无源码修改功能、软件汉化等

2、如何识别PE文件

<1> PE文件的特征(PE指纹)

分别打开.exe .dlI .sys 等文件,观察特征前2个字节。



<2>不要仅仅通过文件的后缀名来认定PE文件

5.2.PE文件的两种状态

1、PE文件主要结构体

  • IMAGE_DOS_HEADER占64个字节。
  • DOS Sub:IMAGE_DOS_HEADER尾部的四个字节指向PE文件的开始位置。IMAGE_DOS_HEADER尾部到PE文件头开始的中间部分是DOS_Sub部分(大小不固定)
  • PE文件头标志:PE头是前面4个字节
  • PE文件表头:IMAGE_FILE_HEADER是20个字节
  • 扩展PE头:IMAGE_OPTIONAL_HEADER在32位中占224个字节(这个大小是可以修改的)
  • IMAGE_SECTION_HEADER:40个字节

2、PE文件的两种状态

5.3.DOS头属性说明

IMAGE_DOS_HEADER结构体

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

主要就看两个成员

WORD   e_magic;       //PE文件判断表示   4D5A,ascii是MZ
LONG e_lfanew; //存储PE头首地址
  • e_magic两个字节和e_lfanew四个字节内容不能修改
  • 开头e_magic和结尾e_lfanew中间的成员部分可以随意修改
  • e_lfanew到PE头文件中间的DOS Stub部分可以随便修改

5.4.标志PE头属性说明

1、PE头

typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; //PE标识,占4字节
IMAGE_FILE_HEADER FileHeader; //标志PE头
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //扩展PE头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

PE标识不能破坏,操作系统在启动一个程序的时候会检测这个标识。

2、标准PE头(占20字节)

typedef struct _IMAGE_FILE_HEADER {
WORD Machine;//可以运行在什么样的CPU上 任意:0 Intel 386以及后续:14C x64:8664
WORD NumberOfSections;//表示节的数量
DWORD TimeDateStamp;//编译器填写的时间戳 与文件属性里面(创建时间、修改时间)无关
DWORD PointerToSymbolTable;//调试相关
DWORD NumberOfSymbols;//调试相关
WORD SizeOfOptionalHeader;//可选PE头的大小(32位PE文件:0xE0 64位PE文件:0xF0)
WORD Characteristics;//文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Characteristics文件属性



文件属性

Characteristics值为: 01 0F

转换为二进制:0000 0001 0000 1111

说明下标0,1,2,3,8有值,根据下标是不是1,然后查看对应的文件属性

5.5.扩展PE头属性说明

1、扩展PE头结构体(总共224字节)

typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; // 分辨32位程序还是64位,如果32位则10B,64位则20B
BYTE MajorLinkerVersion; //链接器版本号
BYTE MinorLinkerVersion; //链接器版本号
DWORD SizeOfCode; //所有代码节的总和 文件对齐后的大小 编译器填写的,无用处
DWORD SizeOfInitializedData; //已经初始化数据的节的总大小 文件对齐后的大小 编译器填写的,无用处
DWORD SizeOfUninitializedData; // 未初始化数据的节的总大小 文件对齐后的大小 编译器填写的,无用处
DWORD AddressOfEntryPoint; // 程序入口
DWORD BaseOfCode; //代码开始的基址 编译器填写的,无用处
DWORD BaseOfData; //数据开始的基址 编译器填写的,无用处 //
// NT additional fields.
// DWORD ImageBase; //内存镜像基址
DWORD SectionAlignment; //内存对齐
DWORD FileAlignment; //文件对齐
WORD MajorOperatingSystemVersion; //操作系统版本号
WORD MinorOperatingSystemVersion; //操作系统版本号
WORD MajorImageVersion; //PE文件自身的版本号
WORD MinorImageVersion; //PE文件自身的版本号
WORD MajorSubsystemVersion; //运行所需要子系统的版本号
WORD MinorSubsystemVersion; //运行所需要子系统的版本号
DWORD Win32VersionValue; //子系统版本的值,必须为0
DWORD SizeOfImage; //内存中整个PE文件的映射尺寸,比实际的值大,必须是SectionAlignment整数倍
DWORD SizeOfHeaders; //所有的头+节表按照文件对齐后的大小
DWORD CheckSum; //校验和,可伪造
WORD Subsystem; //子系统, 驱动程序(1) 图形界面(2) DLL(3)
WORD DllCharacteristics; //文件特性 不是针对DLL文件的
DWORD SizeOfStackReserve; //初始化保留的栈的大小
DWORD SizeOfStackCommit; //初始化实际提交的大小
DWORD SizeOfHeapReserve; //初始化保留的堆的大小
DWORD SizeOfHeapCommit; //初始化实际提交的大小
DWORD LoaderFlags; //调试相关
DWORD NumberOfRvaAndSizes; //目录项数目
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数组,
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

2、ImageBase和AddressOfEntryPoint

ImageBase; //内存镜像基址
AddressOfEntryPoint; // 程序入口,相对于ImageBase的偏移

实例

程序入口:0193BE
内存镜像:400000 程序真正入口=内存镜像+程序入口=4193BE



通过DTDebug确认



3、 DllCharacteristics文件特性

5.6.PE节表

节表结构体(占40字节)

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //ASCII字符串 可自定义 只截取8个字节(占8字节)
union { //Misc双子是该字节没有在对齐前的真实尺寸 该值可以不准确(占4字节)
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; //在内存中的偏移地址加上ImageBase才是内存中的真正地址
DWORD SizeOfRawData; //节在文件中对齐后的尺寸
DWORD PointerToRawData; //节区在文件中的偏移
DWORD PointerToRelocations; //调试相关
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER 40

DOS头64字节+PE标识4字节+PE标准头20字节+PE扩展头224字节,然后就是节表的起始位置,每个节表占40个字节

5.7.RVA与FOA的转换

1、RVA(相对虚拟地址)到FOA(文件偏移地址)的转换:

<1>得到RVA的值:内存地址- ImageBase

<2>判断RVA是否位于PE头中,如果是: FOA== RVA

<3>判断RVA位于哪个节:

RVA>=节VirtualAddress

RVA <=节.VirtualAddress +当前节内存对齐后的大小

差值= RVA-节VirtualAddress;

<4> FOA=节.PointerToRawData +差值;

如果文件对齐和内存对齐的值一样,则RVA=内存地址- ImageBase,F0A=RVA,就可以得出在文件中的地址

5.8.空白区添加代码

给程序添加一个MessageBox对话框,步骤

  • 在PE的空白区构造一段代码
  • 修改入口地址为新增代码的地址
  • 新增代码执行后,跳回到入口地址

1、MessageBox的反汇编硬编码

E8 表示call

6A表示push

9:        MessageBox(0,0,0,0);
00401028 8B F4 mov esi,esp
0040102A 6A 00 push 0
0040102C 6A 00 push 0
0040102E 6A 00 push 0
00401030 6A 00 push 0
00401032 FF 15 8C 42 42 00 call dword ptr [__imp__MessageBoxA@16 (0042428c)]
00401038 3B F4 cmp esi,esp
0040103A E8 31 00 00 00 call __chkesp (00401070)

2、找到要运行的程序的MessageBoxA的地址

用DTDdbug打开程序,点“E”,找到“USER32.DLL”,按“Ctrl+n”,然后找到MessageBoxA函数的地址



构造自己的代码,找一段空白区,写上自己的代码

先执行我们要写的代码(弹出信息框),执行完,然后jmp到程序入口位置

构造要写入的代码

6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00

E8表示call
E8后面的硬编码 = 要跳转的地址 - E8指令当前的地址 - 5 要跳转的MessageBoxA的地址:77D5050B E8后面的硬编码 = 77D5050B - (ImageBase+F98)- 5 = 7794F56E 程序入口:000193BE
ImageBase:00400000
程序运行入口=ImageBase+程序入口=004193BE E9后面的硬编码 = 004193BE - 400F9D - 5 = 1841C 最终代码
6A 00 6A 00 6A 00 6A 00 E8 6E F5 94 77 E9 1C 84 01 00



修改程序入口



把入口改成我们自己构造的代码的起始位置F90

5.9.扩大节

1、为什么要扩大节

我们可以在任意空白区添加自己的代码,但如果添加的代码比较多,空白区不够怎么办?

2、扩大节的步骤

<1>分配一块新的空间,大小为S

<2>将最后-一个节的SizeOfRawData和VirtualSize改成N

N = (SizeOfRawData或者VirtualSize内存对齐后的值)+ S

<3>修改SizeOflmage大小

S = 1000

VirtualSize:78B0 当前节内存中没有对齐的实际大小

SizeOfRawData:8000 当前节文件对齐后的大小

N = 8000 + 1000 = 9000



修改VirtualSize和SizeOfRawData值



扩大节,添加1000h,也就是十进制4096字节。右键-->粘贴-->粘贴零字节-->4096



修改SizeOflmage的值,先内存对齐后再加1000



SizeOflmage结果为

5.10.新增节

1、新增节的步骤:

<1>判断是否有足够的空间,可以添加一个节表.

<2>在节表中新增一个成员.

<3>修改PE头中节的数量.

<4>修改sizeOflmage的大小.

<5>在原有数据的最后,新增一个节的数据(内存对齐的整数倍).

<6>修正新增节表的属性.

2、节表结构

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //ASCII字符串 可自定义 只截取8个字节(占8字节)
union { //Misc双子是该字节没有在对齐前的真实尺寸 该值可以不准确(占4字节)
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; //在内存中的偏移地址加上ImageBase才是内存中的真正地址
DWORD SizeOfRawData; //节在文件中对齐后的尺寸
DWORD PointerToRawData; //节区在文件中的偏移
DWORD PointerToRelocations; //调试相关
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SECTION_HEADER 40

在节表中新增一个节,把.txet节的40个字节复制粘贴到新增加的节,然后修改新增加节的成员属性

  • 前8个字节是节的名字:随便改个名字
  • 把之前最后一个节的VirtualSize(内存中没有对齐的实际值)改为内存对齐后的值



    改为8000



    修改新增加节的VirtualSize和SizeOfRawData,因为新增加的节大小为1000h



    新增加节的VirtualAddress = 上一个节内存对齐后的大小+上一个节.VirtualAddress
新增加节
VirtualAddress = 00008000+0002B000 = 00033000
PointerToRawData=VirtualAddress



修改sizeOflmage的大小



修改为34000



在原有数据的最后,新增一个节的数据,新增加节的大小为1000h

先删除第一个节前面的40个字节(因为前面新增加了一个节表,数据全部往后推移了40个字节)



在最后面添加1000h字节

5.11.导出表

1、如何查找导出表

扩展PE头最后一个成员是一个数组(包含16和元素),每个数组对应一个表(每个表占8字节),如导出表、导入表等。

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //表的起始位置RVA
DWORD Size; //表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

2、导出表结构

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

3、导出表成员 40字节

导出表位置,数组DataDirectory[0]



起始位置2AD80



Name:2ADBC (RVA),然后从2ADBC的位置开始找,到以0结尾,就是导出表的名字



NumberOfFunctions:导出函数的个数 2个



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





AddressOfFunctions:导出函数地址表RVA



AddressOfNames:导出函数名称表RVA



AddressOfNameOrdinals:导出函数序号表RVA。序号是两个字节,序号的个数跟函数名称的个数相同

这里序号为0和1



4、参考

  • 总共四个函数
  • 所有导出函数的个数为5,因为序号中间隔了个14没有。函数个数 = 最大序号 - 最小序号 + 1
  • 以函数名导出的函数个数为3,因为有一个函数没有名字
  • 把函数地址对应的二进制复制到OD里面,可以查看到具体是什么函数

5.12.导入表_确定依赖模块

1、定位导入表

导入表位置,数组DataDirectory[1]

第一个导入表开始的位置:22A10



2、导入表结构

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA指向IMAGE_THUNK_DATA结构数组
};
DWORD TimeDateStamp; // 时间戳
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //RVA 指向dll名字,该名字以0结尾
DWORD FirstThunk; // RVA 指向IMAGE_THUNK_DATA结构数组
} IMAGE_IMPORT_DESCRIPTOR;

3、导入表个数

导入表的个数判断:,每个导入表占20个字节,判断有多少个导入表,以20个0为结尾的位置



4、查看依赖的模块名

第一个模块名字



查看

5.13.导入表_确定依赖函数

1、确定需要导入的函数



第一个成员指向的是一张表INT(导入名称表),INT表里面每个成员都是结构体IMAGE_THUNK_DATA,大小是4个字节

typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString;
PDWORD Function;
DWORD Ordinal;
PIMAGE_IMPORT_BY_NAME AddressOfData;
} u1;
} IMAGE_THUNK_DATA32;

2、INT表里面的结构体

INT表位置22A88,INT表里面有多少个成员(4个字节),就说明依赖当前导入模块多少个函数。结尾标志:四个字节都是00



INT表



3、确定需要导入的函数的名字



确定函数名字为ExitThread

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //可能为空,编译器决定,如果不为空,是函数在导出表中的索引
BYTE Name[1]; //函数名称,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

5.14.导入表_确定函数地址

PE文件加载前



PE文件加载后

5.15.重定位表

重定位表的位置(第六个表)

导入表位置,数组DataDirectory[5]

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

最新文章

  1. FreeRTOS知识点
  2. SQLite数据库在本地可以写,发布到服务器就不能写
  3. [转] JAVA正则表达式:Pattern类与Matcher类详解(转)
  4. [HDOJ 5212] [BestCoder Round#39] Code 【0.0】
  5. reids-geo
  6. python socket理论知识
  7. 刨析Maven(对pom.xml配置文件常用标签的解析)
  8. 一个简单程序快速入门JDBC
  9. 83、源代码管理工具(Git)
  10. MFC中线程相关知识
  11. APP中的图片如何长按可以下载并保存图片到相册
  12. phpstorm 找到文件修改历史
  13. mybatis入门篇:通过SqlSession.selectList进行数据查询
  14. C# ToShortDateString() ToString() 设置日期格式的区别
  15. maven 配置mirror后,本地库与远端库冲突
  16. C#之Action的实际应用例子
  17. Mockito:一个强大的用于Java开发的模拟测试框架
  18. Keras预测股票
  19. echart 图例设置自定义图标?
  20. SCPD

热门文章

  1. 教你玩转CSS 居中
  2. Linux文件/proc/net/tcp分析
  3. img图片默认的3px空白缝隙解决方法
  4. 【springboot读取配置文件】@ConfigurationProperties、@PropertySource和@Value
  5. python进阶(3)序列化与反序列化
  6. 微信小程序:解决小程序中有些格式如webpiPhone手机暂不支持的问题
  7. js 前端MD5加密
  8. tibco EMS 8.2.0安装
  9. 【免费开源】基于Vue和Quasar的crudapi前端SPA项目实战—环境搭建 (一)
  10. Amazon Connect 配置ccp端的soft phone配置(客服坐席工作站配置)