一般我们实现系统功能都是通过调用微软提供的公用 Win32 API 函数来实现。但今天我们采用的方法是通过本机系统服务(Native API)来探测本机系统信息。
Native API 是 Windows 用户模式中为上层 Win32 API 提供接口的本机系统服务。微软没有为我们提供关于本机系统服务的文档 (Undocumented),也就是不会为对它的使用提供任何的保证,所以不提倡使用 Native API 来开发软件。
不过由于通过 Native API 可以实现很多功能,所以相关的参考也可以找到很多。 比如 Gary Nebbett, Windows NT/2000 Native API Reference. New Riders Publishing,
2000. 是一本详细的参考书(@Amazon
),内容可能不是最新的,但非常有价值。 而 The Undocumented Functions
是一个内容充足的在线参考手册。
为了探测系统信息,我们要使用一个函数:NtQuerySystemInformation(与ZwQuerySystemInformation相同,只是入口不同),这是一个属于 ntdll.dll 的函数。
在 MSDN 上对这个函数定义如下:
NTSTATUS WINAPI NtQuerySystemInformation( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __inout PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength );
难点在于各个数据结构是如何定义的。事实上,在不同版本的 Windows 中,这些数据结构的定义是有区别的,这也是微软不建议使用这些 API 的一个原因。
通过我上面给出的资料倒是可以查到这些数据是如何定义的。比如 SYSTEM_INFORMATION_CLASS 是一个集合,如果我想要查看有关系统进程的信息,则要取 5 即 SystemProcessInformation。这个在 winternl.h 也有定义,但是这个头文件中的定义比较保守。更多的信息还需要自己 Google 才行。(比如上文提到的 The Undocumented Functions 中的 SYSTEM_PROCESS_INFORMATION)
对这个函数更详细的说明可以参见:NtQuerySystemInformation(ZwQuerySystemInformation)函数说明,这篇文章也有很全的 SYSTEM_INFORMATION_CLASS 是如何定义的。以及我最早参考的NtQuerySystemInformation,也讲了大部分数据结构如何定义。
至于如果调用这个函数在分析完数据结构后就比较简单了。下面是一个简单的示例,里面还有很多没有说明的类型,不影响理解,进一步查看相关资料就能清楚了。
// 定义类型 typedef NTSTATUS (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG); PROCNTQSI NtQuerySystemInformation; // 调用 NtQuerySystemInformation = (PROCNTQSI)GetProcAddress( GetModuleHandle("ntdll"), "NtQuerySystemInformation" );
网上有很多例子可以参考,第一个是获取每个进程和线程:Processes and Threads Sample,讲解详细(网页上有一个小笔误),提供代码下载,用到了 Windows NT DDK,但是如果用上面我说的方法调用的话就不需要了 DDK 了。研究明白了这个,要点就全掌握了。
还有一个是 Windows NT/2000系统中如何获取系统的启动时间,也有代码下载。
上面提到的 GetProcAddress,GetModuleHandle,要涉及到动态链接库的使用。针对性的,可以参考:3个脱壳相关的重要函数介绍。简单地说就是:
- 如果文件已经被映射进了调用进程的地址空间,则函数 GetModuleHandle 返回指定模块的句柄。注意,GetModuleHandle 是在不增加引用次数的情况下返回已映射模块的句柄。
- LoadLibrary 把模块映射进调用进程的地址空间内。映射进地址空间后,如果必要,则增加模块的引用次数。最后用 FreeLibrary 把它从进程地址空间释放掉。
- 使用 GetProcAddress 函数返回指定的输出动态链接库内的函数地址。
发表评论