关于Win32 API挂钩与导入地址表的技术疑问咨询
理解纠正与疑问解答
初始理解的正确性验证
你的核心理解大部分是对的,但有一处细节错误:
- 编译时静态导入的函数,会在可执行文件加载时由系统加载器一并处理依赖DLL,函数指针最终填充到**导入地址表(IAT)**中,进程调用时直接从IAT取指针,这部分没问题。
- 运行时通过
LoadLibrary和GetProcAddress加载函数的逻辑正确,但你举的例子有误:MessageBoxA属于User32.dll,而非Kernel32.dll。正确流程是调用LoadLibrary("User32.dll")后,再用GetProcAddress获取MessageBoxA的指针。
疑问解答
1. 进程调用MessageBoxA或CreateFileA时是否需要重复加载DLL?
CreateFileA属于Kernel32.dll,而Kernel32.dll是Windows进程默认加载的核心库之一,调用CreateFileA时无需再次加载Kernel32.dll,直接通过静态导入的IAT指针或动态调用GetProcAddress(用已加载的Kernel32.dll句柄)即可获取函数地址。MessageBoxA属于User32.dll,该库并非所有进程都会默认加载(比如部分后台服务进程)。如果进程是第一次调用MessageBoxA:- 若为静态导入,系统加载器会在进程启动时自动加载
User32.dll; - 若为动态调用,则需要先调用
LoadLibrary("User32.dll")加载该库,之后才能用GetProcAddress获取函数指针。
- 若为静态导入,系统加载器会在进程启动时自动加载
2. 动态加载的函数指针是否有缓存?进程再次调用时是否需要重复调用GetProcAddress?
- Windows系统的动态链接器不会自动缓存
GetProcAddress的返回结果,也不会将动态加载的函数指针写入IAT(IAT仅用于静态导入的函数)。 - 从效率角度,进程自己应该缓存
GetProcAddress返回的指针:比如将指针存储在全局变量或类成员变量中,后续直接使用该指针调用函数,无需重复调用GetProcAddress。虽然GetProcAddress的查询开销不大,但重复调用完全没有必要。 - 补充:
LoadLibrary调用同一DLL时,系统会返回已加载模块的句柄,不会重复加载,这是系统层面的模块缓存机制,但和函数指针缓存是两回事。
3. 如何挂钩动态加载的函数?
挂钩动态加载的函数有两种主流方案,并非只能通过挂钩GetProcAddress:
- 方案1:挂钩
GetProcAddress
拦截GetProcAddress的调用,当检测到目标函数被查询时,将返回的原函数指针替换为你的垫片函数指针,同时保存原指针用于后续调用原函数。这种方式能覆盖所有通过GetProcAddress获取函数地址的场景,但如果进程在挂钩之前已经缓存了目标函数的指针,这种方法就无法拦截后续调用。 - 方案2:Inline Hook(内联挂钩)
直接找到目标函数在内存中的地址,修改函数开头的指令(比如写入跳转指令到你的垫片函数)。这种方式不管函数是静态导入还是动态加载的,只要进程调用该函数就会被拦截。需要注意:- 函数所在的内存页默认是只读的,需要先调用
VirtualProtect修改为可写可执行属性; - 跳转指令的写法要适配目标架构(x86/x64);
- 修改完成后建议将内存页属性改回原状态,保证稳定性。
- 函数所在的内存页默认是只读的,需要先调用
内容的提问来源于stack exchange,提问作者Pieter Jansen
相关产品推荐
相关产品推荐

