Windows內核開發-9-32位和64位的區別
32位的應用程序可以完美再64位的電腦上運行,而32位的內核驅動無法再64位的電腦上運行,或者64位的驅動無法在32位的應用程序上運行。這是為什么呢。
原因是在x64的Windows操作系統上,模擬了x86操作系統的操作,並且引入了一個WOW64子系統,將x86和x64完美進行兼容。
WOW64子系統
x86能在x64上運行全靠這個東西。全名叫做Windows On Windows,英文名感覺是在套娃,其實它的意思就是在Windows64上運行Windows32。
這個系統由Wow64.dll,Wow64Win.dll,Wow64Cpu.dll三個dll實現,具體怎么實現的不用考慮。
Wow64子系統可以完美實現x86和x64之間的轉換。
轉換流程: 當一個32位Application發起系統調用時,WOW64會攔截下來,將其轉換為64位的類型(包括指針范圍,數據類型范圍等等),然后再把系統調用請求提交給內核。這整個攔截-轉換的流程被稱為"thunking"。
WOW64有兩個重要的模塊,一個是系統文件重定向(File System Redirector),一個是注冊表重定向(Registry Redirector)。
系統文件重定向(File System Redirector)
Windows64位OS包含了兩個System32文件,一個是System32另一個是SysWow64。默認情況下的安裝路徑%Windows%\System32和%Windows%\SysWow64。
System32這個文件里面保存了系統需要的一些二進制文件,System32里面存放的是x64的系統二進制文件,SysWow64里面存放的是x86里的文件。不要被這個什么system32迷惑成了它就是32位的系統文件了。
一般情況下32位的只能加載32位的系統dll,64只能加載64的。因為是64位的操作系統,所以肯定默認是加載64的dll,但是32位怎么辦,為了解決這個問題WOW64就構成了文件系統重定向模塊,把32的系統dll放到了SysWow64里面,然后把System32這個文件夾給他重定向指到了SysWow64文件夾里了。
這里我們寫一個代碼來驗證一下:
void TestFileRedirector() { HANDLE hFile = CreateFileA("C:\\Windows\\System32\\test.txt", GENERIC_READ | GENERIC_WRITE, 0x00000000, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { cout << "創建文件失敗" << endl; } else { cout << "創建文件成功,文件名為test.txt" << endl; } CloseHandle(hFile); }
假如說這個test文件是在SysWow64文件夾下面創建的,那么說明我們前面的講述沒問題,確實是重定向到了SysWow64里面。這里我們要用管理員啟動Visual Studio才行,因為這個文件夾是系統文件夾,需要管理員權限。
下面是我的驗證結果:

在x86和x64運行后分別是在System32和SysWow64新建了文件,足以說明結論了。
關閉系統文件重定向
文件重定向固然不錯,但是肯定有時候我們會不得不關閉它。
這里就會用到兩個API:
BOOL Wow64DisableWow64FsRedirection( [out] PVOID *OldValue );//關閉重定向,將原來的值保存到輸入參數oldvalue里面 BOOL Wow64RevertWow64FsRedirection( PVOID OlValue );//通過參數olvalue來恢復重定向
這里我們在修改一下我們的代碼,讓他x86的程序不要重定向到x64文件里面:
void TestFileRedirector() { PVOID OldValue; auto ret = Wow64DisableWow64FsRedirection(&OldValue); if (ret == 0) { cout << "調用關閉重定向的函數失敗,請檢查" << endl; return; } else { cout << "調用重定向的函數成功,已經取消文件系統重定向" << endl; } HANDLE hFile = CreateFileA("C:\\Windows\\System32\\test.txt", GENERIC_READ | GENERIC_WRITE, 0x00000000, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { cout << "創建文件失敗" << endl; return; } else { cout << "創建文件成功,文件名為test.txt" << endl; } CloseHandle(hFile); Wow64RevertWow64FsRedirection(OldValue); }
這樣再創建就是到System32系統文件夾里面了。

有一部分文件是不會被重定向的:
%Windows%System32\catrrot
%Windows%System32\catrrot2
%Windows%System32\drivers\etc
%Windows%System32\logfiles
%Windows%System32\spool
注冊表重定向(Registry Redirector)
和系統文件重定向(File System Redirecotr)比較類似,但是功能更為復雜。除了重定向還有注冊表反射功能(Registry Reflection 該功能暫時用不到)。
和File System Redirector比較類似的是,win32訪問HKEY_LOCAL_MACHINE\SOFTWARE會被重定向到HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node下面。同樣寫一下代碼測試一下:
void TestRegRedirector() { HKEY hKey = NULL; RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Test"), 0, NULL, 0, KEY_READ, NULL, &hKey,NULL); if (hKey != NULL) { cout << "創建注冊表成功" <<endl; RegCloseKey(hKey); } else { cout << "創建注冊表失敗" << endl; return; } }
用32位來運行,看他添加到哪里:

當然也肯定有關閉的辦法。
DIY注冊表重定向
在創建注冊表的API上加一個宏定義就可完美解決這個問題了:
RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Test"),0, NULL, 0, KEY_READ | KEY_WOW64_32KEY, NULL, &hKey,NULL);
這里的KEY_WOW64_32KEY就是32位,KEY_WOW64_64KEY就可以鎖定為64位了。
void TestRegRedirector() { HKEY hKey = NULL; RegCreateKeyEx(HKEY_LOCAL_MACHINE, TEXT("Software\\Test"), 0, NULL, 0, KEY_READ | KEY_WOW64_32KEY, NULL, &hKey,NULL); if (hKey != NULL) { cout << "創建注冊表成功" <<endl; RegCloseKey(hKey); } else { cout << "創建注冊表失敗" << endl; return; } }
64位系統的升級技術
64對比32提供了很多新技術,比如之前的32位被很多程序很多公司,進行掛鈎啊各種功能導致很不安全很麻煩。
PatchGuard
所以升級了一個PatchGuard技術,這個機制就是系統會定期檢查內部的關鍵位置是否被篡改,一旦被篡改就會藍屏。
比如一些論壇常見的SSDT(系統描述表),GDT(全局描述表),IDT(中斷描述表)等等。但是其實也是可以繞過的。正所謂道高一尺魔高一丈就是這個意思,沒有絕對的安全。
x64的編譯、安裝、運行
編譯很簡單,vs換成x64就行了。
安裝的話著需要考慮32位exe安裝驅動的時候不會把他放到64位驅動system32這個文件夾下就行了,這個用關閉File System redirecotr就行。
運行:x64的驅動必須得有簽名才行,變相提高了安全吧,不過我們自己測試就把測試機變成測試模式就好了。
編程差異
x86和x64編程還是有少許區別的。
加入匯編
32位和64比較麻煩的就是不能直接內聯匯編了,就比如說下面這段代碼:
#include<ntddk.h> extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(DriverObject); UNREFERENCED_PARAMETER(RegistryPath); __asm { int 3 } return STATUS_SUCCESS; }
一個很簡單的內核驅動代碼,用x64編譯就不行,而x86沒問題:

也就是說不運行直接內聯匯編了,只能用匯編asm寫好了,然后作為函數的形式放進去了。
條件編譯
WDK設置了宏來幫助條件編譯,比如針對操作系統平台有: _M_AMD64, _M_IX64等等,對於位數也有 _WIN64和 _WIN32 ,比如說:
#ifdef _WIN32 //32位情況 #else //不是32位情況 #endif
調整數據結構
當一個32位的exe通過DeviceIoControl的方式和64位驅動進行交互的時候,如果結構體里有指針是不會進行thunking技術調整的,所以這里就會涉及到一些問題了,比如指針位數的不兼容,以及比如int這種位數也是不兼容的,還有對齊的一些很多問題。所以最好是采用限制好了的數據結構體。
比如說結構體定義成這樣
struct DRIVER_DATA { void* POINTER_32 test; UNICODE_STRING32 testUnicode_string; };
直接把長度一次到位限制了。
小結:
x64和x86的區別還是蠻多的,如果要從頭到尾設計一個驅動的話是必須得思考這個問題的。
