• 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"\DosDevicesIBinaryFirst" // 驱动的符号连接名 格式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.....
    ");
    	if (!NT_SUCCESS(ntStatus))
    	{
    		//判断是否设置成功
    		DbgPrint(L"IoCreateDevice Failed 
    ");
    		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.... 
    ");
    	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 
    ");
    	//设置驱动卸载
    	
    	
    	
    	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
    ");
    }
    
    
    
    

    根据上面代码我们可以做个解析
    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
    ", 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
    ", GetLastError());
    		return 0;
    	}
    	printf("bytes:%d data:%ls
    ", dwBytes, szBuff);
    	system("pause");
    	//WriteFile();
    	//DeviceIoControl
    	CloseHandle(hFile);
    	system("pause");
    	return 0;
    }
    
    
    
  • 相关阅读:
    MySQL复制的管理和维护
    error nr.1045 access denied for user 'root'@'localhost' (using passwd:no)
    MYSQL-使用mysqldump创建数据库快照
    ConcurrentBag同线程元素的添加和删除
    bootstrap源码学习与示例:bootstrap-tab
    Docker 私有仓库最简便的搭建方法
    使用Apache Jmeter进行并发压力测试
    cmd隐藏指定文件
    Bootstrap之BootstrapDialog
    全栈工程师的武器——MEAN(转)
  • 原文地址:https://www.cnblogs.com/iBinary/p/10990660.html
Copyright © 2020-2023  润新知