iat是pe文件中由image_import_descriptor等结构组成的逻辑视图,非裸字节区域;直接fopen+fwrite会跳过节对齐、rva转offset、校验和重算等关键步骤,导致加载失败。

什么是 IAT,为什么不能直接用 fopen + fwrite 改
Windows PE 文件的导入函数表(IAT)不是一段可随意覆盖的裸字节区域,而是由多个结构体(IMAGE_IMPORT_DESCRIPTOR、IMAGE_THUNK_DATA 等)组成的、受重定位和绑定影响的逻辑视图。直接用 C++ 标准文件操作读写,会跳过节对齐、数据目录偏移、RVA 转 VA 等关键映射步骤,大概率写错位置、破坏校验和、导致加载失败或崩溃。
- PE 文件头里
OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]指向 IAT 元信息,不是 IAT 本身内容 - IAT 实际存储在 .rdata 或 .idata 节中,需先解析节表找到物理偏移
- 修改后必须重算
OptionalHeader.CheckSum,否则 Windows 加载器可能拒绝加载(尤其启用 /DYNAMICBASE 或签名验证时)
怎么安全定位并解析 IAT 的真实地址
核心是把文件按 PE 格式“解包”,而不是当普通二进制流处理。需手动解析 DOS 头 → NT 头 → 可选头 → 数据目录 → 导入描述符数组。
- 用
fread读完整文件到内存(确保大小 ≥NtHeaders->OptionalHeader.SizeOfImage),避免分段读取导致 RVA 计算错乱 -
IMAGE_NT_HEADERS偏移 =*((DWORD*)fileBuf)(即e_lfanew字段),别硬写 0x3C - 导入描述符数组起始 RVA =
NtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress,再用RvaToOffset()转为文件偏移 —— 这个转换必须基于节表,不能简单加基址 - 每个
IMAGE_IMPORT_DESCRIPTOR后紧跟两个并行的 thunk 数组(OriginalFirstThunk和FirstThunk),多数工具只改后者,但 loader 实际用FirstThunk查函数地址
修改 IAT 条目时最容易崩的三个点
不是改了字符串就完事;loader 在 LoadLibrary/GetProcAddress 阶段才真正依赖这些字段,出错往往延迟报。
- 字符串必须以 null 结尾且位于可读节内:改
FirstThunk指向的新函数名,要确保该字符串所在内存页有PAGE_READWRITE权限(若注入到运行进程)或写入文件时落在 .rdata 节范围内(若离线 patch) - 不能只改一个 thunk:
OriginalFirstThunk用于显示原始符号名(调试/依赖分析),FirstThunk用于运行时调用;两者应同步更新,否则 Dependency Walker 会报“Import Address Table mismatch” - 新增导入项必须对齐:每个
IMAGE_IMPORT_DESCRIPTOR占 20 字节,末尾用全 0 表示结束;若扩展导入表,需保证新条目对齐到节边界,并更新SizeOfImage和节的SizeOfRawData,否则加载器读越界
用 SetFilePointer + WriteFile 写回文件前必做的三件事
Windows API 写文件比 std::ofstream 更可控,但依然要绕过缓存、对齐、校验三关。
- 打开文件必须用
CREATE_ALWAYS或TRUNCATE_EXISTING,且dwFlagsAndAttributes设为FILE_ATTRIBUTE_NORMAL,避免 NTFS 压缩/加密干扰字节布局 - 写入前调用
SetFilePointer(hFile, offset, nullptr, FILE_BEGIN)定位,offset 必须是文件偏移(非 RVA),且需确保 offset ≤ 当前文件大小(否则 WriteFile 失败) - 写完立即调用
CheckSumMappedFile(imagehlp.h)重算校验和,并写回OptionalHeader.CheckSum字段;漏这步,某些安全策略或系统服务会静默拒绝加载
底层改 IAT 不是替换几个字节的事,真正难的是保持整个 PE 结构语义一致。哪怕只动一个函数名,也得确认节权限、thunk 同步、校验和、重定位有效性 —— 少一环,文件就变成无法加载的“死镜像”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











