看雪-課程-Windows內核安全編程實踐之路-筆記
July 16, 2020 10:30 PM
環境搭建
安於此生@ExpLife,https://www.github.com/explife0011
開發環境:WDK+VS
調試環境:VM+VirtualKD+WindbugX
VirtualKD:
免去配置Windbg雙機調試瑣碎的步驟
VS -> VisualC++ -> Empty WDM Driver
驅動編程書籍:
- Windows驅動開發技術詳解
- 天書夜讀:從匯編語言到Windows內核編程
- 寒江獨釣:Windows內核安全編程
- Windows內核安全與驅動開發
- Rootkits-Windows內核的安全防護
- Rootkit 系統灰色地帶的潛伏者
- Windows PE權威指南
- 深入解析Windows操作系統
- windows內核情景分析
- windows內核原理與實現
- Windows內核設計思想
- Reversing Modern Malware and Next Generation Threats
編寫一個最簡單的驅動:
驅動的入口函數DriverEntry: (可以與C語言控制台程序的入口函數main進行類比)
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
);
入口函數的第一個參數PDRIVER_OBJECT DriverObject
入口函數的第二個參數PUNICODE_STRING RegistryPath
指向代表該驅動程序注冊表鍵路徑的UNICODE_STRING結構
typedef struct _DRIVER_OBJECT {
CSHORT Type; //驅動程序的類型
CSHORT Size; //驅動對象結構體的大小
PDEVICE_OBJECT DeviceObject; //指向驅動程序創建的設備對象,這些設備對象構成一個鏈表
ULONG Flags; //驅動程序標志
PVOID DriverStart; //驅動程序映像的起始地址
ULONG DriverSize; //驅動程序映像的大小
PVOID DriverSection; //指向驅動程序映像的內存區對象,可以通過該成員遍歷系統中所有的驅動模塊
PDRIVER_EXTENSION DriverExtension;//指向驅動程序對象的擴展結構
UNICODE_STRING DriverName; //驅動的名稱
PUNICODE_STRING HardwareDatabase; //設備的硬件數據庫名,一般為HKEY_LOCAL_MACHINE\Hardware\DESCRIPTION\System
PFAST_IO_DISPATCH FastIoDispatch; //指向文件系統以及網絡傳輸驅動會用到的派遣函數的指針表
PDRIVER_INITIALIZE DriverInit; //指向DriverEntry函數,這是IO管理器設置的
PDRIVER_STARTIO DriverStartIo; //記錄StartIO例程的函數地址,用於串行化操作.
PDRIVER_UNLOAD DriverUnload; //指向驅動卸載時所用的回調函數地址
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]; //指向驅動程序的派遣函數地址表
} DRIVER_OBJECT,*PDRIVER_OBJECT;
UNREFERENCED_PARAMETER宏的運用:
在編寫驅動的時候,如果有函數的參數在函數體內未引用,並且將警告視為錯誤的編譯選項啟用了的話,這時驅動程序編譯會報錯,並提示程序中存在未引用的變量.這個時候我們只需要使用UNREFERENCED_PARAMETER來描述未引用到的參數就可以消除該警告。
UNICODE_STRING結構:
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
Length:存儲在緩沖區中的字符串所占的字節數
MaxinumLength:緩沖區能夠容納的字節數
Buffer:指向了用於包含一個寬字符字符串的緩沖區
分頁與非分頁內存
windows使用虛擬內存的方式來管理內存.虛擬內存系統可以讓軟件有一個比物理內存大得多的虛擬內存空間.為了做到這一點,內存管理器需要在物理內存與磁盤文件之間交換頁幀. 但操作系統的某些部分是不能被分頁的,比如處理缺頁中斷的代碼與數據結構就必須常駐內存.
windows把內核模式地址空間分成分頁內存池與非分頁內存池.
用戶模式地址空間總是分頁的.必須常駐內存的代碼與數據放在非分頁池;不必常駐內存的代碼與數據放在分頁池.
編寫驅動程序的時候決定代碼與數據是否需要常駐非分頁池有一個簡單規則:執行在>=DISPATCH_LEVEL級的代碼不可以引發缺頁中斷
PAGED_CODE()
windows為每個硬件中斷和少數軟件事件賦予了一個優先級,即中斷請求級別(interrupt request level - IRQL):
申請內存 ExAllocatePool/ExAllocatePoolWithTag 類比 C語言中的malloc
釋放內存 ExFreePool/ExFreePoolWithTag 類比 C語言中的free
常見的函數返回值類型NTSTATUS
WDK頭文件中的定義
常用的狀態碼:
STATUS_SUCCESS - 成功
STATUS_UNSUCCESSFUL - 失敗
判斷是否狀態碼是否
成功的宏NT_SUCCESS(ntStatus)
內核下的字符串操作
RtlInitUnicodeString : 初始化一個UNICODE_STRING結構 RtlInitUnicodeStringEx: 初始化一個UNICODE_STRING結構 RtlAnsiCharToUnicodeChar:將第一個多字節字符串轉換為一個寬字節字符串
RtlAnsiStringToUnicodeString:將一個ANSI_STRING轉換為一個UNICODE_STRING
RtlAnsiStringToUnicodeSize:如果將一個ANSI_STRING轉換為一個UNICODE_STRING,那么這個UNICODE_STRING的緩沖區所需要占用的字節數
RtlAppendUnicodeStringToString:將一個UNICODE_STRING連接到一個UNICODE_STRING中
RtlAppendUnicodeToString:將一個寬字節字符串連接到一個UNICODE_STRING中
RtlCompareUnicodeString:比較兩個UINICODE_STRING的大小RtlCompareUnicodeStrings:比較兩個寬字節字符串RtlConvertSidToUnicodeString:生成一個可打印的代表安全標識符的UNICODE_STRING
RtlCopyUnicodeString:將一個UNICODE_STRING拷貝到另一個UNICODE_STRING中
RtlCreateUnicodeString:用一個寬字節字符串創建一個新的UNICOCE_STRING
RtlDowncaseUnicodeChar:將指定的寬字節字符串轉換為小寫
RtlDowncaseUnicodeString:將指定的UNICODE_STRING轉換為小寫
RtlDuplicateUnicodeString:將一個UNICODE_STRING拷貝至另一個UNICODE_STRING
RtlEqualUnicodeString:判斷兩個UNICODE_STRING是否相等
RtlFindUnicodePrefix:查找UNICODE_STRING的前綴
RtlFreeUnicodeString:釋放UNICOCE_STRING的存儲空間
RtlHashUnicodeString:為UNICODE_STRING創建一個哈希值
RtlInitializeUnicodePrefix:初始化一個前綴表
RtlInsertUnicodePrefix:插入一個新元素到Unicode前綴表中
RtlInt64ToUnicodeString:將一個無符號64位整數值轉換為UNICODE_STRING
RtlIntegerToUnicode:將一個整數值轉換為寬字節字符串
RtlIntegerToUnicodeString:將一個整數值轉換為UNICODE_STRING
RtlMultiByteToUnicodeN:將多字節字符串轉換為寬字節字符串
RtlMultiByteToUnicodeSize:將多字節字符串轉換為的寬字節字符串所需的字節數
RtlUpcaseUnicodeChar:將指定的寬字節字符串轉換為大寫
RtlUpcaseUnicodeString:將指定的UNICODE_STRING轉換為大寫
RtlValidateUnicodeString:驗證UNICODE_STRING是否有效
Code Demo
#include <Ntifs.h>
#include <ntddk.h> //頭文件如果順序想法,會提示重定義。
VOID StringExample() // Demo for string
{
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
UNICODE_STRING ustr1 = {0};
UNICODE_STRING ustr2 = {0};
UNICODE_STRING ustr3 = {0};
ANSI_STRING str1 = {0};
LONG lRet = 0;
RtlInitUnicodeString(&ustr1, L"This is a unicode string!");
RtlInitAnsiString(&str1, "This is a ansi string!");
RtlAnsiStringToUnicodeString(&ustr2, &str1, TRUE);
lRet = RtlCompareUnicodeString(&ustr1, &ustr2, FALSE);
if (lRet > 0)
{
//ustr1 > ustr2
DbgPrint("ustr1 > ustr2\r\n");// Function Print
}
else if (lRet == 0)
{
//ustr1 == ustr2
DbgPrint("ustr1 == ustr2\r\n"); // Function Print
}
else
{
//ustr1 < ustr2
DbgPrint("ustr1 < ustr2\r\n");// Function Print
}
if (ustr2.Buffer)
{
RtlFreeUnicodeString(&ustr2);
}
ntStatus = RtlDowncaseUnicodeString(&ustr3, &ustr1, TRUE);
if (NT_SUCCESS(ntStatus))
{
if (ustr3.Buffer)
{
RtlFreeUnicodeString(&ustr3);
}
}
}
VOID
Unload(
IN PDRIVER_OBJECT DriverObject
) //Driver Unload
{
UNREFERENCED_PARAMETER(DriverObject);
}
NTSTATUS
DriverEntry(
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
) // Driver Main Entrance
{
UNREFERENCED_PARAMETER(DriverObject); //消除警告
UNREFERENCED_PARAMETER(RegistryPath); //消除警告
KdBreakPoint(); // BreakPoint
DriverObject->DriverUnload = Unload;
StringExample();
return STATUS_SUCCESS; // Return Code
}