应逐个读取 IMAGE_IMPORT_DESCRIPTOR 数组,检查 OriginalFirstThunk 和 FirstThunk 是否均为 0 来终止遍历,避免越界;OriginalFirstThunk 更可靠,Name 为 RVA 需加基址;注意 Hint/Name 与 Ordinal 导入区分。

怎么用 IMAGE_IMPORT_DESCRIPTOR 读导入表,别跳过空项
Windows PE 文件的导入表不是连续内存块,而是一组以全零 IMAGE_IMPORT_DESCRIPTOR 结尾的结构数组。直接按固定数量遍历会越界或漏掉最后一组导入。
正确做法是逐个读取,直到遇到 FirstThunk == 0 && OriginalFirstThunk == 0(或两者都为 0)才停止。注意:某些加壳或手工构造的 PE 可能只填了其中一个字段,所以建议两个都检查。
-
OriginalFirstThunk指向未绑定前的IMAGE_THUNK_DATA数组(含 Hint/Name),更可靠;FirstThunk是运行时被 loader 填充的函数地址,文件中可能为空 - 每个
IMAGE_THUNK_DATA的低 32 位若最高位为 1(即AddressOfData & 0x80000000),说明是 Hint/Name 方式;否则是序号导入(Ordinal) - 从
Name字段读 DLL 名称时,该值是 RVA,必须加上 PE 映像基址(如base + importDesc->Name)才能访问字符串
导出表解析关键在 IMAGE_EXPORT_DIRECTORY 和三个 RVA 数组对齐
导出表结构紧凑但索引关系容易搞混:AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals 三个数组长度不同,且全部是 RVA —— 忘记加基址会导致读到乱码或崩溃。
典型错误是把 NumberOfNames 当作函数总数,其实真正函数数是 NumberOfFunctions;而 NumberOfNames 只是“有名字的导出函数”个数,其余是纯序号导出(无名称)。
立即学习“C++免费学习笔记(深入)”;
Windows 命令行编码与兼容规范。涵盖 GBK/UTF-8 编码、PowerShell/pwsh 互操作、Python/Node.js、Git 配置及代码生成规则。适用于 Windows 10/11 + MSYS2/Git Bash 环境下的所有命令行操作。
- 先校验
Characteristics == 0且TimeDateStamp != 0,排除无效或伪造导出表 -
AddressOfNames存的是函数名 RVA 数组(每个元素 4 字节),AddressOfNameOrdinals存的是对应序号(每个元素 2 字节),二者长度均为NumberOfNames - 查某个函数名时,需二分搜索
AddressOfNames数组,拿到下标i,再用i去AddressOfNameOrdinals中取实际序号ord,最后用ord查AddressOfFunctions得到函数 RVA
资源节区(.rsrc)是树形结构,不能线性扫描
.rsrc 节不是扁平列表,而是三级嵌套的资源目录树:类型 → 名称/ID → 语言。硬编码偏移或假设层级深度会直接失败 —— 比如图标、版本信息、对话框模板全挤在同一级目录里,但实际路径是 Type → Name → Language。
每个目录项(IMAGE_RESOURCE_DIRECTORY_ENTRY)的 u.s.NameIsString 位决定后续是字符串还是 ID;u.s.OffsetToDirectory 位决定下一级是子目录(置位)还是叶子数据(未置位)。
- 资源数据本身不存于目录结构中,最终要走到叶子节点,读
IMAGE_RESOURCE_DATA_ENTRY,它含OffsetToData(RVA)、Size、CodePage - 常见坑:没处理“名称为整数 ID”的情况(如
RT_ICON、RT_VERSION是预定义宏,值为 3、16),误当字符串去读导致访问非法地址 - 版本资源(
VS_VERSIONINFO)是二进制块,需按WORD对齐解析,其中szKey字段是 Unicode 字符串,长度不固定,后面紧跟子块
所有 RVA 都得转 VA,但基址不是 ImageBase 那么简单
PE 文件加载后虚拟地址 = 文件映射基址 + RVA,但解析静态文件时,“映射基址”不是 OptionalHeader.ImageBase,而是你 LoadLibrary 或 MapViewOfFile 后实际得到的基址;如果只是读磁盘文件,就得用文件偏移转换(通过节表计算)。
多数工具链(如 MinGW、MSVC)默认生成的 PE 使用 ImageBase = 0x400000,但 ASLR 或重定位后真实加载地址完全不同。硬编码 0x400000 解析内存镜像必然出错。
- 读磁盘文件:用
RVA → Section → PointerToRawData + (RVA - VirtualAddress)算文件偏移 - 读内存镜像(如
GetModuleHandle(NULL)):用实际模块句柄地址作为基址,而非ImageBase - 务必检查节表中各节的
VirtualAddress和SizeOfRawData是否对齐(尤其.rsrc常被压缩或错位对齐)
资源和导入/导出都依赖精确的 RVA 计算,这里出一丁点偏差,后面全崩。别图省事跳过节头校验。

















