现在的位置: 首页 > 综合 > 正文

驱动入门实战演练–在驱动下实现自己的CopyFile

2018年06月06日 ⁄ 综合 ⁄ 共 11845字 ⁄ 字号 评论关闭

   

一、一些废话

  第一篇驱动类的博文,希望大家多多支持~!

  入门驱动不久,感觉这一阶段还是遇到了挺多困难,看书的过程中书本上的知识多多少少会和亲身实践有差别,尤其是进到R0级后不再像R3下能那么'为所欲为'了,细节方面的东西特别多,而且一不小心就给蓝了,还好调试时在虚拟机下,鼠标点两下就能重启。其实驱动说难也难,说简单也简单,多动动手,多码几个字,多瞄两眼,熟悉了也就不难了。

  废话不多说,先推荐些Windows驱动入门必备利器:

  1.《天书夜读》Windows驱动编程基础教程  想要快速入门,这个当之无愧。

  2.《从汇编语言到Windows内核编程》  很经典的一本书,本文就是在其基础上写成的(个人很喜欢的一本书)

  3.《寒江独钓 Windows内核安全编程》.(谭文等)  相当不错的一本驱动入门书籍

  4.  竹林蹊径:深入浅出Windows驱动开发   这本书个人觉得不如前几本好,跳跃性有点大

  

  本文的目的是通过实现驱动下的CopyFile熟悉驱动编程。

  关键词: 驱动(内核)编程   内核字符串   应用层与驱动的通信   IRP请求  


二、前缀知识


  在上代码前让我们先快速回顾下驱动基础知识。

①、驱动环境搭建

  网上关于驱动环境搭建的教程已有很多,而且也并不复杂,无论是xp还是win7都有很好的教程,这里就不再重复了。
  我搭建的驱动环境是: 主机--win7 64位  wdk vs2008  虚拟机--xp wdk vs2008,使用windbg双机调试
 

②、Windbg常用命令

  windbg是在windows平台下,强大的用户态和内核态调试工具。相比较于VS,它是一个轻量级的调试工具,所谓轻量级指的是它的安装文件大小较小,但是其调试功能,却比VS更为强大。它的另外一个用途是可以用来分析dump数据。
由于windbg命令很多,而在一般调试中很多都不会用到,这里仅仅介绍一些最基本也是最常用的一些命令

 最常用的一些命令如下: 

 dv  -- 查看局部变量             dv /i  
 --查看局部变量, 并显示符号的类型和参数类型
 bl   -- 查看所有断点             bc*
 
    --清除所有断点
 bu FileName!Function                  --下延迟断点
 bp FileName!Function  /  bp +地址    --设置条件断点

③、内核中的UNICODE_STRING

  出于对安全的考虑,在驱动中几乎是UNICODE_STRING的天下,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;

  这里要注意的就是上面红色字体bytes,为什么说这个字很重要呢,因为Buffer中是WSTR类型的字符串,在获取字符串长度时常常会用sizeof( .. ),而sizeof得出的结果是以WSTR为单位的,也就是说结果和Length的值存在两倍的关系。 比如: PWSTR a="1111";  sizeof(a)=4; 而Length的值却为 4*2=8; 这点在给UNICODE_STRING手动赋值时要特别注意。

  在得到UNICODE_STRING结构后的第一件事就是要对其进行初始化,在初始化时最常用的有如下:
  1、RTL_CONSTANT_STRING
  这是一个已经定义好的宏,用在常量赋值上(注意只能在初始化定义字符串时使用)

  2、RtlInitUnicodeString
  上面那个宏只能用在定义常量上,而RtlInitUnicodeString则能用在随时定义变量上。


  初始化完就该操作这些UNICODE_STRING了,基本操作有拷贝、连接、打印等等
  1、RtlCopyUnicodeString
  这个函数用来拷贝字符串。

  2、RtlAppendUnicodeToString
  这个函数将一个常量字符串连接至一个UNICODE_STRING后

  3、RtlAppendUnicodeStringToString
  这个与上面那个相似,都是用来连接字符串的,区别在于这个是连接两个UNICODE_STRING

  当然还有很多字符串操作的函数,限于篇幅就不一 一介绍了。

④、OBJECT_ATTRIBUTE结构体

  在操作文件、注册表时OBJECT_ATTRIBUTE结构体显得至关重要,R3层操作文件只是简简单单的传入文件路径就能完成,而在R0下却要填写这样一个结构体。其初始化操作由InitializeObjectAttributes函数执行。具体结构如下:
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与驱动交互

  说到驱动与应用层的交互当然不能不提IRP(I/O Request Package),相当于R3层的消息,与消息的消息处理函数对应,IRP也有相应的处理例程(驱动下可以简单认为例程就是函数啦)。不同IRP当然就该有不同的标示符,这个标示符就是IRP的“控制码”,我们也可以自定义控制码,定义控制码方法如下:
//用户自定义控制码 用于和应用层的通信
#define MYCOPYFILE_CODE \
	(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN,\
	0xa01,METHOD_BUFFERED,FILE_READ_DATA|FILE_WRITE_DATA)

其中MYCOPYFILE_CODE即我们自定义的控制码,在初始化时需给他一个‘代号’,即上面的'0xa01'(这个可以自己随意定,只要不与系统的重复就好)。定义控制码的其余部分在现阶段可以认为都一样。

  控制码可以连通应用层和驱动内核,应用层与驱动的交互有多种方法,这里主要介绍使用DeviceIoControl,其结构如下:
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为单位。

 使用DeviceIoControl的好处是这个函数能为输入输出提供了统一的接口,无需使用WriteFile写入而用ReadFile来读出。
 我们可以定义自己的数据结构然后转成LPVOID,再传给驱动,具体操作见完整代码。


三、完整代码及详细注释

本次实践的功能很简单,仅仅是在驱动下接受应用层传入的文件名,然后拷贝源文件至目标文件,没啥好说的就直接看代码吧。
驱动代码:
///////////////////////////////////////////////////////////////////////////////
///
/// 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

DriverCopyFile.h 中的主要代码(有些是vs2008自动生成的就不贴出来了)
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;
}

四、总结

  本文通过实现驱动下的CopyFile让大家熟悉了驱动下的基本操作。驱动编程只有自己实际动手写写才会知道自己错在哪,其实IT行业也就是这样,多动手比只看出好得多,毕竟“绝知此事要躬行”啊!
  本文仅仅是驱动编程的冰山一脚,另外本人能力有限,也是初学驱动,难免有些遗漏及不足之处,还请大家多多指教!



抱歉!评论已关闭.