PE之手动分析PE文件结构

 

 

 

 

 

 

 PE之手动分析PE文件结构

1)手动分析PE文件结构优势

为啥需要手动分析PE文件结构的核心价值,除了.exe,.dll这类常见后缀的PE文件,以及.so,.out 这类ELF文件,我们能通过后缀直观判断文件类型;但在实际场景中,大量恶意文件(如木马、病毒)或自定义加载的代码(如shellcode,后面有时间带这各位写一个如何注入到其他进程中shellcode),会通过修改后缀来规避检测,比如我们可以将virus.exe改为无后缀的virus,或伪装成virus.bin,甚至先加密存储,解密后再偷偷加载到内存中。所以这类文件无法通过后缀直接识别,因此必须掌握手动分析PE结构的能力,才能精准判断文件本质,解析其执行逻辑,从根本上应对格式伪装或恶意篡改的情况。

 

2)查用分析工具

手动分析工具WinHex或者UltraEdit

自动分析工具CFF_Explorer,exeinfope

 

 

3)手动解析PE常见的结构

我们使用vs2022编一个exe,并使用UltraEdit和CFF手动分析一下

#include <stdio.h>

#include <windows.h>

int main() 

{

 printf("Hello World\n");

 system("pause");

 return 0;

}

UltraEdit内容(注意每个人分析的地址都不一样哦,咱们主要把流程搞懂,配合着PE分析工具就行)

 

3.1)Dos头

偏移00-01h

4D 5A(e_magic) -> MZ ,它范围0-3ch

偏移3ch

E8(e_lfanew)-> PE头的位置

 

3.2)PE头(NT节点下面有两个头分别为文件头和可选头)

偏移e8-e9h

50 45 -> PE头的位置,它范围e8-16ch

下面我们看下File Header(文件头)几个值信息范围ec-fe。

偏移ec-edh

86 64 (Machine) -> 目标机器架构(如0x8664表示x64,0x014C表示x86)

偏移ed-eeh

0A (NumberOfSections) -> 节表数量(决定节头的总个数)

偏移feh

00 22 (Characteristics) -> 文件属性(如0x0002表示可执行,0x2000表示DLL)

我们这里是22 00 ,

0x0002:IMAGE_FILE_EXECUTABLE_IMAGE,表示这是一个可执行文件(非目标文件)。

0x00000022:MAGE_FILE_LARGE_ADDRESS_AWARE,表示程序支持大地址空间(在 32 位系统中可访问超过 2GB 的内存,64 位系统中通常默认开启)

两者相加(0x0002 + 0x0020 = 0x0022)

 

下面我们看下Optional Header(可选头)几个值信息范围100-16ch。

偏移100-101h

02 0B (Magic) -> 标识位数(0x10B=32位,0x20B=64位)

偏移110-113h

00 01 12 76 (AddressOfEntryPoint) -> 程序入口点(内存中的RVA)

偏移118-11fh

00 00 00 40 01 00 00 00 00 (BaseOfCode) -> 代码节起始RVA(32位特有)

偏移120-123h

00 01 (SectionAlignment) -> 内存中节对齐粒度(通常为0x1000)

偏移124-127h

00 02 00 00 (FileAlignmentt) -> 磁盘中节对齐粒度(通常为0x200)

偏移16Ch

10 (NumberOfRvaAndSizes) -> 数据目录表项数(固定为0x10)

 

 

3.3)数据目录

Data Directories数据目录表由NumberOfRvaAndSizes有多少个IMAGE_DATA_DIRECTORY结构体组成。该数组包含输入表、输出表、资源、重定位等数据目录项的RVA(相对虚拟地址)和大小。范围170-1e4h。

数据目录表是可选头的最后一个字段,每项指向一个特定功能结构(如导入表、资源表),定义如下:  

typedef struct _IMAGE_DATA_DIRECTORY {

    DWORD VirtualAddress; // 结构在内存中的RVA(相对虚拟地址)

    DWORD Size; // 结构大小(字节)

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;  

我们只需要找到1个值就行,后面的资源表 或是其他表找的方式类似

偏移178h

Import Directory RVA

000213B0

 

 

3.3)节头

Section Headers范围1f0-358h。

Debug模式下面

.textbss

.text

.rdata

.pdata

.idata

在这个节中有两个中值记录下来

偏移2c4-2c7h

00100200

Import Address Table Directory: 00021000

偏移2cc-2cfh

00D80000

Raw Address: 0000D800

更加上面的数据目录得到

This section contains:

Import Directory: 000213B0

.msvcjmc

.00cfg

.rsrc

 

 

3.4)定位导入表:IMAGE_IMPORT_DESCRIPTOR怎么在UltraEdit找到

从之前的节表中可知:

.idata节的Virtual Address(内存起始 RVA):00021000

.idata节的Raw Address(文件起始偏移):0000D800

Import Directory(导入目录的 RVA):000213B0

计算导入目录的文件偏移:

公式:文件偏移 = 导入目录 RVA - .idata 节起始 RVA + .idata 节文件起始偏移

代入数据:

第一步:000213B0(导入目录 RVA) - 00021000(.idata 节起始 RVA) = 000003B0(RVA 相对于 .idata 节内的偏移)

第二步:000003B0 + 0000D800(.idata 节文件起始偏移) = 0000DBB0

 

3.5)结论如何找到定位导入表

在UltraEdit中跳转到文件偏移0000DBB0,即可定位到IMAGE_IMPORT_DESCRIPTOR结构的起始位置。

在WinHex中定位到文件偏移0000DBB0处的字节为50 15 02 00,这是IMAGE_IMPORT_DESCRIPTOR结构中第一个字段(OriginalFirstThunk,原始导入地址表 RVA)的小端序表示。将其转换为大端序后,该字段的数值为00021550,代表原始导入地址表的相对虚拟地址(RVA),用于指向该导入模块的函数导入表结构。这是解析导入表函数列表的关键入口,后续可通过该RVA继续定位具体的导入函数信息。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

  union {

    DWORD Characteristics;

    DWORD OriginalFirstThunk; // 导入名称表 `INT` 的 RVA

  };

  DWORD TimeDateStamp;

  DWORD ForwarderChain;

  DWORD Name; // 库名称字符串 RVA

  DWORD FirstThunk; // 导入地址表 `IAT` 的 RVA

} IMAGE_IMPORT_DESCRIPTOR;

我们顺着找0002188A属于Name字段

 

最终计算步骤:将模块名称RVA转换为文件偏移

计算模块名称RVA相对于 .idata 节的内部偏移公式:”节内偏移 = 模块名称 RVA - .idata 节起始 RVA代入数据:0002188A(模块名称 RVA) - 00021000(.idata 节起始 RVA) = 0000088A“

 

将节内偏移转换为文件偏移公式:”文件偏移 = 节内偏移 + .idata 节文件起始偏移“

代入数据:0000088A(节内偏移) + 0000D800(.idata 节文件起始偏移) = 0000E08A

0000E08A是通过导入模块名称的 RVA(0002188A)转换而来的文件偏移,对应 .idata 节中存储导入模块名称字符串(如DLL 文件名)的具体位置。

 

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值