一、一些废话
第一篇驱动类的博文,希望大家多多支持~!
入门驱动不久,感觉这一阶段还是遇到了挺多困难,看书的过程中书本上的知识多多少少会和亲身实践有差别,尤其是进到R0级后不再像R3下能那么'为所欲为'了,细节方面的东西特别多,而且一不小心就给蓝了,还好调试时在虚拟机下,鼠标点两下就能重启。其实驱动说难也难,说简单也简单,多动动手,多码几个字,多瞄两眼,熟悉了也就不难了。
废话不多说,先推荐些Windows驱动入门必备利器:
1.《天书夜读》Windows驱动编程基础教程 想要快速入门,这个当之无愧。
2.《从汇编语言到Windows内核编程》 很经典的一本书,本文就是在其基础上写成的(个人很喜欢的一本书)
3.《寒江独钓 Windows内核安全编程》.(谭文等) 相当不错的一本驱动入门书籍
4. 竹林蹊径:深入浅出Windows驱动开发 这本书个人觉得不如前几本好,跳跃性有点大
本文的目的是通过实现驱动下的CopyFile熟悉驱动编程。
关键词: 驱动(内核)编程 内核字符串 应用层与驱动的通信 IRP请求
二、前缀知识
①、驱动环境搭建
②、Windbg常用命令
由于windbg命令很多,而在一般调试中很多都不会用到,这里仅仅介绍一些最基本也是最常用的一些命令
--查看局部变量, 并显示符号的类型和参数类型
--清除所有断点
③、内核中的UNICODE_STRING
typedef struct _UNICODE_STRING { USHORT Length; //The length in bytes of the string stored in Buffer. USHORT MaximumLength; //The length in bytes of Buffer PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING;
④、OBJECT_ATTRIBUTE结构体
VOID InitializeObjectAttributes( OUT POBJECT_ATTRIBUTES InitializedAttributes, //被初始化的OBJECT_ATTRIBUTES结构体 IN PUNICODE_STRING ObjectName, //如果是操作文件则为文件名 IN ULONG Attributes, IN HANDLE RootDirectory, //相对目录 IN PSECURITY_DESCRIPTOR SecurityDescriptor );
主要内容已经注释,这里有一定要特别注意: 在内核中文件路径不能像在应用层那样 写"C:\\aa.txt" 而是写成"\\??\\C:\\aa.txt" 或 "\\DosDevices\\C:\\aa.txt" ,因为在内核中使用对象路径,"C:\\"只是一个符号链接对象(仅仅对用户而言有意义),链接对象一般都在\\??\\
(也可写成\\DosDevices\\) 所以在内核中要写完整的对象路径。
⑤、使用DeviceIoControl与驱动交互
//用户自定义控制码 用于和应用层的通信 #define MYCOPYFILE_CODE \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,\ 0xa01,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA)
其中MYCOPYFILE_CODE即我们自定义的控制码,在初始化时需给他一个‘代号’,即上面的'0xa01'(这个可以自己随意定,只要不与系统的重复就好)。定义控制码的其余部分在现阶段可以认为都一样。
BOOL WINAPI DeviceIoControl( __in HANDLE hDevice, //设备句柄 要通过CreateFile得到 __in DWORD dwIoControlCode, //这里就写我们自定义控制码 __in LPVOID lpInBuffer, //输入缓冲区 __in DWORD nInBufferSize, //输入缓冲区大小 __out LPVOID lpOutBuffer, //输出缓冲区 __in DWORD nOutBufferSize, //输出缓冲区大小 __out LPDWORD lpBytesReturned, //在输出缓冲区接收到的的大小 __in LPOVERLAPPED lpOverlapped );
注意:这里的缓冲区大小也是以byte为单位。
三、完整代码及详细注释
/////////////////////////////////////////////////////////////////////////////// /// /// Original filename: DriverCopyFile.cpp /// Project : DriverCopyFile /// Date of creation : 2013-02-18 /// Author(s) : CouLd /// /////////////////////////////////////////////////////////////////////////////// // $Id$ #ifdef __cplusplus extern "C" { #endif #include <ntddk.h> #include <ntdef.h> #include <string.h> #ifdef __cplusplus }; // extern "C" #endif #include "DriverCopyFile.h" #define MEM_TAG 'MyT'//定义内存标志 //用户自定义控制码 用于和应用层的通信 #define MYCOPYFILE_CODE \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,\ 0xa01,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA) //设备名 UNICODE_STRING my_device_name=RTL_CONSTANT_STRING(L"\\Device\\MyCF"); //符号链接名 应用层使用 DosDevices注意别忘了s UNICODE_STRING symb_link_name=RTL_CONSTANT_STRING(L"\\DosDevices\\MyCF"); #ifdef __cplusplus namespace { // anonymous namespace to limit the scope of this global variable! #endif PDRIVER_OBJECT pdoGlobalDrvObj = 0; #ifdef __cplusplus }; // anonymous namespace #endif NTSTATUS DRIVERCOPYFILE_DispatchCreateClose( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS status = STATUS_SUCCESS; DbgPrint("Create Close"); //返回最简单的IRP三部曲: Irp->IoStatus.Status = status;//(1) Irp->IoStatus.Information = 0;//(2) IoCompleteRequest(Irp, IO_NO_INCREMENT);//(3) return status; } NTSTATUS MyDeviceIOControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS status = STATUS_SUCCESS; WCHAR temp_sourcebuf[100],temp_destbuf[100],temp_sourcebuf2[100],temp_destbuf2[100]; UNICODE_STRING sourcebuffer,destinbuffer; UNICODE_STRING sourcehead,destinhead; UNICODE_STRING head=RTL_CONSTANT_STRING(L"\\??\\"); //初始化UNICODE_STRING空串 RtlInitEmptyUnicodeString(&sourcebuffer,temp_sourcebuf,sizeof(WCHAR)*100); RtlInitEmptyUnicodeString(&destinbuffer,temp_destbuf,sizeof(WCHAR)*100); RtlInitEmptyUnicodeString(&sourcehead,temp_sourcebuf2,sizeof(WCHAR)*100); RtlInitEmptyUnicodeString(&destinhead,temp_destbuf2,sizeof(WCHAR)*100); RtlCopyUnicodeString(&sourcehead,&head); RtlCopyUnicodeString(&destinhead,&head); //得到irpsp的目的是为了得到功能号、输入/输出缓冲区长度等内容 PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp); ULONG code=irpSp->Parameters.DeviceIoControl.IoControlCode; //得到输入/输出缓冲区长度 ULONG in_len=irpSp->Parameters.DeviceIoControl.InputBufferLength; ULONG out_len=irpSp->Parameters.DeviceIoControl.OutputBufferLength; //输入/输出缓冲区是公用内存空间的 PVOID buffer=Irp->AssociatedIrp.SystemBuffer; //DbgPrint(((PMyMessage)buffer)->SourceFileName)); switch(code) { case MYCOPYFILE_CODE: //如果是我们自定义的控制码 DbgPrint("MYCOPYFILE_CODE_SUCCESS"); //因为传进来的字符串以空为结尾所以可以用wcscpy //初始化UNICODE_STRING字符串 wcscpy(sourcebuffer.Buffer,((MyMessage*)buffer)->SourceFileName); wcscpy(destinbuffer.Buffer,((MyMessage*)buffer)->DestinFileName); //UNICODE_STRING 中几个字段的MSDN解释如下: //Length :The length in bytes of the string stored in Buffer. //MaximumLength : The length in bytes of Buffer. //很明了了,要转换成真正的长度要乘2 sourcebuffer.Length=2*wcslen(sourcebuffer.Buffer); destinbuffer.Length=2*wcslen(destinbuffer.Buffer); //这里要特别注意:应用层传过来的文件路径不能直接使用, //要在字符串前面加上"//??//" 字符串连接要用到RtlAppendUnicodeStringToString status=RtlAppendUnicodeStringToString( &sourcehead,&sourcebuffer); if (status!=STATUS_SUCCESS) { DbgPrint("Append Unicode String Error!"); return status; } status=RtlAppendUnicodeStringToString( &destinhead,&destinbuffer); if (status!=STATUS_SUCCESS) { DbgPrint("Append Unicode String Error!"); return status; } MyCopyFile(&sourcehead,&destinhead); Irp->IoStatus.Information=0; Irp->IoStatus.Status=STATUS_SUCCESS; break; default: Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST; Irp->IoStatus.Information = 0; break; } status = Irp->IoStatus.Status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; } VOID DRIVERCOPYFILE_DriverUnload( IN PDRIVER_OBJECT DriverObject ) { PDEVICE_OBJECT pdoNextDeviceObj = DriverObject->DeviceObject; IoDeleteSymbolicLink(&symb_link_name);//删除符号链接 // 卸载驱动对象 while(pdoNextDeviceObj) { PDEVICE_OBJECT pdoThisDeviceObj = pdoNextDeviceObj; pdoNextDeviceObj = pdoThisDeviceObj->NextDevice; IoDeleteDevice(pdoThisDeviceObj); } } NTSTATUS MyCopyFile( PUNICODE_STRING pSourceFile, PUNICODE_STRING pDestinFile) { HANDLE HSourceFile,HDestinFile; /*内核中读写文件等操作不能直接进行,要填写OBJECT_ATTRIBUTES结构 初始化时调用InitializeObjectAttributes函数并传入*/ OBJECT_ATTRIBUTES ObjectAttrSource,ObjectAttrDestin; /*IO_STATUS_BLOCK在内核开发中经常使用,表示一个操作的结果 返回结果在其中的Status字段中,成STATUS_SUCCESS,否则为错误码 错误码存在Information字段中*/ IO_STATUS_BLOCK io_status={0}; /*内核中常用到的长长整型数据 LARGE_INTEGER--共用体 能很方便的表示高32位、低32位 在运算比较的时候用QuadPart字段(直接表示64位值)即可 */ LARGE_INTEGER offset={0}; PVOID buffer=NULL; ULONG Length; NTSTATUS status = STATUS_SUCCESS; InitializeObjectAttributes( &ObjectAttrSource,//被初始化的OBJECT_ATTRIBUTES pSourceFile,//对象名字字符串(文件路径)不能像在应用层那样直接用路径应写成 "\\??\\C:\\aa.txt" OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,//前者表示名字不区分大小写,后者表示打开内核句柄 NULL,//用于相对打开的情况 NULL);//用于设置安全描述符 /* ZwCreateFile中第二个字段ACCESS_MASK表示申请的权限具体如下: 打开写文件--FILE_WRITE_DATA 打开读文件--FILE_READ_DATA 删除文件--DELETE 设置文件属性--FILE_READ_ATTRIBUTES 常用宏:GENERIC_READ--常用读权限 GENERIC_WRITE--常用写权限 GENERIC_ALL--全部权限 */ status=ZwCreateFile( &HSourceFile, GENERIC_READ|GENERIC_WRITE,//申请的权限 &ObjectAttrSource, &io_status,//返回操作结果 NULL, FILE_ATTRIBUTE_NORMAL,//控制新建的文件属性 FILE_SHARE_READ,//共享访问 FILE_OPEN_IF,//打开或新建 FILE_NON_DIRECTORY_FILE|FILE_RANDOM_ACCESS| //表示打开的是文件 FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if(!NT_SUCCESS(status))//是否顺利打开 { DbgPrint("Open Source File Error!"); return status; } InitializeObjectAttributes( &ObjectAttrDestin, pDestinFile, OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL); status=ZwCreateFile( &HDestinFile, GENERIC_READ|GENERIC_WRITE, &ObjectAttrDestin, &io_status, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE|FILE_RANDOM_ACCESS| FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0 ); if(!NT_SUCCESS(status))//是否顺利打开 { DbgPrint("Open Destin File Error!"); return status; } Length=4*1024; //每次读取4KB //为临时buffer分配内存 buffer=ExAllocatePoolWithTag(NonPagedPool,Length,MEM_TAG); /*打开文件后要进行循环读写文件操作*/ while(1) { status=ZwReadFile( HSourceFile,NULL,NULL,NULL, &io_status, buffer,//存内容的buffer Length, &offset,//要读取的文件的偏移 NULL); if(!NT_SUCCESS(status)) { //如果状态为STATUS_END_OF_FILE说明文件读取成功结束 if(status==STATUS_END_OF_FILE) { status=STATUS_SUCCESS; break; } else { DbgPrint("Read File Error!"); break; } } //获取实际读取到的长度 Length=io_status.Information; //把读取到的内容写入文件 status=ZwWriteFile( HDestinFile,NULL,NULL,NULL, &io_status, buffer,Length,&offset,NULL); if(!NT_SUCCESS(status))//是否顺利打开 { DbgPrint("Write File Error!"); break; } //偏移量后移直至读取到文件结尾 offset.QuadPart+=Length; } //退出前注意要手动释放资源 if (HSourceFile!=NULL) { ZwClose(HSourceFile); } if (HDestinFile!=NULL) { ZwClose(HDestinFile); } if (buffer!=NULL) { ExFreePool(buffer); } return status; } #ifdef __cplusplus extern "C" { #endif NTSTATUS DriverEntry( IN OUT PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { PDEVICE_OBJECT pdoDeviceObj = 0; NTSTATUS status = STATUS_UNSUCCESSFUL; // 创建设备对象 status = IoCreateDevice( DriverObject,//生成该设备的驱动对象 0,//当用户需要在每个设备上记录一些额外信息时使用 &my_device_name, FILE_DEVICE_UNKNOWN,//表示设备类型 现在用不到 FILE_DEVICE_SECURE_OPEN, FALSE,//必须为FALSE &pdoDeviceObj//生成的设备对象指针 ); if(!NT_SUCCESS(status)) { return status; } // 创建符号链接 status = IoCreateSymbolicLink( &symb_link_name, &my_device_name ); if(!NT_SUCCESS(status)) { IoDeleteDevice(pdoDeviceObj); return status; } DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = DRIVERCOPYFILE_DispatchCreateClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceIOControl;//设置为自己定义的分发函数 DriverObject->DriverUnload = DRIVERCOPYFILE_DriverUnload; return STATUS_SUCCESS; } #ifdef __cplusplus }; // extern "C" #endif
typedef struct mymessage { PWSTR SourceFileName; PWSTR DestinFileName; }MyMessage,*PMyMessage; NTSTATUS MyCopyFile( PUNICODE_STRING pSourceFile, PUNICODE_STRING pDestinFile);
应用层测试代码:
#include <stdio.h> #include <Windows.h> //用户自定义控制码 用于和应用层的通信 #define MYCOPYFILE_CODE \ (ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,\ 0xa01,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA) typedef struct mymessage { PWSTR SourceFileName; PWSTR DestinFileName; }MyMessage,*PMyMessage; int main() { BOOL ret; DWORD length=0; MyMessage input_buffer; //用CreateFile打开驱动 HANDLE device=CreateFileW( L"\\\\.\\MyCF", //应用层应写成这种形式 GENERIC_READ|GENERIC_WRITE,0,0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,0); if (device==INVALID_HANDLE_VALUE) { printf("open device error! %d",GetLastError()); system("pause"); return -1; } //填写自定义buffer input_buffer.DestinFileName=L"D:\\MyCopyFile.txt"; input_buffer.SourceFileName=L"D:\\output.txt"; printf("%d",sizeof(input_buffer)); ret=DeviceIoControl( device, MYCOPYFILE_CODE,//我们自定义的控制码 (PVOID)&input_buffer,//输入缓冲区 sizeof(input_buffer), NULL,//没有输出缓冲区 0,//输出缓冲区的长度 &length, NULL ); if (!ret) { printf("Device IO error!"); return -2; } CloseHandle(device); system("pause"); return 0; }