自动获取 NT 系统服务描述表与函数名映射表

来源:互联网 发布:女友是韩国人知乎 编辑:程序博客网 时间:2024/06/07 03:15

转自Flier's sky

http://www.cnblogs.com/flier/archive/2004/07/08/22311.html

 

经历过DOS时代人朋友一定还记得内存开始处那个神奇的ISR映射表,其实NT内部的系统服务描述表(System Service Descriptor Table)也起着类似的作用。用户态程序调用系统服务时,通过某种机制(以前是INT 2EH,XP/2003改为SYSENTER/SYSCALL指令)进入核心态,然后系统根据系统服务号查表调用相应函数。
     以前碰到要查一个服务号对应函数名时,都是Ctrl+D启动SoftICE,然后加载相应符号库手工查询。前段时间重装2003系统后,使用SoftICE一直有各种各样莫名其妙的问题,搞得很是不爽。为了查一个服务号的函数名,还得跑到别人机器上,麻烦得要死。于是响应毛主席号召,自己动手丰衣足食,呵呵,写了一个小程序自动获取 NT 系统服务描述表与函数名映射表。
     实现思路起始很简单,每一步的技术也都没什么难度,就是...麻烦...sigh

     1.定位内核的ntoskrnl.exe模块
     2.载入调试符号并查找KeServiceDescriptorTable符号地址
     3.读取KeServiceDescriptorTable内容并定位描述表地址
     4.打印描述表,对每个入口地址通过符号库查询响应函数名

     下面具体说说每一步的实现思路

 1.定位ntoskrnl.exe模块

     最简单的方法莫过于使用NTDLL提供的ZwQuerySystemInformation函数查询SystemModuleInformation信息,一次性将内核态所有模块的信息读入,呵呵,后面查询函数入口地址时也会用到这些信息。

以下为引用:

 NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation(
     IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
     OUT PVOID SystemInformation,
     IN ULONG SystemInformationLength,
     OUT PULONG ReturnLength OPTIONAL);

 typedef struct _SYSTEM_MODULE_INFORMATION { // Information Class 11
     ULONG Reserved[2];
     PVOID Base;
     ULONG Size;
     ULONG Flags;
     USHORT Index;
     USHORT Unknown;
     USHORT LoadCount;
     USHORT ModuleNameOffset;
     CHAR ImageName[256];
 } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;



     相关使用方法网上讨论很多,或请参考NT Native API一书,这儿就不再罗嗦。

 2.载入调试符号并查找KeServiceDescriptorTable符号地址

     MS提供的DbgHelp库还是很好用的,呵呵,直接载入你安装的符号库,查询符号信息。

     首先用SymInitialize函数初始化一下符号库引擎,对应最后需要用SymCleanup函数清除;然后用SymSetSearchPath函数设置符号库搜索路径,也可以使用_NT_SYMBOL_PATH环境变量指定;对需要载入符号库的模块,调用SymLoadModule函数载入对应符号库,并使用SymGetModuleInfo函数获取符号库信息;再使用SymEnumSymbols函数枚举模块中的符号;对我们需要的符号,可以通过SYMBOL_INFO.Address得到符号在内存中的地址。

 3.读取KeServiceDescriptorTable内容并定位描述表地址

     KeServiceDescriptorTable符号指向一个KSERVICE_TABLE_DESCRIPTOR结构,定义系统服务描述表的函数入口表和参数表的地址:

以下为引用:

 typedef struct _KSERVICE_TABLE_DESCRIPTOR {
     PULONG_PTR Base;
     PULONG Count;
     ULONG Limit;
     PUCHAR Number;
 } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
 



     Limit存储此服务描述表中有多少项;
     Base指向函数入口表地址,每个表项是一个DWORD,表示一个函数入口地址;
     Number指向函数参数表地址,每个表项是一个UCHAR,表示一个函数参数长度。

     但因为此结构存储在2G以上地址空间中,为核心态内存,无法在用户态访问,所以我们必须想办法直接读取核心态内存。

     思路起始也很简单,呵呵,将KeServiceDescriptorTable符号所指向的虚拟空间地址转换为物理地址,然后通过读取DevicePhysicalMemory设备直接访问物理内存。网上以前也有过很多讨论文章。

     虚拟地址向物理地址转换的方法,webcrazy以前有过精彩论述《小议Windows NT/2000分页机制》。但因为我的程序不准备在核心态跑,所以使用一个简化的经验转换算法。具体原理请参考webcrazy的文章和MmGetPhysicalAddress函数(ntosmmiosup.c:5490)的实现代码。

以下为引用:

 PHYSICAL_ADDRESS TPhysicalMemoryMapping::LinearAddressToPhysicalAddress(LPCVOID lpVirtualAddress)
 {
   PHYSICAL_ADDRESS addr = { 0, 0 };

   if((DWORD)lpVirtualAddress < 0x80000000L || (DWORD)lpVirtualAddress >= 0xA0000000L)
     addr.QuadPart = (DWORD)lpVirtualAddress & 0x0FFFF000;
   else
     addr.QuadPart = (DWORD)lpVirtualAddress & 0x1FFFF000;

   return addr;
 }

 

     读取物理内存实际上就是使用NtOpenSection函数打开NT内建/Device/PhysicalMemory,然后将此Section中需要访问的内存页用NtMapViewOfSection函数映射到用户态空间中,就可以直接读取。最后再相应调用NtUnmapViewOfSection函数和NtClose函数关闭映射。

 4.打印描述表,对每个入口地址通过符号库查询响应函数名

     将描述表读取后,就可以根据每个函数入口地址,先定位到某个模块,再使用SymFromAddr函数定位到某个符号。

 几个相关工具短期内可以在这里下载::URL::http://flier.5i4k.net/KernelExplorer.rar