64位核心開發第一講,驅動框架.
驅動框架介紹
1.應用程式3環到0環的框架
1.1 3環到0環的驅動框架.
首先是我們的3環API
API -> 封裝資料跟命令 ->呼叫kerner32或者ntdll的函式 ->進行封裝,傳送給IRP結構體 ->呼叫驅動
這裡接觸了一個新的概念.IRP .IRP結構體其實是3環的資料以及命令.進行封裝傳送到0環的時候.儲存在這個結構體裡面. 0環通過讀取進而呼叫0環的 NT函式來執行.
如我們呼叫ReadFile.那麼會直接呼叫我們寫的驅動的派遣函式
DispathRead
其中有0x1B(27)個分發派遣函式. 以及一個DriverUnLoad函式.
我們的資料都存放在 IRP中.我們如果要完成例程,那麼就設定IRP中的.
1.2 NT驅動框架
上面我們說了,3環的API會呼叫0環.其中資料以及命令資訊會放在IRP結構體中.
那麼如果我們呼叫 CreateFile. 那麼則會產生一個IRP_MJ_CREATE
我們核心層則會呼叫DispathCreate()來進行設定.
如下:
Nt模型,函式 | 訊息 |
---|---|
DriverEntry | 單執行緒環境,程式入口點. |
DispatchCreate | IRP_MJ_CREATE |
DispatchRead | IRP_MJ_READ |
DispatchWrite | IRP_MJ_WRITE |
DisPatchchClose | IRP_MJ_CLOSE FileObject核心物件 |
DispatchClean | IRP_MJ_CLEANUP HANDLE為控制代碼 |
DisPatchControl | irp_mj_device_control |
DriverUnLoad | 單執行緒環境,程式解除安裝. |
檔案控制代碼為0.那麼系統就會發送IRP_MJ_CLEANUP
FileOBject核心物件.如果對檔案的核心物件沒有在操作了(包括核心)
則會發送IRP_MJ_CLOSE. 大部分情況這兩種都會同時發生的.
WDM模型
WDM是網絡卡等.它引入了兩個新的函式
WDMAddDevice()
連結即可.
應用框架
Sfilter/Minifilter 檔案過濾框架.可以使用Nt模型.
TDI/NDIS/WFP 基於NT模型加的新的框架.防火牆用的
DISPERF 磁碟基於Nt模型.產生的磁碟過濾框架
HOOK
二丶編寫自己的最簡單的 NT模型驅動.
#include <ntddk.h> //很多驅動的結構體函式的宣告呀.都包含在這裡面
#define DEVICE_NAME L"\\device\\IBinaryFirst" // 驅動的裝置的名字 格式為 \device\你自定義的名字. \\是代表轉義 在source中要一樣.
//#define LINK_NAME L"\\DosDevices\IBinaryFirst" // 驅動的符號連線名 格式\dosdevices\自定義的名字 也可以\\??\\自定義的名字
#define LINK_NAME L"\\DosDevices\\IBinaryFirst"
/*
控制碼,應用層,核心層通用.
*/
#define IOCTRL_BASE 0x800
#define MYIOCTRL_CODE(i)\
CTL_CODE(FILE_DEVICE_UNKNOWN,IOCTRL_BASE+i,METHOD_BUFFERED,FILE_ANY_ACCESS)
//驅動裝置型別 裝置控制碼數值 定義R3跟R0的通訊方式.是指定Device. 我們的許可權.
/*
METHOD_BUFFERED 以快取方式讀取
METHOD_IN_DIRECT 只讀,只有開啟裝置的時候 IoControl將會成功 METHOD_OUT_DIRECT 則會失敗
METHOD_OUT_DIRECT 讀寫方式的時候.兩種方式都會成功 都在MDL中拿資料
METHOD_NEITHER 在 type3InputBuffer拿資料 IN的資料. stack->Parameters.DeviceIoControl.Type3InputBuffer
傳送給R3 在 pIrp->UserBuffer裡面.
3中通訊方式.
*/
#define CTL_HELLO MYIOCTRL_CODE(0) //控制碼為0則是HELLO.
NTSTATUS DispatchCommon(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
NTSTATUS DispatchRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
NTSTATUS DispatchWrite(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
NTSTATUS DispatchClose(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
NTSTATUS DispatchClean(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
NTSTATUS DispatchControl(PDEVICE_OBJECT pDeviceObject,PIRP pIrp);
VOID DriverUnLoad(PDRIVER_OBJECT pDriverObject);
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegPath)
{
UNICODE_STRING uDeviceName = {0};
UNICODE_STRING uLinkName = {0};
NTSTATUS ntStatus = 0;
PDEVICE_OBJECT pDeviceObject = NULL;
ULONG i = 0;
pDriverObject->DriverUnload = DriverUnLoad;
DbgPrint("Load Driver Sucess");
RtlInitUnicodeString(&uDeviceName,DEVICE_NAME); //初始化驅動裝置名字
RtlInitUnicodeString(&uLinkName,LINK_NAME); //初始化3環宇0環通訊的裝置名字
ntStatus = IoCreateDevice(pDriverObject,0,&uDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,&pDeviceObject);//建立裝置物件
/*
引數1: 驅動物件
引數2: 裝置擴充套件,建立完裝置物件之後,申請的一段額外記憶體.可以儲存裝置物件的上下文的一些資料
引數3: 裝置名字,傳入函式,需要傳地址
引數4: 裝置型別.普通的驅動設定為FILE_DEVICE_UNKNOWN
引數5: 裝置的屬性
引數6: 裝置物件是用來傳入IRP請求的.是讓我們應用層開啟它. R3 傳送IRP -> 裝置物件(我們自己建立的)
引數6的意思就是 如果為TRUE 只能一個程序開啟,獨佔開啟.FALSE是可以多個程序開啟的.
引數7: 建立好的裝置物件通過最後一個引數傳出. 注意是2級指標.
*/
DbgPrint("IoCreateDevice load.....\r\n");
if (!NT_SUCCESS(ntStatus))
{
//判斷是否設定成功
DbgPrint(L"IoCreateDevice Failed \r\n");
return 0;
}
//設定通訊的方式
pDeviceObject->Flags |= DO_BUFFERED_IO;
/*
R3 -> IRP ->核心. 通過IRP傳送給核心層.
三種通訊方式
1.快取方式:
DO_BUFFERED_IO 最安全的一個通訊方式.(資料的交換)基於快取
核心中會專門會分配跟R3的 Buffer一樣的快取. 核心層從這個空間讀取
這個就是 DO_BUFFERED. 處理完畢之後.在放到分配的快取區中.那麼IO管理器
在拷拷貝給應用層.完成資料互動.
2.直接IO方式
DO_DIRECT_IO
R3 有一塊資料. 會使用MDL方式. 會將R3傳送的資料.對映到實體記憶體中.
並且鎖住.
就相當於 R3的資料地址 對映到核心中實體地址. R3往核心中寫資料其實也是
往核心資料讀取. 這個通訊完全就是在核心中對映的實體記憶體中進行的.
3.虛擬地址直接傳送到R0
第三種方式是虛擬地址 直接傳送到R0. 前提條件.程序不能切換.必須處在
同一個執行緒上下文.
這樣不安全所以我們要對這塊記憶體進行檢查才可以.
ProbeFroWrite
ProbeFroRead
*/
DbgPrint("IoCreateSymbolicLink load.... \r\n");
ntStatus = IoCreateSymbolicLink(&uLinkName,&uDeviceName); //建立符號連結名字.
if (!NT_SUCCESS(ntStatus))
{
//建立失敗,我們就要刪除
IoDeleteDevice(pDeviceObject);
DbgPrint("IoCreateSymbolicLink Error");
return 0;
}
DbgPrint("IoCreateSymbolicLink Sucess");
//初始化分發派遣函式.
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION +1;i++)
{
//分發函式有0x1b個(27)我們不注意的可以進行設定通用的分發函式.分發函式都是一樣的.
pDriverObject->MajorFunction[i] = DispatchCommon;
}
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
pDriverObject->MajorFunction[IRP_MJ_WRITE]= DispatchWrite;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
pDriverObject->MajorFunction[IRP_MJ_CLEANUP]=DispatchClean;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DbgPrint("驅動安裝成功IBinary \r\n");
//設定驅動解除安裝
return STATUS_SUCCESS;
}
NTSTATUS DispatchCommon(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS; //IRP記錄這次操作與否的.
pIrp->IoStatus.Information = 0; //Information用來記錄實際傳輸的位元組數的.
//提交請求.
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS; //上面的 STATUS_SUCCESS是給R3看的.現在的返回時給IO管理器系統的
}
NTSTATUS DispatchCreate(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
//提交請求.
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchRead(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
PVOID pReadBuffer = NULL;
ULONG uReadLength = 0;
PIO_STACK_LOCATION pStack = NULL;
ULONG uMin = 0;
ULONG uHelloStr = 0;
uHelloStr = (wcslen(L"Hello World") + 1) * sizeof(WCHAR);
pReadBuffer = pIrp->AssociatedIrp.SystemBuffer; //緩衝區通訊方式.則是這個值
//獲取IRP堆疊.我們說過3環呼叫0環.需要封裝在IRP結構中.windows是分層驅動.所以IRP頭部是共用的.其餘的是棧傳遞.
pStack = IoGetCurrentIrpStackLocation(pIrp);
uReadLength = pStack->Parameters.Read.Length;
uMin = uReadLength > uHelloStr ? uHelloStr : uReadLength;
RtlCopyMemory(pReadBuffer,L"Hello World",uMin); //拷貝到緩衝區中給3環.
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = uMin;
//提交請求.
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchWrite(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
PVOID pWriteBuffer = NULL;
ULONG uWriteLength = 0;
PIO_STACK_LOCATION pIrpStack = NULL;
PVOID pBuffer = NULL;
//獲取IRP堆疊
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
//獲取寫的長度.
uWriteLength = pIrpStack->Parameters.Write.Length;
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
//申請記憶體.
pBuffer = ExAllocatePoolWithTag(PagedPool,uWriteLength,'TSET');
/*
PagedPool 在分頁中分配記憶體 CPU無分頁才能在分頁中分配. Dispathch級別則不能使用分頁記憶體.
NoPagePool非分頁中分配.
優先順序最低的才能使用分頁記憶體.
引數2: 長度
引數3: 標記. 不能超過4個位元組. 單引號引起來. 引數3是用來跟蹤我們分配的記憶體的.
注意是低位優先, 記憶體中看到的是 TEST.
*/
if (NULL == pBuffer)
{
pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_INSUFFICIENT_RESOURCES;
}
//提交請求.
memset(pBuffer,0,uWriteLength);
//拷貝到0環緩衝區
RtlCopyMemory(pBuffer,pWriteBuffer,uWriteLength);
ExFreePool(pBuffer);
pBuffer = NULL;
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = uWriteLength;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClose(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
//控制 其它互動都通過控制碼傳送.
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
//提交請求.
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClean(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
//提交請求.
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchControl(PDEVICE_OBJECT pDeviceObject,PIRP pIrp)
{
//核心中共享 SystemBuffer 有時間差.先讀在寫.
PIO_STACK_LOCATION pIrpStack;
PVOID InPutBuffer = NULL;
PVOID OutPutBuffer = NULL;
ULONG uInPutLength = 0;
ULONG uOutPutBufferLength = 0;
ULONG IoCtrl = 0;
InPutBuffer = OutPutBuffer = pIrp->AssociatedIrp.SystemBuffer;
pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
//uOutPutBufferLength = pIrpStack->Parameters.DeviceIoControl.OutPutBufferLength;
//uInPutLength = pIrpStack->Parameters.DeviceIoControl.InPutBufferLength;
IoCtrl = pIrpStack->Parameters.DeviceIoControl.IoControlCode; //獲取控制碼.
/*
switch(IoCtrl)
{
case CTL_HELLO:
KdPrint("Hello World");
break;
default:
break;
}
*/
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.Information = 0;
//提交請求.
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//驅動解除安裝
VOID DriverUnLoad(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("Unload MyDrive\n");
}
根據上面程式碼我們可以做個解析
1.DriverEntry();這個是NT驅動的入口點.兩個引數. 驅動物件.以及登錄檔路勁.
2.使用IoCreateDevice函式建立了一個驅動裝置物件.這樣當r3使用ReadFile等函式會傳送給裝置物件.
3.使用IoCreateSymbolicLink();建立符號連結.此時我們R3呼叫CreateFile則可以進行連結了.
4.最後註冊派遣函式即可.
5.在派遣函式中寫入你的操作.如讀取操作.我們將資料返還給R3.
1.3 IRP 結構
上面我們看的IRP有頭部.
可以看到 IOSTATUS .裡面儲存了狀態.以及實際Buffer位元組.
SystemBuffer.這個是快取IO.就是我們現在用的. 核心中開闢空間儲存3環.再從裡面讀取.最後再給這個緩衝區設定.進行輸出.
MdlAddress 這個則是直接IO.我們上面程式碼的註釋中說了.直接IO是
3環的緩衝區地址,對映到0環的物理聶村. 進而0環讀取實體記憶體進行操作.
UserBuffer
UserBuffer是自定義的.其中UserBuffer是傳出的.而內部還有一個Buffer是用來讀取的.
n以後就是IRP的棧. 在我們檔案驅動與磁碟驅動.那麼共享IRP頭部.
磁碟裝置則會使用0層的.
因為驅動是分層的.
而在棧中有一個很重要的聯合體.
Read Write DeviceControl...等等.不同結構體對應不同的IRP請求.
所以在Read派遣函式中.獲取ReadIrp的堆疊.
二丶編譯驅動.
我用的是WDK7600.可以使用XP進行測試.
編譯的時候需要使用WDK的 命令列.
當你安裝WDK7600之後再開始選單中則會看到.
開啟之後切換到你的編寫程式碼的目錄.直接輸入build進行編譯即可.
注意你的驅動程式碼字尾名要為.c的檔案.這樣不會編譯錯誤.
cpp有名字粉碎.你需要使用 extern C 表示這個函式名不會名稱粉碎.
在編譯的時候我們還需要提供一個sources 檔案.
內容為:
TARGETNAME= IBinaryFirst //編譯的驅動名字.
TARGETTYPE=DRIVER //編譯的型別為驅動
SOURCES= IBinaryFirst.c //你驅動的程式碼檔案
這是我的:
TARGETNAME=IBinaryFirst
TARGETTYPE=DRIVER
SOURCES=IBinaryFirst.c
編譯之後如下.
3.載入驅動.
載入驅動有專門的的API進行操作.我以前寫過.
可以看之前的文章.
https://www.cnblogs.com/iBinary/p/8280912.html
現在我們直接用工具載入了.
可以看到載入成功.寬字元打印出錯.不影響.
4.ring3操作核心.進行讀取.
可以看到我們的 HelloWorld已經正常讀取了.
ring3下完整程式碼.
// Ring3.cpp : Defines the entry point for the console application.
//
#include <windows.H>
#include <stdio.h>
int main(int argc, char* argv[])
{
HANDLE hFile = CreateFile(TEXT("\\\\?\\IBinaryFirst"),
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile ErrorCode:%d\n", GetLastError());
return 0;
}
system("pause");
TCHAR szBuff[0X100];
DWORD dwBytes = 0;
if (!ReadFile(hFile, szBuff, sizeof(szBuff)/sizeof(szBuff[0]), &dwBytes, NULL))
{
CloseHandle(hFile);
printf("ReadFile ErrorCode:%d\n", GetLastError());
return 0;
}
printf("bytes:%d data:%ls\n", dwBytes, szBuff);
system("pause");
//WriteFile();
//DeviceIoControl
CloseHandle(hFile);
system("pause");
return 0;
}