驅動框架介紹
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中的.
IOstatus即可.我們的驅動是分層驅動.如果不設置.他還會調用其它的驅動.
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()
wdmpnp()
鏈接即可.
應用框架
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; //注意此位置,一定要 |= 不然打死你也不好找出原因.經驗之談.而且是 Device的flag 不是Driver的flags
/*
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"),
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;
}