x64dbg 的 gpa 命令实战:获取 DLL 导出函数地址的完整指南
【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg
gpa(Get Process Address)是 x64dbg 内置的一条实用命令,用于获取已加载 DLL 中导出函数(Export)的地址。无论是定位kernel32:LoadLibraryW、ntdll.dll:NtQueryInformationProcess这类系统 API,还是在脚本中动态解析导出地址,gpa都是最直接的入口。读完本文,你将掌握gpa的完整语法、$result返回值语义,以及它背后module:export表达式解析与转发导出(Forwarded Export)解析的底层原理,可直接在命令行和脚本中灵活使用。
命令概述:一行命令取到导出地址
gpa是 x64dbg 内置调试命令之一,命令注册于 src/dbg/x64dbg.cpp:
dbgcmdnew("gpa", cbInstrGpa, true); //get proc address其功能正如命令注释所写——"get proc address":获取 DLL 内部某个导出函数的地址。它本质上是一条对"API 表达式解析"的薄封装:把用户传入的导出名(以及可选的 DLL 名)拼装成"模块名":导出名形式的表达式,交给 x64dbg 的表达式解析器求值,再把结果写入$result变量。同时它也被列入脚本命令表 src/dbg/script_commands.txt,因此既能在命令行窗口使用,也能在 x64dbg 脚本中调用。
语法与参数
gpa <ExportName> [DLLName]| 参数 | 含义 | 是否必填 |
|---|---|---|
arg1 | 导出(Export)名称 | 必填 |
arg2 | DLL 名称 | 可选 |
- arg1(导出名):要查找的导出函数名称,例如
LoadLibraryW、VirtualAlloc、NtQueryInformationProcess。导出名不区分大小写(底层按大小写不敏感方式匹配)。 - arg2(DLL 名,可选):限定在哪个模块中查找。若省略,x64dbg 会枚举当前已加载的所有模块,逐个查找匹配的导出;若提供了 DLL 名,则只在指定模块中解析。
返回值:$result 变量
gpa执行结束后会设置$result变量:
- 找到导出:
$result被设置为该导出函数的实际加载地址(虚拟地址); - 未找到导出:
$result被设置为0。
因此脚本中惯用的判断写法是:
gpa LoadLibraryW kernel32 cmp $result, 0 ; 为 0 表示未找到源码剖析:cbInstrGpa 的实现
gpa的实际处理函数是cbInstrGpa,定义在 src/dbg/commands/cmd-misc.cpp,完整实现如下:
bool cbInstrGpa(int argc, char* argv[]) { if(IsArgumentsLessThan(argc, 2)) return false; char newcmd[deflen] = ""; if(argc >= 3) sprintf_s(newcmd, "\"%s\":%s", argv[2], argv[1]); else sprintf_s(newcmd, "%s", argv[1]); duint result = 0; if(!valfromstring(newcmd, &result, false)) return false; varset("$RESULT", result, false); return true; }这段代码揭示了gpa的完整工作流程:
- 参数检查:通过
IsArgumentsLessThan(argc, 2)确保至少提供了导出名(命令本身算argv[0],导出名是argv[1])。 - 拼装表达式:
- 如果提供了 DLL 名(
argc >= 3),拼装为"DLL名":导出名的形式,例如"kernel32":LoadLibraryW——用双引号包裹模块名,是为了防止模块名中出现空格或特殊字符破坏表达式语法; - 如果省略 DLL 名,则直接把导出名作为表达式传入。
- 如果提供了 DLL 名(
- 表达式求值:调用
valfromstring解析该字符串,得到地址result。 - 写入结果:通过
varset("$RESULT", result, false)将地址写入$result变量。
函数声明位于 src/dbg/commands/cmd-misc.h。可以看出,gpa是"命令行语法糖":它让用户免去手写"kernel32":LoadLibraryW这种带引号的表达式,直接以gpa LoadLibraryW kernel32更自然地表达"取某模块某导出地址"。
底层原理:valapifromstring 支持的寻址语法
gpa的真正解析逻辑位于表达式求值链路中的valapifromstring(src/dbg/value.cpp)。该函数是 x64dbg 表达式系统解析 API 地址的统一入口,在valfromstring的解析顺序中优先级很高(位于插件回调之后、标签与符号解析之前,见 src/dbg/value.cpp)。理解它,就等于掌握了gpa(以及一切模块:导出表达式)的所有能力边界。
1. 显式格式模块:导出
冒号:是最明确的定界符。Windows 文件路径中不能包含:(详见 MSDN 命名约定),因此它不会与路径冲突:
gpa LoadLibraryW kernel32 ; 内部拼装为 "kernel32":LoadLibraryW模块名部分还支持以下特殊写法:
| 写法 | 含义 |
|---|---|
:导出名(模块名为空) | 解析当前反汇编视图选中模块的导出 |
模块:base/模块:imagebase/模块:header | 获取模块加载基址 |
模块:entrypoint/模块:entry/模块:oep/模块:ep | 获取模块入口点 |
模块:$RVA | 模块基址 + 指定 RVA($前缀) |
模块:#FileOffset | 把文件偏移转换为虚拟地址(#前缀) |
例如在gpa中省略第二个参数、直接写gpa VirtualAlloc时,x64dbg 会走"无模块名"分支:枚举所有已加载模块查找该导出,并对命中结果按规则优先返回 kernel32 的导出(其余命中会打印到日志窗口),见 src/dbg/value.cpp。
2. 兼容格式模块.导出与?分隔符
kernel32.GetProcAddress这种点号格式同样受支持(为兼容 OllyDbg 风格的 API 表达式),通过strstr(name, "..") ? strchr(name, '.') : strrchr(name, '.')定位定界符;模块?导出:使用?作为定界符时,禁用转发导出解析(resolveForwards = false),用于获取导出表项本身的原始地址而不是转发目标地址。
3. 序号导出:模块:序号
除了按名称查找,valapifromstring还支持按导出序号寻址:
模块:0x1A:按十六进制序号查找(默认 16 进制);模块:.26:带.前缀表示十进制序号;模块:0:特例,返回模块基址。
底层通过modInfo->exportOrdinalBase换算序号在导出表中的索引,见 src/dbg/value.cpp。
转发导出与 API Set 解析
gpa解析到的地址未必是"导出表里写的那个地址"。MODINFO::getProcAddress(src/dbg/module.cpp)在处理命中项时会检查转发导出(forwarded export):如果某导出项被标记为转发(如forwardName形如KERNEL32.HeapAlloc),它会递归解析到真正的目标模块与目标导出;若目标属于api-*/ext-*前缀的 API Set 虚拟模块,还会先通过resolveApiSetForward解析 API Set 重定向。转发深度由maxForwardDepth控制,逐层递减防止死循环。
这意味着在 Windows 10+ 上执行:
gpa CreateFileW api-ms-win-core-file-l1-1-0得到的将是重定向后的真实实现地址(通常是KERNELBASE.dll中的实现),这正是逆向分析时需要的"实际调用目标"。
实战示例
命令行窗口直接使用
在调试会话中(gpa依赖调试上下文,必须在调试状态下使用),命令窗口输入:
gpa LoadLibraryW kernel32执行后日志无输出(成功时不打印),但$result已被设置为kernel32.dll中LoadLibraryW的地址,可用var $result查看。
在脚本中使用
由于gpa在 src/dbg/script_commands.txt 中被标记为脚本命令,可直接写入脚本:
gpa NtQueryInformationProcess ntdll.dll cmp $result, 0 je notfound log "NtQueryInformationProcess = {$result}" notfound: log "export not found"典型场景是:脚本先取GetProcAddress之类关键 API 的地址,再结合SetBPX下条件断点,或与call等脚本命令配合完成动态调用。
在表达式中直接使用(等价写法)
gpa只是表达式语法的一层封装,任何需要地址的场景都可以直接写表达式:
"kernel32":LoadLibraryW ; 冒号定界 kernel32.HeapAlloc ; 点号定界(OllyDbg 风格) kernel32:0x1A ; 序号寻址实际上 x64dbg 内部代码也大量采用这一表达式,例如loadlib命令加载 DLL 前先解析"kernel32":LoadLibraryW取得函数地址(src/dbg/commands/cmd-misc.cpp),getcommandline相关逻辑解析kernelBase:GetCommandLineA与kernel32:GetCommandLineA(src/dbg/debugger.cpp)。可见module:export表达式是整个调试器内部公用的寻址机制,gpa只是把它开放给了用户。
注意事项与限制
- 必须在调试状态下使用:
valapifromstring首先检查DbgIsDebugging(),未调试时直接返回失败(src/dbg/value.cpp); - 模块必须已加载:
gpa查找的是进程当前已加载模块的导出表,ModBaseFromName找不到目标模块时解析失败,返回 0;尚未加载的 DLL 可先用loadlib加载; - 失败时
$result为 0:当导出不存在、模块未加载或参数不足时,cbInstrGpa返回false且$result保持0,脚本务必对0做防御判断; - 省略 DLL 名时的多命中:不指定模块会枚举所有模块,命中多个时优先返回 kernel32 的导出,其余命中会打印到日志,可通过日志观察候选地址(src/dbg/value.cpp);
- 无参数时静默失败:只输入
gpa不带任何参数会被参数检查直接拦截(IsArgumentsLessThan),不会产生任何输出。
小结
gpa以极简的接口(导出名 + 可选模块名)封装了 x64dbg 强大的 API 表达式解析能力:从模块:导出、点号兼容格式、序号寻址,到转发导出与 API Set 重定向,其底层valapifromstring+MODINFO::getProcAddress构成了完整的导出寻址链路。掌握gpa,不仅能在命令行和脚本中快速取得 API 地址,更理解了整个 x64dbg 表达式系统中module:export语法的运作机理,是逆向调试中高频、实用的基础技能。
【免费下载链接】x64dbgAn open-source user mode debugger for Windows. Optimized for reverse engineering and malware analysis.项目地址: https://gitcode.com/gh_mirrors/x6/x64dbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考