本文是我整理逆向工程学习周报的第一篇,系统梳理了 Windows PE 文件结构、常见文件格式魔数、Win32 GUI 程序基本原理、常用 Windows API 以及 .NET 程序逆向基础。内容适合作为逆向入门的知识点参考,并附有实际分析中的注意要点。

一、PE 文件结构详解

Windows 平台下的可执行文件(如 exedllsysocxcom 等)均采用 PE(Portable Executable)文件结构。PE 结构主要由四大部分组成:

  • DOS 头:包含 MZ 标识和 e_lfanew 字段,用于定位 PE 头。
  • PE 头:包含 PE 标识、文件头(IMAGE_FILE_HEADER)、可选头(IMAGE_OPTIONAL_HEADER)等。
  • 节表(Section Table):描述各节区的位置、大小和属性。
  • 节区数据:如 .text(代码)、.data(数据)、.rdata(只读数据)等实际内容。

1. DOS 头(DOS Header)

DOS 头是 PE 文件的开头部分,兼容古老的 MS-DOS 系统。关键字段:

字段 偏移 大小 说明
e_magic 0x00 2 字节 魔数 MZ,十六进制 4D 5A(小端存储)
e_lfanew 0x3C 4 字节 指向 PE 头的文件偏移量

注意e_lfanew 的值是相对于文件开头的偏移,不是 RVA。在分析时需结合节表转换为文件偏移或虚拟地址。

2. DOS 存根(DOS Stub)

DOS 存根是一段 16 位汇编代码,当程序在 DOS 环境下运行时,输出 "This program cannot be run in DOS mode" 并退出。该区域在正常情况下没有实际用途,但可以被利用来植入恶意代码或自定义数据。

3. PE 头(PE Header)

e_lfanew 指向的位置,首先是 PE 标识:

  • PE 标识50 45 00 00,即 ASCII 字符串 PE\0\0,用于确认这是一个 PE 文件。

紧接着是 IMAGE_FILE_HEADER(文件头),包含 7 个参数,使用 WORD(2 字节)和 DWORD(4 字节)类型:

字段 大小 说明
Machine WORD 目标 CPU 类型,如 0x14C(x86)、0x8664(x64)
NumberOfSections WORD 节区数量
TimeDateStamp DWORD 编译时间戳
PointerToSymbolTable DWORD 符号表偏移(通常为 0)
NumberOfSymbols DWORD 符号数量(通常为 0)
SizeOfOptionalHeader WORD 可选头大小
Characteristics WORD 文件属性标志,如可执行、DLL 等

随后是 IMAGE_OPTIONAL_HEADER(可选头),尽管名字叫可选,但对于 PE 文件来说实际上是必需的。关键字段包括:

  • Magic0x10B(32 位 PE)、0x20B(64 位 PE+)
  • AddressOfEntryPoint:程序入口点 RVA
  • ImageBase:首选加载基址
  • SectionAlignment / FileAlignment:节区对齐值
  • Subsystem:子系统类型(如 GUI、CUI)
  • DataDirectory:数据目录表,包含导入表、导出表、重定位表等重要结构的 RVA 和大小

4. 节表(Section Table)

节表是一个 IMAGE_SECTION_HEADER 结构数组,每个结构描述一个节区。关键字段:

  • Name:节区名称(最多 8 字节)
  • VirtualSize:节区在内存中的大小
  • VirtualAddress:节区在内存中的 RVA
  • SizeOfRawData:节区在文件中的大小
  • PointerToRawData:节区在文件中的偏移
  • Characteristics:节区属性(如可读、可写、可执行)

在逆向时,经常需要将 RVA 转换为文件偏移(或反之),公式为:
文件偏移 = RVA - VirtualAddress + PointerToRawData

5. 数据目录表(Data Directory)

数据目录表位于可选头末尾,是一个 IMAGE_DATA_DIRECTORY 结构数组,包含 16 个条目,常见的有:

索引 名称 用途
0 Export Table 导出函数信息
1 Import Table 导入函数信息
2 Resource Table 资源信息
5 Base Relocation Table 重定位信息
9 TLS Table 线程局部存储信息
12 Import Address Table 导入地址表(IAT)

alt text
导入函数表记录了这个程序运行时要加载哪些DLL,以及要调用DLL里的哪些函数。比如你的程序用了 MessageBox,链接器会把 user32.dll 和 MessageBox 写进导入表。程序启动时,Windows加载器会加载这些DLL,并把函数地址填到导入地址表(IAT)中,程序才能正确调用。
alt text
导出函数表是外部访问该函数的“入口”

二、ELF 文件与常见魔数

1. ELF 文件

Linux 平台的可执行文件格式为 ELF(Executable and Linkable Format)。其魔数为 7f 45 4c 46,对应的 ASCII 字符串为 ^?ELF。ELF 文件结构包括 ELF 头、程序头表、节头表等,与 PE 结构类似但有所不同。

2. 文件魔数(Magic Number)

许多文件类型的起始几个字节是固定的,用于快速识别文件类型。常见魔数如下:

文件类型 魔数
ELF 7f 45 4c 46
tar 第 257 字节起为 ustar
PE 0x00004550(即 PE\0\0
Java .class 0xCAFEBABE
PNG 89 50 4E 47 0D 0A 1A 0A
JPEG FF D8 FF
ZIP 50 4B 03 04
RAR 52 61 72 21
GZIP 1F 8B

在逆向中,通过魔数可以快速识别文件真实类型,即使文件扩展名被篡改。

三、Windows GUI 程序基础

1. WinMain 入口

Windows GUI 程序的入口函数为 WinMain(控制台程序为 main)。原型如下:

1
2
3
4
5
6
int WINAPI WinMain(
HINSTANCE hInstance, // 当前程序实例句柄
HINSTANCE hPrevInstance, // 前一个实例句柄(通常为 NULL)
LPSTR lpCmdLine, // 命令行参数
int nCmdShow // 窗口初始显示状态
);

2. 创建窗口的典型流程

定义窗口类:填充 WNDCLASS 或 WNDCLASSEX 结构体,指定窗口过程函数、背景色、图标、光标、类名等。
注册窗口类:调用 RegisterClass 将窗口类注册到系统。
创建窗口:调用 CreateWindow 或 CreateWindowEx,根据窗口类创建实际窗口。
显示窗口:调用 ShowWindow 显示窗口,并调用 UpdateWindow 触发重绘。
消息循环:通过 GetMessage / TranslateMessage / DispatchMessage 不断从消息队列获取消息并分派给窗口过程函数。

3. WNDCLASS 结构体关键字段

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _WNDCLASS {
UINT style; // 窗口类样式
WNDPROC lpfnWndProc; // 窗口过程函数指针
int cbClsExtra; // 类额外内存
int cbWndExtra; // 窗口额外内存
HINSTANCE hInstance; // 实例句柄
HICON hIcon; // 图标
HCURSOR hCursor; // 光标
HBRUSH hbrBackground; // 背景画刷
LPCSTR lpszMenuName; // 菜单名称
LPCSTR lpszClassName; // 窗口类名
} WNDCLASS;

其中 lpfnWndProc 是核心,它负责处理所有发送到该窗口的消息(如鼠标点击、键盘输入、绘制等)。