如何在纯机器码PE程序中调用Windows DLL(API)函数?
Windows系统中,PE文件调用DLL函数的核心是导入表(Import Table)和导入地址表(IAT, Import Address Table),这是PE文件的标准结构,完全由系统加载器负责解析和填充,和编译器无关。以下是关键细节:
导入表的核心作用:
PE文件通过导入表声明依赖的DLL(比如user32.dll、kernel32.dll)以及需要调用的函数名/序号。当系统加载你的PE程序时,会先定位并加载所有依赖的DLL,然后遍历导入表,将每个函数的实际内存地址写入到IAT中。手动构造PE导入表的关键结构:
你需要在PE的IMAGE_OPTIONAL_HEADER的DataDirectory数组中,设置IMAGE_DIRECTORY_ENTRY_IMPORT项,指向导入表的相对虚拟地址(RVA)。导入表由IMAGE_IMPORT_DESCRIPTOR数组组成,每个元素对应一个依赖DLL,包含:- 指向DLL名称字符串的RVA
- 指向
IMAGE_THUNK_DATA数组的RVA(加载前存储函数的导入信息,加载后被系统替换为函数的实际内存地址)
其中IMAGE_THUNK_DATA如果是按名称导入,会指向IMAGE_IMPORT_BY_NAME结构(包含函数名);按序号导入则直接存储序号值。
机器码中调用WinAPI的具体方式:
系统加载完成后,IAT中已经是函数的实际地址。x64下调用的机器码格式为FF 15 xx xx xx xx,这是call [rip+offset]的指令,其中offset是当前指令到IAT中目标函数地址的位移。比如调用MessageBoxA,你只需要计算好IAT中对应项的地址偏移,生成对应的机器码即可。x64调用约定的强制要求:
WinAPI使用fastcall约定,前4个参数依次放入RCX、RDX、R8、R9寄存器,剩余参数从右到左压入栈;调用前必须预留32字节的栈阴影空间(即使参数少于4个),调用后不需要手动清理栈(由被调用函数负责)。调试与验证技巧:
可以用dumpbin /imports <合法PE文件>查看标准导入表结构,作为手动构造的参考;用x64dbg加载自己生成的PE,查看IAT是否被正确填充,以及函数调用的指令是否正确。
内容的提问来源于stack exchange,提问作者interstellarfrog

