彙編部分
1、call 的本質相當于push+jmp,ret的本質相當于pop+jmp。
2、Windows中,不管哪種調用方式都是傳回值放在eax中,然後傳回。外部從eax中得到值。
3、Ebp總是被我們用來儲存這個函數執行之前的esp的值。
4、把局部變量區域初始化成全0cccccccch,0cch實際是int 3 指令的機器碼,這是一個斷點中斷指令。
5、任何一段中間不加任何跳轉,連續的mov和加減乘除指令一般都可以還原為一個C表達式。
如果有下面的代碼段,說明可能是含有數組或結構體。
Mov eax,<數組下标>
Inul eax,eax,<結構大小>
Mov ecx, <結構數組開始的位址>
Mov eax, dword ptr [ecx + eax]
6、分析彙編指令時:
與堆棧操作相關的,call,ret等相關指令,我們叫做函數調用([函數])指令:F
流程控制代碼,涉及判斷和跳轉指令:C
資料處理指令,其它一般為資料處理指令:D。
核心基礎
基本概念
首先需要安裝DDK (Device Driver Kit),這裡我選擇Microsoft Windows Server 2003 SP1 DDK。
Windows 驅動分成兩類,一類是不支援即插即用的NT式驅動,一類是支援即插即用的WDM((Windows Driver Model))驅動。NT式驅動的安裝是基于服務的,可以通過修改系統資料庫進行,也可以直接通過服務函數,如CreateService進行安裝;但WDM 式驅動不同,它安裝的時候需要通過編寫一個inf檔案進行控制。
Driver.h頭檔案中包含了開發NT式驅動所需要的NTDDK.h,此外還定義了幾個标志來指明函數和變量配置設定在分頁記憶體還是非分頁記憶體中。Windows驅動程式的入口函數是DriverEntry函數。
有兩種編譯驅動的辦法,一種是用DDK環境來編譯,需要在源代碼所在目錄下建立兩個檔案makefile和Sources,功能是引入DDK的bin目錄下 的makefile.def檔案,然後在開始菜單中選擇“Windows XP Checked Build Environment”編譯環境,進入需要編譯的目錄,輸入”build“指令就可以;第二種編譯方式是使用VC++進行編譯。[1]]
為了調試友善,最好安裝一個虛拟機。[2]
Windows的驅動模型概念,本來是就驅動程式的行為而言的。比如WDM驅動,必須要滿足提供n種被要求的特性(如電源管理、即插即用)才被稱為WDM驅動。如果不提供這些功能,那麼統一稱為NT式驅動。同樣的,WDF驅動也有它的一系列規範。
WDF(Windows Driver Foundation)驅動是可以調用傳統型驅動所調用的核心API的,WDF可以視為傳統型的更新版。[3]
WDK = DDK (Driver Development Kit) + HCT Kit (Hardware Compatibility Test) + WDF (Windows Driver Foundation) + DTM (Driver Test Manager) + WDF Driver Verification Tools + IFS Kit (Installable File Systems Kit) + Free ISO image download - Visual Studio 2005 out of the box integration[4]
基本文法[5]
字元串
在驅動開發中四處可見的是Unicode字元串。是以可以說:Windows的核心是使用Uincode編碼的。ANSI_STRING僅僅在某些碰到窄字元的場合使用。而且這種場合非常罕見。一個定義如下:
typedef struct _UNICODE_STRING {
USHORT Length; // 字元串的長度(位元組數)
USHORT MaximumLength; // 字元串緩沖區的長度(位元組數)
PWSTR Buffer; // 字元串緩沖區
} UNICODE_STRING, *PUNICODE_STRING;
UNICODE_STRING并不保證Buffer中的字元串是以空結束的。是以,類似下面的做法都是錯誤的,可能會會導緻核心崩潰:
UNICODE_STRING str;
…
len = wcslen(str.Buffer); // 試圖求長度。
DbgPrint(“%ws”,str.Buffer); // 試圖列印str.Buffer。
如果要用以上的方法,必須在編碼中保證Buffer始終是以空結束。但這又是一個麻煩的問題。是以,使用微軟提供的Rtl系列函數來操作字元串,才是正确的方法。[6]
記憶體與連結清單
ExAllocatePoolWithTag
記憶體配置設定:
LIST_ENTRY中的資料成員Flink指向下一個LIST_ENTRY。
整個連結清單中的最後一個LIST_ENTRY的Flink不是空。而是指向頭節點。得到LIST_ENTRY之後,要用CONTAINING_RECORD來得到連結清單節點中的資料。
鎖一般不會定義成局部變量。可以使用靜态變量、全局變量,或者配置設定在堆中。
檔案操作
在核心中不能調用使用者層的Win32 API函數來操作檔案。在這裡必須改用一系列與之對應的核心函數。
VOID InitializeObjectAttributes(
OUT POBJECT_ATTRIBUTES InitializedAttributes,
IN PUNICODE_STRING ObjectName,
IN ULONG Attributes,
IN HANDLE RootDirectory,
IN PSECURITY_DESCRIPTOR SecurityDescriptor);
Windows核心中,無論是打開檔案,還是系統資料庫,裝置等,都會先調用初始化一個OUT POBJECT_ATTRIBUTES。
OBJ_KERNEL_HANDLE表明打開的檔案句柄一個“核心句柄”。核心檔案句柄比應用層句柄使用更友善,可以不受線程和程序的限制。在任何線程中都可以讀寫。同時打開核心檔案句柄不需要顧及目前程序是否有權限通路該檔案的問題(如果是有安全權限限制的檔案系統)。
路徑并不是像應用層一樣直接寫“C:\\a.dat”,而是寫成了“\\??\\C:\\a.dat”。這是因為ZwCreateFile使用的是對象路徑。“C:”是一個符号連結對象。符号連結對象一般都在“\\??\\”路徑下。
系統資料庫[5]
應用程式設計中對應的子鍵 驅動程式設計中的路徑寫法
HKEY_LOCAL_MACHINE \Registry\Machine
HKEY_USERS \Registry\User
HKEY_CLASSES_ROOT 沒有對應的路徑
HKEY_CURRENT_USER 沒有簡單的對應路徑,但是可以求得
ZwOpenKey
ZwQueryValueKey
ZwSetValueKey
時間
void MyGetTickCount (PULONG msec)
{
LARGE_INTEGER tick_count;
ULONG myinc = KeQueryTimeIncrement();
KeQueryTickCount(&tick_count);
tick_count.QuadPart *= myinc;
tick_count.QuadPart /= 10000;
*msec = tick_count.LowPart;
}
KeSetTimer
核心的代碼始終運作在某個“中斷級”上。Dispatch > APC > Passive
參考
[6] Windows DDK