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;

逆向初级-PE(五)的更多相关文章

  1. 逆向学习-PE文件格式

    从DOS头到节区头是PE头部分,其下的节区合称PE体.文件中使用偏移(offset),内存中使用VA(Virtual Address,虚拟地址)来表示位置.文件加载到内存时,情况就会发生变化(节区的大 ...

  2. 密码学初级教程(五)消息认证码MAC-Message Authentication Code

    密码学家工具箱中的6个重要的工具: 对称密码 公钥密码 单向散列函数 消息认证码 数字签名 伪随机数生成器 MAC能识别出篡改和伪装,也就是既可以确认消息的完整性,也可以进行认证. 消息认证码的输入包 ...

  3. Flask初级(五)flash在模板中使用继承,模板的模板

    Project name :Flask_Plan templates:templates static:static 继续上一篇文章. 我们不希望每个页面都写一遍引入js,css,导航条……………… ...

  4. 滴水逆向初级-C语言(二)

    2.1.C语言的汇编表示 c语言代码 int plus(int x,int y) { return 0; } void main() { __asm { mov eax,eax } //调用函数 pl ...

  5. 【Linux】-NO.87.Assembly.1.滴水逆向.1.001-【介绍】-

    1.0.0 Summary Tittle:[Linux]-NO.87.Assembly.1.滴水逆向.1.001-[基础]- Style:Java Series:Log4j Since:2017-04 ...

  6. Laravel初级教程浅显易懂适合入门

    整理了一些Laravel初级教程,浅显易懂,特适合入门,留给刚学习laravel想快速上手有需要的朋友 最适合入门的laravel初级教程(一)序言 最适合入门的laravel初级教程(二)安装使用 ...

  7. 【资源】C++学习资料 - 逆天整理 - 精华无密版【最新】

    再失效就太无语了,链接都是多份的~~—————————————————基础——————————————C++环境搭建(全套)http://pan.baidu.com/s/1o6y0smY链接:http ...

  8. windows 花式装系统

    目录 一.安装系统前准备 准备U盘 准备好一个制作启动盘的软件 准备系统镜像 二.接下来先制作启动盘(以微PE为例) 三.插上u盘,调BIOS(BIOS即基本输入输出系统) 四.进入PE 五.开始安装 ...

  9. Hacking Tools

    Hacking Tools 种各样的黑客工具浩如天上繁星,这也让许多刚刚入门安全技术圈的童鞋感到眼花缭乱,本文整理了常用的安全技术工具,希望能够给你带来帮助.以下大部分工具可以在 GitHub 或 S ...

随机推荐

  1. 「NGK每日快讯」11.18日NGK公链第15期官方快讯

  2. JDK环境解析,安装和目的

    目录 1. JDK环境解析 1.1 JVM 1.2 JRE 1.3 JDK 2. JDK安装 2.1 为什么使用JDK8 2.1.1 更新 2.1.2 稳定 2.1.3 需求 2.2 安装JDK 2. ...

  3. Java volatile 关键字底层实现原理解析

    本文转载自Java volatile 关键字底层实现原理解析 导语 在Java多线程并发编程中,volatile关键词扮演着重要角色,它是轻量级的synchronized,在多处理器开发中保证了共享变 ...

  4. day1 分布式基础概念

    1. 分布式:一个业务分拆多个子业务,部署在不同的服务器上集群:同一个业务,部署在多个服务器上节点:集群中的一个服务器 2.远程调用 分布式系统中调用其它主机 springcloud用http+jso ...

  5. idea分布式创建子模块后不能创建java文件

    问题描述:多模块情况下,创建java文件,找不到java类,如下图,即使手动创建,在里面编写内容也没有任何反应. 解决方案:右键将文件标记为Sources Root便可以了,如果想要标记为资源文件的话 ...

  6. vue中将分号去掉,将双引号变为单引号的配置

    在项目根目录下创建.prettierrc文件,文件内容如下: { "semi": false, "singleQuote": true } 实现vs code中 ...

  7. 第41天学习打卡(死锁 Lock synchronized与Lock的对比 线程协作 使用线程池)

    死锁 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块同时拥有"两个以上对象的锁"时 ...

  8. docker 上传到docker hub 采坑

    前面是仓库名称 后面可以命名img名字 docker push gaodi2345/wj:docker_gui

  9. go 在crontab里面运行报错 解决方案

    问题背景 你高高兴兴的写好了一个go脚本,放到你的服务器上,打算定期运行这个脚本,你打开crontab -e, 然后输入: */1 * * * * go run /root/test/main.go ...

  10. HDOJ-1160(最长上升子序列变形)

    FatMouse's Speed HDOJ-1160 注意输出长度的时候不是输出dp[n] #include<bits/stdc++.h> using namespace std; con ...