内核与驱动_01_内核通信实战

实战练习

1.创建设备对象并使用

  • 0环代码:
#include <ntddk.h>

//这里一定要记住名称的固定格式
#define DEVICE_NAME L"\\Device\\MyDevice"
//!!!!DosDevices,一定要记得加s
#define DEVICE_SYMBOL_NAME L"\\DosDevices\\MyDeviceSym"
//初始化设备对象的名字字符串
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(DEVICE_NAME);
//符号链接字符串名称
UNICODE_STRING symName = RTL_CONSTANT_STRING(DEVICE_SYMBOL_NAME);

NTSTATUS OnDeviceCreate(DEVICE_OBJECT *pDevice, IRP *pIrp);
NTSTATUS OnDeviceRead(DEVICE_OBJECT *pDevice, IRP *pIrp);
NTSTATUS OnDeviceWrite(DEVICE_OBJECT *pDevice, IRP *pIrp);
NTSTATUS OnDeviceClose(DEVICE_OBJECT *pDevice, IRP *pIrp);

//驱动卸载函数
VOID OnDriverUnload(PDRIVER_OBJECT pDriver)
{
	//卸载设备对象和符号链接
	IoDeleteSymbolicLink(&symName);
	pDriver->DeviceObject;	//设备对象链表
	IoDeleteDevice(pDriver->DeviceObject);
	KdPrint(("项目被卸载\n"));
}


//驱动入口函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path)
{
	reg_path;	//引用未使用的形参
	NTSTATUS status = STATUS_SUCCESS;
	pDriver->DriverUnload = OnDriverUnload;
	//1. 创建设备对象
	PDEVICE_OBJECT pDevice = NULL;
	status = IoCreateDevice(
		pDriver,			//新设备对象的所属驱动对象
		0,					//扩展大小
		&deviceName,		//新设备对象的名称
		FILE_DEVICE_UNKNOWN,//设备的类型
		0,
		0,
		&pDevice);			//接收新的设备
	//判断是否成功,在内核编程时最好都做判断,防止蓝屏
	if (!NT_SUCCESS(status))
	{
		KdPrint(("创建新设备失败,错误码:%08x\n", status));
		return status;
	}
	pDevice->Flags |= DO_DIRECT_IO;	// or DO_DIRECT_IO
	//2. 绑定符号链接
	IoCreateSymbolicLink(
		&symName,			//符号链接名
		&deviceName			//要绑定到的设备对象名
	);
	//3. 绑定派遣函数
	pDriver->MajorFunction[IRP_MJ_CREATE] = OnDeviceCreate;
	pDriver->MajorFunction[IRP_MJ_READ] = OnDeviceRead;
	pDriver->MajorFunction[IRP_MJ_WRITE] = OnDeviceWrite;
	pDriver->MajorFunction[IRP_MJ_CLOSE] = OnDeviceClose;
	return status;
}

/*****************派遣函数*******************/

