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中的.
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;
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM