

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 文件名)的具体位置。
1416

被折叠的 条评论
为什么被折叠?