NTSTATUS OnDeviceCreate(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	pDevice;
	KdPrint(("设备对象被创建了\n"));
	//设置3环的CreateFile的函数执行结果
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;	//IO实际完成的字节数
	//将IRP设置为完成
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

NTSTATUS OnDeviceRead(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	pDevice;
	KdPrint(("设备被读取了\n"));
	PCHAR pBuff = NULL;
	//用户层缓冲区有如下3种
	pIrp->AssociatedIrp.SystemBuffer;	//系统的堆空间
	pIrp->MdlAddress;					//MDL
	pIrp->UserBuffer;					//用户从虚拟地址
	//这三种缓冲区前两种是二选一,具体选哪一个,取决于设备对象的Flags字段的值
	//这个值默认是使用系统堆空间,也可一手动修改它的值为:DO_BUFFERED_IO\DO_DIRECT_IO
	//前者是使用缓冲方式(副本方式),后者使用MDL
	if (pIrp->MdlAddress!=NULL)
	{
		//需要一个函数,让MDL对象映射出一个新的系统领空的虚拟地址
		//MmGetSystemAddressForMdlSafe函数获取系统MDl对象映射出来的新的虚拟地址
		//这个虚拟地址可以直接修改到用户从的虚拟地址空间
		pBuff = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, 0);
	}
	else if (pIrp->AssociatedIrp.SystemBuffer!=NULL)
	{
		pBuff = pIrp->AssociatedIrp.SystemBuffer;
	}
	else if (pIrp->UserBuffer!=NULL)
	{
		//没有缓冲区
		pBuff = NULL;
	}
	if (pBuff!=NULL)
	{
		//将数据写入到缓冲区,用户层就可以接收到数据了
		RtlCopyMemory(pBuff, "来自内核的一段字符串", 21);
		//设置完成的字节数,如果没有设置,并且缓冲区的获得方式是缓冲IO
		//的方式,那么,即使把数据写入到缓冲区了,系统也不会将这个缓冲区
		//拷贝到用户层的地址
		// 因为系统在拷贝时,是 pIrp->IoStatus.Information 
		// 来提供拷贝的字节数的.
		// 同时pIrp->IoStatus.Information 的值, 也决定了
		// 用户层函数ReadFile的第4个参数(lpNumberOfBytesRead)
		// 被输出多少字节.
		pIrp->IoStatus.Information = 21;	//IO实际完成的字节数
	}
	//设置3环的CreateFile的函数执行结果
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	//将IRP设置为完成
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

NTSTATUS OnDeviceWrite(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	pDevice;
	KdPrint(("设备被写入了\n"));
	char* pBuffer = NULL;
	if (pIrp->MdlAddress!=NULL)
	{
		pBuffer = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, 0);
	}
	else if (pIrp->AssociatedIrp.SystemBuffer!=NULL)
	{
		pBuffer = pIrp->AssociatedIrp.SystemBuffer;
	}
	else 
	{
		//没有缓冲区
		pBuffer = NULL;
	}
	if (pBuffer!=NULL)
	{
		//输出
		KdPrint(("[内核]:从用户层传入的数据--[%s]\n", pBuffer));
	}
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	pIrp->IoStatus.Information = 0;	
	//将IRP设置为完成
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

NTSTATUS OnDeviceClose(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	pDevice;
	KdPrint(("设备对象被释放了\n"));
	//设置3环的CreateFile的函数执行结果
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	//将IRP设置为完成
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
  • 3环用户程序代码
int main()
{
	HANDLE hDevice = CreateFile(
		L"\\\\.\\MyDeviceSym",
		GENERIC_READ|GENERIC_WRITE,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		0);
	if (hDevice==INVALID_HANDLE_VALUE)
	{
		printf("[3环]:设备打开失败,%d\n", GetLastError());
		system("pause");
		return 0;
	}
	char buff[] = "这是一个来自用户层的字符串";
	DWORD dwRelSize = 0;
	WriteFile(hDevice, buff, sizeof(buff), &dwRelSize, 0);

	char str[0x10] = {};
	ReadFile(hDevice, str, 0x10, &dwRelSize, 0);
	printf("[3环]:获取到的字符串--[%s]\n", str);
	CloseHandle(hDevice);
	system("pause");
	return 0;
}

2.使用DeviceIoContral进行内核通讯

  • 使用ReadFileWriteFile进行内核通讯时有个缺点就是只能读/写,不能传递额外的命令。
  • 这种情况下我们可以通过DeviceIoContral来实现,在用户层调用这个函数后,驱动对象的派遣函数IRP_MJ_DEVICE_CONTROL会被调用,在内核层的派遣函数中,通过IO_STACK_COMPLATE.Parameters.DeviceIoControl.IoControlCode来得到用户层传入进来的控制码。

R3代码

  • Data.h头文件,用于存储所有共享的数据结构
#pragma once
#include <Windows.h>
#define MYCTRLCODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN,0x800+(code),METHOD_BUFFERED,FILE_ANY_ACCESS)

typedef enum _MyCtrlCode
{
	readProcessMemory = MYCTRLCODE(0),
	writeProcessMemory= MYCTRLCODE(1),
}MyCtrlCode;

typedef struct _ProcessInfo {
	ULONG dwPid;
	void* address;
	char buff[1000];
	int buffSize;
}ProcessInfo, *PProcessInfo;
  • main代码
// 03-DeviceIoControl-R3.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include "pch.h"
#include <iostream>
#include <Windows.h>
#include "Data.h"


int main()
{
	HANDLE hDevice = CreateFile(L"\\\\.\\MyDeviceSymCtrl", GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (hDevice==INVALID_HANDLE_VALUE)
	{
		printf("[3环程序]打开设备失败: %d\n", GetLastError());
		system("pause");
		return 0;
	}
	//构造发送的信息
	DWORD dwPid = 1234;
	char buff[0x50];
	DWORD dwRelSize = 0;
	//DeviceIoControl完成通信
	DeviceIoControl(
		hDevice,			//设备对象
		readProcessMemory,	//控制码
		&dwPid,				//输入缓冲区,传送给设备的数据
		sizeof(DWORD),		//输入缓冲区大小
		buff,				//输出缓冲区
		sizeof(buff),		//输出缓冲去大小
		&dwRelSize,			//实际完成的字节
		NULL);
	printf("发送[writeProcessMemory]类型数据完毕,实际完成字节数:%d,得到数据:%s\n", dwRelSize, buff);
	ProcessInfo proInfo = { 1234,(void*)0x400000,"123456",7 };
	DeviceIoControl(
		hDevice,			//设备对象
		writeProcessMemory,	//控制码
		&proInfo,			//输入缓冲区
		sizeof(proInfo),	//输入缓冲区大小
		buff,				//输出缓冲区
		sizeof(buff),		//输出缓冲区大小
		&dwRelSize,			//实际完成的字节数
		NULL);
	printf("发送[readProcessMemory]类型数据完毕,实际完成字节数:%d,得到数据:%s\n", dwRelSize, buff);
	CloseHandle(hDevice);
	system("pause");
	return 0;
}

R0代码

  • Data.h,和3环的应用程序区别在于宏MYCTRL_CODE的定义,这里为了避免重定义我直接使用了简单的数据进行了替换
#pragma once
#include <ntddk.h>
#define MYCTRLCODE(code) CTL_CODE(FILE_DEVICE_UNKNOWN,0x800+(code),METHOD_BUFFERED,FILE_ANY_ACCESS)

typedef enum _MyCtrlCode
{
	readProcessMemory = MYCTRLCODE(0),
	writeProcessMemory = MYCTRLCODE(1),
}MyCtrlCode;

typedef struct _ProcessInfo {
	ULONG dwPid;
	void* address;
	char buff[1000];
	int buffSize;
}ProcessInfo, *PProcessInfo;
  • DriverEntry代码
#include <ntddk.h>
#include "Data.h"
#define DEVICE_NAME L"\\Device\\MyDeviceCtrl"
#define DEVICE_SYMBOL_NAME L"\\DosDevices\\MyDeviceSymCtrl"

UNICODE_STRING DeviceName = RTL_CONSTANT_STRING(DEVICE_NAME);
UNICODE_STRING DeviceSymName = RTL_CONSTANT_STRING(DEVICE_SYMBOL_NAME);

//函数声明
NTSTATUS OnDeviceClose(DEVICE_OBJECT *pDevice, IRP *pIrp);
NTSTATUS OnDeviceControl(DEVICE_OBJECT *pDevice, IRP *pIrp);
NTSTATUS OnDeviceCreate(DEVICE_OBJECT *pDevice, IRP *pIrp);

NTSTATUS OnDeviceIoCtrlReadProcessMemory(PDEVICE_OBJECT pDevice, PIRP pIrp);
NTSTATUS OnDeviceIoCtrlWriteProcessMemory(PDEVICE_OBJECT pDevice, PIRP pIrp);

//构造一个消息泵,用于控制消息的收发
typedef struct _DeviceIoCtrlHandler {
	ULONG ctrlCode;
	NTSTATUS(*callback)(PDEVICE_OBJECT pDevice, PIRP pIrp);
}DeviceIoCtrlHandler,*PDeviceIoCtrlHandler;

//将所有的和控制IO有关的派遣函数定义在这个地方
DeviceIoCtrlHandler g_handler[] =
{
	{readProcessMemory,OnDeviceIoCtrlReadProcessMemory},
	{writeProcessMemory,OnDeviceIoCtrlWriteProcessMemory},
};


PCHAR g_buff = NULL;

VOID OnDriverUnload(PDRIVER_OBJECT pDriver)
{
	DbgBreakPoint();
	pDriver;
	IoDeleteSymbolicLink(&DeviceSymName);
	pDriver->DeviceObject;	//设备对象链表
	IoDeleteDevice(pDriver->DeviceObject);
	KdPrint(("驱动已被卸载\n"));
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pPath)
{
	pPath;
	NTSTATUS status = STATUS_SUCCESS;
	pDriver->DriverUnload = OnDriverUnload;
	//新建一个设备对象指针
	PDEVICE_OBJECT pDevice = NULL;
	//创建新的设备对象
	status = IoCreateDevice(
		pDriver,				//新设备对象的所属驱动对象
		0,						//扩展大小
		&DeviceName,			//设备对象名称
		FILE_DEVICE_UNKNOWN,	//设备类型
		0,
		0,
		&pDevice);				//接收新的设备对象指针
	if (!NT_SUCCESS(status))
	{
		KdPrint(("创建设备失败,错误码:%08x\n", status));
		return status;
	}
	pDevice->Flags |= DO_BUFFERED_IO;	//DO_DIRECT_IO
	//绑定符号链接
	IoCreateSymbolicLink(&DeviceSymName,&DeviceName);
	//绑定派遣函数
	pDriver->MajorFunction[IRP_MJ_CREATE] = OnDeviceCreate;
	pDriver->MajorFunction[IRP_MJ_CLOSE] = OnDeviceClose;
	pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = OnDeviceControl;
	return status;
}

NTSTATUS OnDeviceCreate(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	pDevice;
	KdPrint(("设备对象已创建\n"));
	//设置IRP为完成
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
NTSTATUS OnDeviceClose(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	pDevice;
	KdPrint(("设备对象已关闭\n"));
	//设置IRP为完成
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}
NTSTATUS OnDeviceControl(DEVICE_OBJECT *pDevice, IRP *pIrp)
{
	DbgBreakPoint();
	pDevice;
	//还是判断使用那那种缓冲区
	//PCHAR pBuff = NULL;
	if (pIrp->MdlAddress!=NULL)
	{
		g_buff =(PCHAR) MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, 0);
	}
	else if (pIrp->AssociatedIrp.SystemBuffer!=NULL)
	{
		g_buff = (PCHAR)pIrp->AssociatedIrp.SystemBuffer;
	}
	else
	{
		g_buff = NULL;
	}
	//获取当前IO栈
	IO_STACK_LOCATION* pIoStack = IoGetCurrentIrpStackLocation(pIrp);
	//得到输入和输出缓冲区的字节数
	ULONG uInputLen = pIoStack->Parameters.DeviceIoControl.InputBufferLength;
	ULONG uOutputLen = pIoStack->Parameters.DeviceIoControl.OutputBufferLength;
	//得到控制码
	ULONG uCtrlCode = pIoStack->Parameters.DeviceIoControl.IoControlCode;

	KdPrint(("控制码:%08x 输入长度:%d  输出长度:%d\n", uCtrlCode, uInputLen, uOutputLen));

	//依据控制码来完成不同的操作 
	int i = 0;
	while (g_handler[i].callback!=NULL)
	{
		if (g_handler[i].ctrlCode == uCtrlCode)
		{
			g_handler[i].callback(pDevice, pIrp);
			break;
		}
		++i;
	}
	//设置IRP为完成
	pIrp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(pIrp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

NTSTATUS OnDeviceIoCtrlReadProcessMemory(PDEVICE_OBJECT pDevice, PIRP pIrp)
{
	pDevice;
	DbgBreakPoint();
	//接收3环传递的4字节的PId
	//可以将缓冲区强制转换成一个DWORD*来使用
	PULONG pPid = (PULONG)g_buff;
	KdPrint(("[内核层]:读取进程内存的请求\n"));
	KdPrint(("读取到的PID=%d\n", pPid));
	//输入缓冲区同时也是输出缓冲区
	RtlCopyMemory(g_buff, "hello World", 12);
	//设置输出的字节数
	pIrp->IoStatus.Information = 12;
	return STATUS_SUCCESS;
}
NTSTATUS OnDeviceIoCtrlWriteProcessMemory(PDEVICE_OBJECT pDevice, PIRP pIrp)
{
	pDevice;
	DbgBreakPoint();
	//接收3环传过来的结构体
	PProcessInfo pProInfo = (PProcessInfo)g_buff;
	KdPrint(("[内核]写进程内存的请求\n"));
	KdPrint(("pid=%d address=%p buff=%s buffSize=%d\n",
		pProInfo->dwPid,
		pProInfo->address,
		pProInfo->buff,
		pProInfo->buffSize));
	//设置缓冲区返回内容
	RtlCopyMemory(g_buff, "已经接收到", 11);
	//设置输出的字节数
	pIrp->IoStatus.Information = 11;
	return STATUS_SUCCESS;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值