本章將探索驅動程式開發的基礎部分,瞭解驅動物件DRIVER_OBJECT
結構體的定義,一般來說驅動程式DriverEntry
入口處都會存在這樣一個驅動物件,該物件內所包含的就是當前所載入驅動自身的一些詳細引數,例如驅動大小,驅動標誌,驅動名,驅動節等等,每一個驅動程式都會存在這樣的一個結構。
首先來看一下微軟對其的定義,此處我已將重要欄位進行了備註。
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;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload; // 驅動物件的解除安裝地址
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
DRIVER_OBJECT結構體是Windows
作業系統核心中用於表示驅動程式的基本資訊的結構體。它包含了一系列的欄位,用於描述驅動程式的特定屬性。
以下是DRIVER_OBJECT
結構體中的一些重要欄位:
- Type:該欄位標識該結構體的型別,始終設定為DRIVER_OBJECT_TYPE。
- Size:該欄位表示該結構體的大小,以位元組為單位。
- DeviceObject:該欄位是一個指標,指向驅動程式所建立的裝置物件連結串列的頭部。每個裝置物件代表著一個裝置或者驅動程式建立的一種虛擬裝置。
- DriverStart:該欄位是一個指標,指向驅動程式程式碼的入口點,也就是驅動程式的DriverEntry函式。該函式會在驅動程式被載入時被呼叫。
- DriverSize:該欄位表示驅動程式程式碼的大小,以位元組為單位。
- DriverName:該欄位是一個UNICODE_STRING結構體,用於表示驅動程式的名稱。
- Flags:該欄位是一個32位的位掩碼,用於表示驅動程式的一些屬性。例如,可以設定DO_BUFFERED_IO標誌表示驅動程式支援緩衝I/O。
如果我們想要遍歷出當前自身驅動的一些基本資訊,我們只需要在驅動的頭部解析_DRIVER_OBJECT
即可得到全部的資料,這段程式碼可以寫成如下樣子,其中的IRP_MJ_
這一系列則是微軟的呼叫號,不同的RIP代表著不同的涵義,但一般驅動也就會用到如下這幾種呼叫號。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n");
Driver->DriverUnload = UnDriver;
DbgPrint("驅動名字 = %wZ \n", Driver->DriverName);
DbgPrint("驅動起始地址 = %p | 大小 = %x | 結束地址 %p \n",Driver->DriverStart,Driver->DriverSize,(ULONG64)Driver->DriverStart + Driver->DriverSize);
DbgPrint("解除安裝地址 = %p\n", Driver->DriverUnload);
DbgPrint("IRP_MJ_READ地址 = %p\n", Driver->MajorFunction[IRP_MJ_READ]);
DbgPrint("IRP_MJ_WRITE地址 = %p\n", Driver->MajorFunction[IRP_MJ_WRITE]);
DbgPrint("IRP_MJ_CREATE地址 = %p\n", Driver->MajorFunction[IRP_MJ_CREATE]);
DbgPrint("IRP_MJ_CLOSE地址 = %p\n", Driver->MajorFunction[IRP_MJ_CLOSE]);
DbgPrint("IRP_MJ_DEVICE_CONTROL地址 = %p\n", Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL]);
// 輸出完整的呼叫號
for (auto i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
DbgPrint("IRP_MJ呼叫號 = %d | 函式地址 = %p \r\n", i, Driver->MajorFunction[i]);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
編譯這段程式,簽名並執行,我們即可看到如下輸出資訊,此時當前自身驅動的詳細引數都可以被輸出;
當然運用_DRIVER_OBJECT
物件中的DriverSection
欄位我們完全可以遍歷輸出當前系統下所有的驅動程式的具體資訊,DriverSection
結構指向了一個_LDR_DATA_TABLE_ENTRY
結構,結構的微軟定義如下;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
為了能夠遍歷出所有的系統驅動,我們需要得到pLdr
結構,該結構可透過Driver->DriverSection
的方式獲取到,獲取到之後透過pLdr->InLoadOrderLinks.Flink
得到當前驅動的入口地址,而每一次呼叫pListEntry->Flink
都將會指向下一個驅動物件,透過不斷地迴圈CONTAINING_RECORD
解析,即可輸出當前系統內所有驅動的詳細資訊。這段程式的寫法可以如下所示;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n");
Driver->DriverUnload = UnDriver;
PLDR_DATA_TABLE_ENTRY pLdr = NULL;
PLIST_ENTRY pListEntry = NULL;
PLIST_ENTRY pCurrentListEntry = NULL;
PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
pLdr = (PLDR_DATA_TABLE_ENTRY)Driver->DriverSection;
pListEntry = pLdr->InLoadOrderLinks.Flink;
pCurrentListEntry = pListEntry->Flink;
// 判斷是否結束
while (pCurrentListEntry != pListEntry)
{
// 獲取LDR_DATA_TABLE_ENTRY結構
pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (pCurrentModule->BaseDllName.Buffer != 0)
{
DbgPrint("模組名 = %wZ | 模組基址 = %p | 模組入口 = %p | 模組時間戳 = %d \n",
pCurrentModule->BaseDllName,
pCurrentModule->DllBase,
pCurrentModule->EntryPoint,
pCurrentModule->TimeDateStamp);
}
pCurrentListEntry = pCurrentListEntry->Flink;
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
編譯這段程式,簽名並執行,我們即可看到如下輸出資訊,此時當前自身驅動的詳細引數都可以被輸出;
透過使用上一篇文章《驅動開發:核心字串複製與比較》
中所介紹的的RtlCompareUnicodeString
函式,還可用於對比與過濾特定結果,以此來實現透過驅動名返回驅動基址的功能。
LONGLONG GetModuleBaseByName(PDRIVER_OBJECT pDriverObj, UNICODE_STRING ModuleName)
{
PLDR_DATA_TABLE_ENTRY pLdr = NULL;
PLIST_ENTRY pListEntry = NULL;
PLIST_ENTRY pCurrentListEntry = NULL;
PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
pLdr = (PLDR_DATA_TABLE_ENTRY)pDriverObj->DriverSection;
pListEntry = pLdr->InLoadOrderLinks.Flink;
pCurrentListEntry = pListEntry->Flink;
while (pCurrentListEntry != pListEntry)
{
// 獲取LDR_DATA_TABLE_ENTRY結構
pCurrentModule = CONTAINING_RECORD(pCurrentListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (pCurrentModule->BaseDllName.Buffer != 0)
{
// 對比模組名
if (RtlCompareUnicodeString(&pCurrentModule->BaseDllName, &ModuleName, TRUE) == 0)
{
return (LONGLONG)pCurrentModule->DllBase;
}
}
pCurrentListEntry = pCurrentListEntry->Flink;
}
return 0;
}
上這段程式碼的使用也非常簡單,透過傳入一個UNICODE_STRING
型別的模組名,即可獲取到模組基址並返回,至於如何初始化UNICODE_STRING
則在《驅動開發:核心字串轉換方法》
中有詳細的介紹,此處你只需要這樣來寫。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n");
UNICODE_STRING unicode;
// 獲取WinDDK驅動基地址
RtlUnicodeStringInit(&unicode, L"WinDDK.sys");
LONGLONG winddk_address = GetModuleBaseByName(Driver, unicode);
DbgPrint("WinDDK模組基址 = %p \n", winddk_address);
// 獲取ACPI驅動基地址
RtlUnicodeStringInit(&unicode, L"ACPI.sys");
LONGLONG acpi_address = GetModuleBaseByName(Driver, unicode);
DbgPrint("ACPI模組基址 = %p \n", acpi_address);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
執行這段驅動程式,即可分別輸出WinDDK.sys
以及ACPI.sys
兩個驅動模組的基地址;