本章將探索驅動程式開發的基礎部分,瞭解驅動對象`DRIVER_OBJECT`結構體的定義,一般來說驅動程式`DriverEntry`入口處都會存在這樣一個驅動對象,該對象內所包含的就是當前所載入驅動自身的一些詳細參數,例如驅動大小,驅動標誌,驅動名,驅動節等等,每一個驅動程式都會存在這樣的一個結構,... ...
本章將探索驅動程式開發的基礎部分,瞭解驅動對象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: [email protected]
#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: [email protected]
#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
兩個驅動模塊的基地址;
文章出處:https://www.cnblogs.com/LyShark/p/17140039.html
版權聲明:本博客文章與代碼均為學習時整理的筆記,文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!
轉載文章請遵守《中華人民共和國著作權法》相關法律規定或遵守《署名CC BY-ND 4.0國際》規範,合理合規攜帶原創出處轉載,如果不攜帶文章出處,並惡意轉載多篇原創文章被本人發現,本人保留起訴權!