|
typedef struct _SPY_VERSION_INFO
{
DWORD dVersion;
WORD awName [SPY_NAME];
}
SPY_VERSION_INFO, *PSPY_VERSION_INFO, **PPSPY_VERSION_INFO;
#define SPY_VERSION_INFO_ sizeof (SPY_VERSION_INFO)
NTSTATUS SpyOutputVersionInfo (PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_VERSION_INFO svi;
svi.dVersion = SPY_VERSION;
wcscpyn (svi.awName, USTRING (CSTRING (DRV_NAME)), SPY_NAME);
return SpyOutputBinary (&svi, SPY_VERSION_INFO_,
pOutput, dOutput, pdInfo);
}
列表 4-12. 获取 Spy 驱动程序的版本信息
IOCTL 函数 SPY_IO_OS_INFO
该函数比上一个有趣的多。它是另一个只有输出的函数,不需要输入参数,使用几个操作系统的内部参数来填充调用者提供的 SPY_OS_INFO 结构。 列表 4-13 列出了该结构的定义,和 Dispatcher 调用的 SpyOutputOsInfo() 帮助函数。有些结构体成员只是被简单的设为定义于 DDK 头文件和 w2k_spy.h 中的常量;其他的将被设为从几个内部的内核变量和结构体中读取的当前值。在第二章中,你已经了解了变量 NtBuildNumber 和 NtGlobalFlag (由 ntoskrnl.exe 导出,参见 附录 B 中的 表 B-1 )。和其他的 Nt* 符号不同,这两个符号不指向 API 函数,而是指向位于内核的 .data section 中的变量。在 Win32 世界里,导出变量是十分罕见的。不过, Windows 2000 的几个内核模块都使用了这一技术。 Ntoskrnl.exe 导出了至少 55 个变量, ntdll.dll 提供了 4 个, hal.dll 提供了 1 个。 SpyOutputOsInfo() 将从 ntoskrnl.exe 导出的变量中复制 MmHighestUserAddress 、 MmUserProbeAddress 、 MmSystemRangeStart 、 NtGlobalFlag 、 KeI386MachineType 、 KeNumberProcessors 和 NtBuildNumber 到输出缓冲区中。
当一个模块从另一个模块中导入数据时,它需要使用 extern 关键字来通知编译器和链接器。这会使链接器生成一个进入模块导出节的入口,并会解析符号名以确定其地址。有些 extern 声明已经包含在 ntddk.h 。 列表 4-13 给出了缺失的那些 extern 声明。
extern PWORD NlsAnsiCodePage;
extern PWORD NlsOemCodePage;
extern PWORD NtBuildNumber;
extern PDWORD NtGlobalFlag;
extern PDWORD KeI386MachineType;
typedef struct _SPY_OS_INFO
{
DWORD dPageSize;
DWORD dPageShift;
DWORD dPtiShift;
DWORD dPdiShift;
DWORD dPageMask;
DWORD dPtiMask;
DWORD dPdiMask;
PX86_PE PteArray;
PX86_PE PdeArray;
PVOID pLowestUserAddress;
PVOID pThreadEnvironmentBlock;
PVOID pHighestUserAddress;
PVOID pUserProbeAddress;
PVOID pSystemRangeStart;
PVOID pLowestSystemAddress;
PVOID pSharedUserData;
PVOID pProcessorControlRegion;
PVOID pProcessorControlBlock;
DWORD dGlobalFlag;
DWORD dI386MachineType;
DWORD dNumberProcessors;
DWORD dProductType;
DWORD dBuildNumber;
DWORD dNtMajorVersion;
DWORD dNtMinorVersion;
WORD awNtSystemRoot [MAX_PATH];
}
SPY_OS_INFO, *PSPY_OS_INFO, **PPSPY_OS_INFO;
#define SPY_OS_INFO_ sizeof (SPY_OS_INFO)
NTSTATUS SpyOutputOsInfo (PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_SEGMENT ss;
SPY_OS_INFO soi;
NT_PRODUCT_TYPE NtProductType;
PKPCR pkpcr;
NtProductType = (SharedUserData->ProductTypeIsValid
? SharedUserData->NtProductType
: 0);
SpySegment (X86_SEGMENT_FS, 0, &ss);
pkpcr = ss.pBase;
soi.dPageSize = PAGE_SIZE;
soi.dPageShift = PAGE_SHIFT;
soi.dPtiShift = PTI_SHIFT;
soi.dPdiShift = PDI_SHIFT;
soi.dPageMask = X86_PAGE_MASK;
soi.dPtiMask = X86_PTI_MASK;
soi.dPdiMask = X86_PDI_MASK;
soi.PteArray = X86_PTE_ARRAY;
soi.PdeArray = X86_PDE_ARRAY;
soi.pLowestUserAddress = MM_LOWEST_USER_ADDRESS;
soi.pThreadEnvironmentBlock = pkpcr->NtTib.Self;
soi.pHighestUserAddress = *MmHighestUserAddress;
soi.pUserProbeAddress = (PVOID) *MmUserProbeAddress;
soi.pSystemRangeStart = *MmSystemRangeStart;
soi.pLowestSystemAddress = MM_LOWEST_SYSTEM_ADDRESS;
soi.pSharedUserData = SharedUserData;
soi.pProcessorControlRegion = pkpcr;
soi.pProcessorControlBlock = pkpcr->Prcb;
soi.dGlobalFlag = *NtGlobalFlag;
soi.dI386MachineType = *KeI386MachineType;
soi.dNumberProcessors = *KeNumberProcessors;
soi.dProductType = NtProductType;
soi.dBuildNumber = *NtBuildNumber;
soi.dNtMajorVersion = SharedUserData->NtMajorVersion;
soi.dNtMinorVersion = SharedUserData->NtMinorVersion;
wcscpyn (soi.awNtSystemRoot, SharedUserData->NtSystemRoot,
MAX_PATH);
return SpyOutputBinary (&soi, SPY_OS_INFO_,
pOutput, dOutput, pdInfo);
}
列表 4-13. 获取有关操作系统的信息
SPY_OS_INFO 结构的剩余成员会由位于内存中的系统数据结构填充。例如, SpyOutputOsInfo() 将内核的进程控制区域( Kernel's Processor Control Region, KPCR )的基地址赋值给 pProcessorControlRegion 成员。 KPCR 是一个非常重要的数据结构,该结构包含很多线程相关的数据项,因此,它位于自己的内存段中,该内存段的地址由 CPU 的 FS 寄存器给出。 Windows NT 4.0 和 Windows 2000 都将 FS 指向处于内核模式的线性地址 0xFFDFF000 。 SpyOutputOsInfo() 调用 SpySegment() 函数(稍后讨论它)来查询 FS 段在线性地址空间中的基地址。这个段中还包含内核的进程控制块( Kernel's Processor Control Block, KPRCB ), KPCR 结构的 Prcb 成员指向 KPRCB 结构的首地址,紧随其后的是一个 CONTEXT 结构,该结构包含当前线程的底层 CPU 信息。 KPCR 、 KPRCB 和 CONTEXT 结构定义在 ntddk.h 头文件中。
列表 4-13 中引用的另一个内部数据结构是 SharedUserData 。该结构实际上是一个由一个“众所周知的地址”通过类型转化( TypeCast )得来的结构体指针。 列表 4-14 给出了它在 ntddk.h 中的定义。那个“众所周知的地址”位于线性地址空间中,它会在编译时被设置,因此不需要花费额外的时间或进行配置。显然, SharedUserData 是一个指向 KUSER_SHARED_DATA 结构的指针,该结构的基地址在 0xFFDF0000 (这是一个线性地址)。这个内存区域由系统和用户模式的应用程序共享,它包含像操作系统版本号这样的数据, SpyOutputOsInfo() 将该版本数据复制到 SPY_OS_INFO 结构(由调用者提供)的 dNtMajorVersion 和 dNtMinorVersion 成员。就像我稍后要展示的那样, KUSER_SHARED_DATA 结构将被映射到 0x7FFE0000 ,这样用户模式的代码就可以访问它了。
在对 Spy 设备的 IOCTL 函数的讲解之后还将提供了一个示例程序,该示例程序会把返回的数据显示在屏幕上。
#define KI_USER_SHARED_DATA 0xFFDF0000
#define SharedUserData ((KUSER_SHARED_DATA *const)KI_USER_SHARED_DATA)
列表 4-14. SharedUserData 结构定义
IOCTL 函数 SPY_IO_SEGMENT
到现在讨论以变得更加有趣了。 SPY_IO_SEGMENT 函数通过一些更底层的操作来查询指定段的属性,调用者需要首先给出一个选择器( selector )。 SpyDispatcher() 首先调用 SpyInputDword() 来获取由调用程序传入的选择器的值。你可能还记得选择器( selector )是一个 16 位的数。不过,只要可能,我就会尝试避免使用 16 位的数据类型,这是因为原生的 WORD 在 i386 CPU 的 32 位模式下是 32 位的 DWORD 类型。因此,我将选择器参数扩展为 DWORD ,不过其高 16 位总是 0 。如果 SpyInputDword() 报告操作成功,接下来就会调用 SpyOutputSegemnt() 函数( 列表 4-15 给出了此函数)。不管 SpySegment() 帮助函数如何, SpyOutputSegemnt() 总是返回到调用者。基本上来说, SpySegment() 将填充 SPY_SEGMENT 结构,该结构定义于 列表 4-15 的顶部。它以 X86_SELECTOR 结构(参见 列表 4-2 )的形式给出选择器的值,紧随其后的是 64 位的 X86_DESCRIPTOR ,以及相应的段基址,段的大小限制以及一个名为 fOk 的标志,该标志用来指出 SPY_SEGMENT 结构是否有效。在稍后的一些函数中需要一次返回多个段的属性,利用 fOk 成员,调用者就可以将无效的段信息从输出数据中筛选出来。
上一页 [1] [2] [3] 下一页 |