Windows调试器如何获取目标进程动态函数表列表
Windows动态注册函数表获取方法
在调试器附加到运行中进程、无法提前Hook注册API的场景下,不需要注入目标进程,就可以直接枚举所有通过RtlAddFunctionTable、RtlInstallFunctionTableCallback注册的动态函数表,核心逻辑是利用ntdll.dll内部维护的全局动态函数表链表——两个API注册的所有条目都会被加入这个链表,系统本身调用RtlLookupFunctionEntry查找不存在于PE异常目录的函数表时,也是遍历这个结构获取数据。
具体实现步骤
- 定位ntdll.dll中的链表头
链表头是ntdll的未导出全局变量RtlpDynamicFunctionTableListHead,你可以通过逆向当前系统版本ntdll中RtlAddFunctionTable的函数实现快速定位:RtlAddFunctionTable内部在完成条目填充后,会调用InsertTailList把新节点插入这个链表,顺着InsertTailList的第二个参数就能直接找到链表头地址。
从Win10 1709到最新的Win11 24H2,64位系统下这个全局变量的偏移在不同ntdll版本间没有固定值,但定位逻辑完全通用,不需要硬编码偏移。 - 按节点结构遍历链表
64位系统下链表节点的结构在近10年的Windows版本中保持稳定,可直接使用如下定义解析:typedef enum _RTL_DYNAMIC_FTABLE_TYPE { RtlDynamicFtableNormal = 0, // RtlAddFunctionTable注册的静态数组类型表 RtlDynamicFtableCallback = 1 // RtlInstallFunctionTableCallback注册的回调类型表 } RTL_DYNAMIC_FTABLE_TYPE; typedef struct _RTL_DYNAMIC_FTABLE_ENTRY { LIST_ENTRY Link; // 双向链表节点 PRUNTIME_FUNCTION FunctionTable;// 普通表类型下指向RUNTIME_FUNCTION数组首地址 DWORD EntryCount; // 普通表类型下数组的条目总数 DWORD64 RangeBase; // 当前函数表覆盖的内存地址范围基址 DWORD64 RangeSize; // 当前函数表覆盖的内存地址范围长度 DWORD64 Reserved; PGET_RUNTIME_FUNCTION_CALLBACK CallbackFunc; // 回调表类型下的回调函数指针 PVOID CallbackContext; // 回调表注册时传入的上下文参数 DWORD64 MaxGpReg; RTL_DYNAMIC_FTABLE_TYPE FtableType; PWSTR OutOfProcessCallbackDll; } RTL_DYNAMIC_FTABLE_ENTRY, *PRTL_DYNAMIC_FTABLE_ENTRY; - 遍历过程加入校验逻辑避免崩溃
由于访问的是未导出结构,遍历过程必须做合法性校验,防止不同版本结构差异导致的内存访问错误:- 每拿到一个节点,先调用
VirtualQueryEx校验RangeBase和RangeSize覆盖的内存区域是已提交的可执行内存,过滤无效节点 - 对普通表类型的节点,校验
FunctionTable指向的内存具备可读权限,且数组内每个RUNTIME_FUNCTION记录的起止地址都落在RangeBase~RangeBase+RangeSize区间内 - 对回调表类型的节点,不需要手动解析函数条目,栈展开时如果RIP落在对应地址范围,直接调用保存的
CallbackFunc并传入CallbackContext,就能拿到对应的RUNTIME_FUNCTION结构,和系统原生逻辑完全一致。
- 每拿到一个节点,先调用
兜底兼容方案
如果不想维护不同Windows版本的结构偏移,可以直接实现一套和RtlLookupFunctionEntry逻辑一致的函数表查找逻辑:
对任意待展开的指令地址RIP,先枚举目标进程所有已加载模块,判断RIP是否落在某个模块的地址范围内,如果是则直接读取模块PE头的异常目录,获取对应的
RUNTIME_FUNCTION;如果不在任何已加载模块范围内,再遍历上述动态函数表链表匹配地址范围,普通表用二分查找定位对应条目,回调表调用回调拿条目,最终拿到的结果和系统原生返回结果完全一致,可以直接传入RtlVirtualUnwind完成栈展开。
注意:Win7及更早版本的Windows没有使用双向链表存储动态函数表,而是采用红黑树结构,如果需要兼容这类已停止支持的系统,可以逆向对应版本ntdll中RtlAddFunctionTable的实现定位树的根节点,遍历逻辑和链表类似。
内容的提问来源于stack exchange,提问作者Artem Razin
相关产品推荐
相关产品推荐

