64位系统新增机制
1. WOW64子系统
WOW64子系统是64位系统为了兼容32位应用程序而新增的子系统。相当于一个轻量级的兼容层,主要在应用层工作,由三个DLL实现,Wow64.dll、Wow64Win.dll、Wow64Cpu.dll。
当一个32位应用程序发起系统调用时,WOW64子系统会拦截到这个系统调用,如果其中包含指针,它会先把这些指针的长度转换成合适的长度,然后再把系统调用提交给内核。
通常把这个拦截—转换的过程称为thunking,它是32位应用程序能运行在64位系统下的关键。
WOW64子系统有两个重要的模块,分别是文件系统重定向器(File System Redirector)模块和注册表重定向器(Registry Redirector)模块。
Windows64位系统存在两个System32目录,分别为"%windir%\System32"和"%windir%\SysWOW64"目录。
System32:包含64位系统二进制文件。
SysWOW64:包含32位系统二进制文件。
WOW64子系统的文件系统重定向器对System32目录做了透明的重定向,32位应用程序访问 %windir%\System32 目录时会被重定向到 %windir%\SysWOW64 目录。
如果32位应用程序需要访问真实的System32目录,则需要调用系统API来关闭文件重定向功能。
// 关闭文件重定向
BOOL
Wow64DisableWow64FsRedirection(IN PVOID *OldValue); // 保存原来文件重定向器的状态
// 恢复文件重定向
BOOL
Wow64RevertWow64FsRedirection(IN PVOID OldValue);
注意:
![](/qrcode.jpg)
文件重定向器并不是把System32下的所有目录和文件都重定向,下面这些是不重定向的:
- System32\catroot
- System32\catroot2
- System32\drivers\etc
- System32\logfiles
- System32\spool
注册表重定向器和文件类似,但是功能更复杂,除了提供重定向功能外,还提供注册表反射功能(Registry Reflection)。
32位应用程序访问HKEY_LOCAL_MACHINE\SOFTWARE会被重定向到HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node。
比如说,从SysWOW64中打开regedit.exe,然后在HKEY_LOCAL_MACHINE\SOFTWARE下创建一个hello,然后关掉。从System32中打开regedit.exe,可以发现,刚刚创建的hello存在于HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node。
如果32位程序想要明确在SOFTWARE下创建子项,RegCreateKeyEx时可以指定dwOptions参数包含KEY_WOW64_64KEY标志,WOW64子系统将不再对路径进行重定向。
如果64位程序想打开32位注册表,可以指定KEY_WOW64_32KEY标志。
2. PatchGuard
32位系统下,驱动程序可以对内核的数据结构或关键的函数进行挂钩和修改,但64位系统不可以,因为在64位系统中引入了 PatchGuard 机制。
PatchGuard:系统会定时检查系统关键的位置,如SSDT、GDT、IDT、系统模块(ntoskrnl.exe、hal.sys)等,一旦发现这些关键位置的数据或代码被篡改,系统就会被触发BSOD(蓝屏)。
因此,32位系统上能使用的SSDT Hook、Inline Hook、IDT Hook等技术,在64位系统下都不能用。
PatchGuard机制的初衷是避免一些恶意的内核模块破坏系统内核,但同时也阻碍了内核模块很多功能的实现。
64位驱动程序的编译、安装与运行
从Vista 64位系统开始,要求驱动文件必须经过微软指定的证书颁发机构颁发的微软代码签名证书签名,只有被签名的驱动文件才可以被加载运行,目的有两个:
- 确保驱动代码没有被非法篡改。
- 确保驱动代码来源的可靠性,从而保护驱动代码的完整性。
开发调试驱动时,往往没有签名,开发者可以选择进入系统的禁用驱动签名模式:重启电脑,在开机画面按F8键,这时系统会出现系统启动菜单项,然后选择禁用驱动程序强制签名,回车后进入系统。这种方法仅针对驱动测试和调试,当一个驱动需要发布时,必须项微软申请证书进行签名。
编程差异
1. WDK编译器不允许在64位模式下嵌入汇编。
比如:x86驱动中可以通过 __asm int 3; 来下断点,x64就不行,编译不通过。如果需要使用汇编,可以将汇编代码写成函数放到单独的asm文件中,然后通过函数的方式调用。
x86,x64下都可以通过 __debugbreak(); 来给驱动程序下断点。
或者用DbgBreakPoint()、KdBreakPoint(),不过这两个底层调用的都是__debugbreak()。
而它内部其实也就是 int 3 指令。
2. 预处理和条件编译
WDK中预置了宏来帮助开发者实现对不同的系统平台进行条件编译。
_WIN64:被定义时,表示当前编译环境是64位。
_M_AMD64:被定义时,表示当前编译环境是AMD64。
_M_IX64:被定义时,表示当前编译环境是IA64。
例如:
#ifdef _WIN64
// 64位操作
#else
// 32位操作
#endif
3. 数据结构调整
上面提到了thunking技术,即拦截-转换指针长度。当一个32位的应用程序通过DeviceIoControl与64位驱动程序通信时,如果通信的数据结构里包含指针,thunking是不会发生的。但也可能会遇到问题,比如有以下结构体:
typedef struvt _UNICODE_STRING
{
// x86 x64
USHORT Length; // 2 2
USHORT MaximumLength; // 2 2 (后面有4字节的对齐)
PWSTR Buffer; // 4 8
}UNICODE_STRING, *PUNICODE_STRING;
typedef struvt _DATA
{
HANDLE Event; // 4 8
UNICODE_STRING ObjectName; // 8 16
}DATA, *PDATA; // 12 24
对于32位应用程序,DATA结构体大小为12字节;对于64位应用程序,大小为24字节。因为64位的指针长度和默认对齐都是8字节。
当32位程序用DATA结构体和64位驱动通信时,驱动期望数据大小为24字节,而实际数据大小只有12字节,最终会导致驱动程序校验数据大小失败。
为了解决这个问题,可以在定义DATA结构体时使用固定长度的类型。
typedef struvt _DATA
{
void* POINTER_32 Event;
UNICODE_STRING32 ObjectName;
}DATA, *PDATA;