http://www.blogcn.com/user8/flier_lu/index.html?id=1404224&run=.0A0B923
经历过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函数定位到某个符号。
几个相关工具短期内可以在这里下载http://flier.5i4k.net/KernelExplorer.rar