驱动通信1

05 驱动通信

介绍

通信流程:

R3->符号链接->设备对象->驱动对象->驱动功能

驱动通信实质上是设备通信

设备是挂在驱动上的DeviceObject上面的

所以我们看一下设备对象

可以参考微软官方文档

kd> dt _DEVICE_OBJECT
ntdll!_DEVICE_OBJECT
+0x000 Type             : Int2B//类型(设备对象)
+0x002 Size             : Uint2B//大小
+0x004 ReferenceCount   : Int4B//引用计数(被引用了多少次)
+0x008 DriverObject     : Ptr32 _DRIVER_OBJECT//驱动对象(谁创建了这个设备)
+0x00c NextDevice       : Ptr32 _DEVICE_OBJECT//下一个设备
+0x010 AttachedDevice   : Ptr32 _DEVICE_OBJECT//附加设备
+0x014 CurrentIrp       : Ptr32 _IRP//当前的IRP请求包
+0x018 Timer            : Ptr32 _IO_TIMER//时钟(每个设备可以带一个定时器)
+0x01c Flags            : Uint4B//描述当前设备的读写方式、标志位等信息
+0x020 Characteristics  : Uint4B//设置是否读栈等相关属性
+0x024 Vpb              : Ptr32 _VPB//磁盘相关的
+0x028 DeviceExtension  : Ptr32 Void//可以给设备添加绑定参数
+0x02c DeviceType       : Uint4B//设备类型
+0x030 StackSize        : Char//设备栈的大小
+0x034 Queue            : <unnamed-tag>//队列
+0x05c AlignmentRequirement : Uint4B
+0x060 DeviceQueue      : _KDEVICE_QUEUE
+0x074 Dpc              : _KDPC//时钟
+0x094 ActiveThreadCount : Uint4B
+0x098 SecurityDescriptor : Ptr32 Void
+0x09c DeviceLock       : _KEVENT
+0x0ac SectorSize       : Uint2B
+0x0ae Spare1           : Uint2B
+0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION
+0x0b4 Reserved         : Ptr32 Void

关键点说明

DRIVER_OBJECTDEVICE_OBJECT互指

一个驱动对象可以创建多个设备

由于Windows不可能让我们直接访问设备,所以创建了FDOPDO物理设备对象(Physical Device Object,简称PDO),一个是功能设备对象(Function Device Object,简称FDO),所以在访问设备前会经过功能设备对象(FDO),所以说是功能设备对象附加到设备上

FDO附加到PDO

PDO是底层驱动(下层驱动),FDO是高层驱动(上层驱动)。

FDO和PDO可以参考微软的官方说明

可以看到在一个设备上面附加了多个驱动

IRP结构

所有对设备的通信都被封装成IRO(IO Request Package)发到FDO

kd> dt _IRP
ntdll!_IRP
+0x000 Type             : Int2B
+0x002 Size             : Uint2B
+0x004 MdlAddress       : Ptr32 _MDL
+0x008 Flags            : Uint4B
+0x00c AssociatedIrp    : <unnamed-tag>
+0x010 ThreadListEntry  : _LIST_ENTRY
+0x018 IoStatus         : _IO_STATUS_BLOCK
+0x020 RequestorMode    : Char
+0x021 PendingReturned  : UChar
+0x022 StackCount       : Char//有多少个设备栈
+0x023 CurrentLocation  : Char//当前设备栈的索引
+0x024 Cancel           : UChar
+0x025 CancelIrql       : UChar
+0x026 ApcEnvironment   : Char
+0x027 AllocationFlags  : UChar
+0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK
+0x02c UserEvent        : Ptr32 _KEVENT
+0x030 Overlay          : <unnamed-tag>
+0x038 CancelRoutine    : Ptr32     void 
+0x03c UserBuffer       : Ptr32 Void//从R0传回R3的数据缓冲区
+0x040 Tail             : <unnamed-tag>

创建设备 IoCreateDevice

IoCreateDevice函数原型

NTSTATUS IoCreateDevice(
  [in]           PDRIVER_OBJECT  DriverObject,//驱动对象
  [in]           ULONG           DeviceExtensionSize,//设备拓展大小
  [in, optional] PUNICODE_STRING DeviceName,//设备名称(需要提前初始化)
  [in]           DEVICE_TYPE     DeviceType,//设备类型,我们创建的都是虚拟设备,所以用FILE_DEVICE_UNKNOWN
  [in]           ULONG           DeviceCharacteristics,//设备属性,正常情况下用FILE_DEVICE_SECURE_OPEN
  [in]           BOOLEAN         Exclusive,//是否读栈
  [out]          PDEVICE_OBJECT  *DeviceObject//创建成功后返回的设备对象
);

其实仔细看上面的函数原型,可以发现基本就是上面的_DEVICE_OBJECT是差不多一致,只是少了很多

demo:

#include <ntifs.h>

#define DEVICE_NAME L"\\Device\\mydevice"//设备名称
#define SYM_NAME L"\\??\\mysym"//符号链接名称


VOID Unload(PDRIVER_OBJECT pDriver)
{
	DbgPrint("unload\r\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();
	UNICODE_STRING uName = { 0 };
	RtlInitUnicodeString(&uName, DEVICE_NAME);//初始化设备名称
	PDEVICE_OBJECT pDevice = NULL;
	NTSTATUS status = IoCreateDevice(pDriver, 0, &uName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);//创建设备,将设备对象保存到pDevice中
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((77, 0, "创建失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建成功\r\n");
	return STATUS_SUCCESS;
}

那么现在就可以在R3访问到我们的设备了么,显然是不能的,我们需要创建符号链接,让R3找到我们的设备

IoCreateSymbolicLink函数就不过多解释了,就两个参数PUNICODE_STRING类型的符号名字设备对象名

作用就是绑定,将符号名和设备名绑定

demo:

UNICODE_STRING uSymName = { 0 };
RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名

status = IoCreateSymbolicLink(&uSymName, &uName);//将符号名和设备名绑定
if (!NT_SUCCESS(status))
{
	IoDeleteDevice(pDevice);//删除设备对象
	KdPrintEx((77, 0, "创建符号连接失败[db]:status:%x\r\n", status));
	return status;
}
pDevice->Flags &= ~DO_DEVICE_INITIALIZING;//设备对象初始化标志去掉
pDevice->Flags |= DO_BUFFERED_IO;//设置通信方式为IO通信

现在可以通信了么?还是不可以的,需要设置回调函数

回调函数

最基础的通信必须要填充下面这几个派发函数

pDriver->MajorFunction[IRP_MJ_CREATE] = ?; //填充打开
pDriver->MajorFunction[IRP_MJ_CLOSE] = ?; //填充关闭
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ?; //填充通信

MajorFunction成员

#define IRP_MJ_CREATE                   0x00//必须填充(创建)
#define IRP_MJ_CREATE_NAMED_PIPE        0x01//必须填充(关闭)
#define IRP_MJ_CLOSE                    0x02
#define IRP_MJ_READ                     0x03//可以用来通信
#define IRP_MJ_WRITE                    0x04//可以用来通信
#define IRP_MJ_QUERY_INFORMATION        0x05
#define IRP_MJ_SET_INFORMATION          0x06
#define IRP_MJ_QUERY_EA                 0x07
#define IRP_MJ_SET_EA                   0x08
#define IRP_MJ_FLUSH_BUFFERS            0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
#define IRP_MJ_DIRECTORY_CONTROL        0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL  0x0f
#define IRP_MJ_SHUTDOWN                 0x10
#define IRP_MJ_LOCK_CONTROL             0x11
#define IRP_MJ_CLEANUP                  0x12
#define IRP_MJ_CREATE_MAILSLOT          0x13
#define IRP_MJ_QUERY_SECURITY           0x14
#define IRP_MJ_SET_SECURITY             0x15
#define IRP_MJ_POWER                    0x16
#define IRP_MJ_SYSTEM_CONTROL           0x17//必须填充(通信)
#define IRP_MJ_DEVICE_CHANGE            0x18
#define IRP_MJ_QUERY_QUOTA              0x19
#define IRP_MJ_SET_QUOTA                0x1a
#define IRP_MJ_PNP                      0x1b
#define IRP_MJ_PNP_POWER                IRP_MJ_PNP      
#define IRP_MJ_MAXIMUM_FUNCTION         0x1b

还要填一个默认的派发函数

对着这个按F12进去就能看到

NTSTATUS DefDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	//定义默认的派发函数
	//走个形式而已
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

接下来我们就可以填充派发函数的成员了

pDriver->MajorFunction[IRP_MJ_CREATE] = DefDispatch; //填充打开(默认的派发函数)
pDriver->MajorFunction[IRP_MJ_CLOSE] = DefDispatch; //填充关闭(默认的派发函数)
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Dispatch; //填充通信(真正执行操作的派发函数)

接下来我们要写我们真正执行我们需要的操作的派遣函数

先看一下和IRP有关的,毕竟从三环传进来的是IRP包

这个函数是获取传进来irp的程序的当前的栈

不过栈中只有我们的控制码等输入输出的大小,没有我们R3传进来的参数

__drv_aliasesMem
		PIO_STACK_LOCATION//断言
		IoGetCurrentIrpStackLocation(
			_In_ PIRP Irp
		)

调用方法

PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);//拿到当前发送IRP包的栈

这个拿到的栈的信息里面有功能号

所以我们可以这样判断一下是不是我们指定的功能号

//只有IRP包中的功能号是我们指定的派发函数的功能号才执行下面的操作
if (ioStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
{
	//code
}

接下来获得R3传进来的异步控制码(IoControlCode)

//拿到R3的异步控制码,这个是R3传进来自定义,
ULONG IoControlCode = ioStack->Parameters.DeviceIoControl.IoControlCode;
//比如R3传进来异步控制码是1执行什么,传进来2执行什么
switch (IoControlCode)
{
case 1:
	break;
case 2:
	break;
}

接下来要定义通信码(异步控制码)

先看一下CTL_CODE原型

#define CTL_CODE( DeviceType, Function, Method, Access ) (                 \
    ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
//DeviceType 设备类型
//Function 索引号(一般自定义的都是0x800开始)
//Method 通信方式,三种:METHOD_BUFFERED、METHOD_IN_DIRECT、METHOD_NEITHER
//Access 这个通信码的访问权限FILE_ANY_ACCESS

接着定义一下我们需要的

#define INIT_KEY CTL_CODE(FILE_DEVICE_UNKNOWN, CODE_CTR_INDEX, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define READ_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 +1, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define WRITE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 +2, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define GET_BASE_DLL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 +3, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define SET_PROTECT_PID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800 +4, METHOD_BUFFERED, FILE_ANY_ACCESS )

接下来我们上面写的switch代码片段就可以变成下面这种形式

switch (IoControlCode)
{
case INIT_KEY:
	//code
	break;
case READ_MEMORY:
	//code
	break;
}

我们R3通过IRP包传进来的参数在这里Irp->AssociatedIrp.SystemBuffer

demo:

switch (IoControlCode)
{
	case TEST:
		{
			//栈中只有我们的控制码等输入输出的大小,没有我们传进来的参数
			//这里假设我们传进来一个整数
			int* x = (int*)Irp->AssociatedIrp.SystemBuffer;//我们R3通过IRP包传进来的参数在这里
			KdPrint(("Entry!\r\n"));
			break;
		}
		case 2:
			break;
		}

卸载驱动

VOID Unload(PDRIVER_OBJECT pDriver)
{
	//在这里又初始化了一遍符号链接,为了防止在接下来的去掉符号链接的时候符号链接没有初始化导致蓝屏
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	//去掉符号链接
	IoDeleteSymbolicLink(&uSymName);
	//去掉设备对象
	IoDeleteDevice(pDriver->DeviceObject);
	DbgPrint("unload\r\n");
}

R0 完整代码

#include <ntifs.h>

#define DEVICE_NAME L"\\Device\\mydevice"//设备名称
#define SYM_NAME L"\\??\\mysym"//符号链接名称
//定义通讯码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )


NTSTATUS DefDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	//定义默认的派发函数
	//走个形式而已
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

NTSTATUS Dispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	//定义我们真正的派发函数
	PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);//拿到当前发送IRP包的栈

	//只有IRP包中的功能号是我们指定的派发函数的功能号才执行下面的操作
	if (ioStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
	{
		//获取IRP包中的栈的信息的DeviceIoControl信息,进而获取从R3带进来的参数的长度
		int size = ioStack->Parameters.DeviceIoControl.InputBufferLength;
		//拿到R3的异步控制码,这个是R3传进来自定义,
		ULONG IoControlCode = ioStack->Parameters.DeviceIoControl.IoControlCode;
		//比如R3传进来异步控制码是1执行什么,传进来2执行什么
		switch (IoControlCode)
		{
		case TEST:
		{
			//栈中只有我们的控制码等输入输出的大小
			//我们R3通过IRP包传进来的参数在这里
			//这里假设我们传进来一个整数
			int* x = (int*)Irp->AssociatedIrp.SystemBuffer;
			DbgPrint("num: %x", *x);
			break;
		}
		case 2:
			break;

		}
	}
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

VOID Unload(PDRIVER_OBJECT pDriver)
{
	//在这里又初始化了一遍符号链接,为了防止在接下来的去掉符号链接的时候符号链接没有初始化导致蓝屏
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	//去掉符号链接
	IoDeleteSymbolicLink(&uSymName);
	//去掉设备对象
	IoDeleteDevice(pDriver->DeviceObject);
	DbgPrint("unload\r\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();

	//创建设备对象
	UNICODE_STRING uName = { 0 };
	RtlInitUnicodeString(&uName, DEVICE_NAME);//初始化设备名称
	PDEVICE_OBJECT pDevice = NULL;
	NTSTATUS status = IoCreateDevice(pDriver, 0, &uName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((77, 0, "创建设备对象失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建设备对象成功\r\n");

	//创建符号链接
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	status = IoCreateSymbolicLink(&uSymName, &uName);//将符号名和设备名绑定
	if (!NT_SUCCESS(status))
	{
		IoDeleteDevice(pDevice);//删除设备对象
		KdPrintEx((77, 0, "创建符号链接失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建符号链接成功\r\n");
	pDevice->Flags &= ~DO_DEVICE_INITIALIZING;//设备对象初始化信息去掉
	pDevice->Flags |= DO_BUFFERED_IO;//设置通信方式为IO通信

	pDriver->MajorFunction[IRP_MJ_CREATE] = DefDispatch; //填充打开
	pDriver->MajorFunction[IRP_MJ_CLOSE] = DefDispatch; //填充关闭
	pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Dispatch; //填充通信
	return STATUS_SUCCESS;
}

R3 程序

三环下使用CreateFile来创建设备句柄

可以参考微软文档

HANDLE CreateFileA(
  [in]           LPCSTR                lpFileName,//对象名称,写驱动的符号文件
  [in]           DWORD                 dwDesiredAccess,//权限
  [in]           DWORD                 dwShareMode,//共享的模式
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,//NULL
  [in]           DWORD                 dwCreationDisposition,//执行的操作,驱动写OPEN_EXISTING
  [in]           DWORD                 dwFlagsAndAttributes,//属性标志,驱动写FILE_ATTRIBUTE_NORMAL
  [in, optional] HANDLE                hTemplateFile//NULL
);

先定义通讯码符号链接名,注意要和驱动里面定义的一致

//定义通讯码,注意要和驱动里面定义的一致
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )
//定义符号链接名称,三环下不写??,三环下是下面的写法,这个要和去当里面定义的一致
#define SYM_NAME L"\\\\.\\mysym"

注意看好符号链接名字!千万不要写错了,尤其是点和斜杠,注意\\之间只有一个点

然后创建设备句柄

HANDLE hDevice = CreateFile(SYM_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

使用通信函数DeviceIoControl进行通信

函数原型

BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,//上面创建的设备对象
  [in]                DWORD        dwIoControlCode,//通讯码
  [in, optional]      LPVOID       lpInBuffer,//输出参数
  [in]                DWORD        nInBufferSize,//输入参数的大小
  [out, optional]     LPVOID       lpOutBuffer,//输出参数
  [in]                DWORD        nOutBufferSize,//输出参数的大小
  [out, optional]     LPDWORD      lpBytesReturned,//执行后的返回码
  [in, out, optional] LPOVERLAPPED lpOverlapped//NULL
);

调用:

int x = 100;//要穿进去的参数
DWORD p = 0;//定义返回码
BOOL b = DeviceIoControl(hDevice, TEST, &x, 4, NULL, NULL, &p, NULL);

完整代码

#include <windows.h>
#include <stdio.h>
#include <winioctl.h>//必须引用,否则CTL_CODE找不到

//定义通讯码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )
//定义符号链接名称,三环下不写??,三环下是下面的写法
#define SYM_NAME "\\\\.\\mysym"

int main(int argc, char** argv)
{
	//创建设备句柄
	HANDLE hDevice = CreateFileA(SYM_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	int x = 100;//要穿进去的参数
	DWORD p = 0;//定义返回码
	//通信
	BOOL b = DeviceIoControl(hDevice, TEST, &x, sizeof(x), NULL, NULL, &p, NULL);
	//通信完成后记得关闭句柄
	CloseHandle(hDevice);
	printf("%d\r\n", b);

	system("pause");
	return 0;
}

分析

先加载驱动,在运行R3程序,驱动就断在了我们自己制定的派发函数那

这时候看一下设备对象

kd> dt DeviceObject
Local var @ 0xb6a1bae4 Type _DEVICE_OBJECT*
0x86efeca0 
   +0x000 Type             : 0n3
   +0x002 Size             : 0xb8
   +0x004 ReferenceCount   : 0n1
   +0x008 DriverObject     : 0x86d01030 _DRIVER_OBJECT
   +0x00c NextDevice       : (null) 
   +0x010 AttachedDevice   : (null) 
   +0x014 CurrentIrp       : (null) 
   +0x018 Timer            : (null) 
   +0x01c Flags            : 0x4c
   +0x020 Characteristics  : 0x100
   +0x024 Vpb              : (null) 
   +0x028 DeviceExtension  : (null) 
   +0x02c DeviceType       : 0x22
   +0x030 StackSize        : 1 ''
   +0x034 Queue            : <anonymous-tag>
   +0x05c AlignmentRequirement : 0
   +0x060 DeviceQueue      : _KDEVICE_QUEUE
   +0x074 Dpc              : _KDPC
   +0x094 ActiveThreadCount : 0
   +0x098 SecurityDescriptor : 0x8e868b68 Void
   +0x09c DeviceLock       : _KEVENT
   +0x0ac SectorSize       : 0
   +0x0ae Spare1           : 0
   +0x0b0 DeviceObjectExtension : 0x86efed58 _DEVOBJ_EXTENSION
   +0x0b4 Reserved         : (null) 

我们看一下设备对象中的驱动对象

   +0x008 DriverObject     : 0x86d01030 _DRIVER_OBJECT
kd> dt 0x86d01030 _DRIVER_OBJECT
nt!_DRIVER_OBJECT
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : 0x86efeca0 _DEVICE_OBJECT
   +0x008 Flags            : 0x12
   +0x00c DriverStart      : 0xb42be000 Void
   +0x010 DriverSize       : 0x6000
   +0x014 DriverSection    : 0x93a649a8 Void
   +0x018 DriverExtension  : 0x86d010d8 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\Driver\MyDriver12"
   +0x024 HardwareDatabase : 0x843c0270 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
   +0x028 FastIoDispatch   : (null) 
   +0x02c DriverInit       : 0xb42c2000     long  MyDriver12_b42be000!GsDriverEntry+0
   +0x030 DriverStartIo    : (null) 
   +0x034 DriverUnload     : 0xb42bf240     void  MyDriver12_b42be000!Unload+0
   +0x038 MajorFunction    : [28] 0xb42bf000     long  MyDriver12_b42be000!DefDispatch+0

可以看到设备指向的是我们的驱动对象

可以证明驱动对象和设备对象是互相引用的

我们再看一下IRP包

kd> dt IRP
Local var @ 0xb6a1bae8 Type _IRP*
0x8894eae8 
   +0x000 Type             : 0n6
   +0x002 Size             : 0x94
   +0x004 MdlAddress       : (null) 
   +0x008 Flags            : 0x60030
   +0x00c AssociatedIrp    : <anonymous-tag>
   +0x010 ThreadListEntry  : _LIST_ENTRY [ 0x86c3227c - 0x86c3227c ]
   +0x018 IoStatus         : _IO_STATUS_BLOCK
   +0x020 RequestorMode    : 1 ''
   +0x021 PendingReturned  : 0 ''
   +0x022 StackCount       : 1 ''
   +0x023 CurrentLocation  : 1 ''
   +0x024 Cancel           : 0 ''
   +0x025 CancelIrql       : 0 ''
   +0x026 ApcEnvironment   : 0 ''
   +0x027 AllocationFlags  : 0x6 ''
   +0x028 UserIosb         : 0x0015fd84 _IO_STATUS_BLOCK
   +0x02c UserEvent        : (null) 
   +0x030 Overlay          : <anonymous-tag>
   +0x038 CancelRoutine    : (null) 
   +0x03c UserBuffer       : (null) 
   +0x040 Tail             : <anonymous-tag>

这里展开一下

kd> dt IRP -r1
Local var @ 0xb6a1bae8 Type _IRP*
0x8894eae8 
   +0x000 Type             : 0n6
   +0x002 Size             : 0x94
   +0x004 MdlAddress       : (null) 
   +0x008 Flags            : 0x60030
   +0x00c AssociatedIrp    : <anonymous-tag>
      +0x000 MasterIrp        : 0x86eea040 _IRP
      +0x000 IrpCount         : 0n-2031181760
      +0x000 SystemBuffer     : 0x86eea040 Void
   +0x010 ThreadListEntry  : _LIST_ENTRY [ 0x86c3227c - 0x86c3227c ]
      +0x000 Flink            : 0x86c3227c _LIST_ENTRY [ 0x8894eaf8 - 0x8894eaf8 ]
      +0x004 Blink            : 0x86c3227c _LIST_ENTRY [ 0x8894eaf8 - 0x8894eaf8 ]
   +0x018 IoStatus         : _IO_STATUS_BLOCK
      +0x000 Status           : 0n0
      +0x000 Pointer          : (null) 
      +0x004 Information      : 0
   +0x020 RequestorMode    : 1 ''
   +0x021 PendingReturned  : 0 ''
   +0x022 StackCount       : 1 ''
   +0x023 CurrentLocation  : 1 ''
   +0x024 Cancel           : 0 ''
   +0x025 CancelIrql       : 0 ''
   +0x026 ApcEnvironment   : 0 ''
   +0x027 AllocationFlags  : 0x6 ''
   +0x028 UserIosb         : 0x0015fd84 _IO_STATUS_BLOCK
      +0x000 Status           : 0n7016208
      +0x000 Pointer          : 0x006b0f10 Void
      +0x004 Information      : 0x1c
   +0x02c UserEvent        : (null) 
   +0x030 Overlay          : <anonymous-tag>
      +0x000 AsynchronousParameters : <anonymous-tag>
      +0x000 AllocationSize   : _LARGE_INTEGER 0x0
   +0x038 CancelRoutine    : (null) 
   +0x03c UserBuffer       : (null) 
   +0x040 Tail             : <anonymous-tag>
      +0x000 Overlay          : <anonymous-tag>
      +0x000 Apc              : _KAPC
      +0x000 CompletionKey    : (null) 

可以看到这里面有一个SystemBuffer

+0x00c AssociatedIrp    : <anonymous-tag>
      +0x000 MasterIrp        : 0x86eea040 _IRP
      +0x000 IrpCount         : 0n-2031181760
      +0x000 SystemBuffer     : 0x86eea040 Void

我们跟进去看一下

可以看到SystemBuffer中就保存了我们R3传进去的值100

尝试R0向R3发送数据

R3:

int y = 0;//返回的值
//驱动控制这里增加返回输出参数和大小
BOOL b = DeviceIoControl(hDevice, TEST, &x, sizeof(x), &y, sizeof(y), &p, NULL);
//打印出y的值
printf("%d",y);

R0:

先在这个位置添加如下代码

//获取R3传进来的需要写的参数
int OutputBufferLength = ioStack->Parameters.DeviceIoControl.OutputBufferLength;

然后触发控制码后执行的代码这里添加写SystemBuffer的操作

//R0->R3
int y = 500;
//将y的值写入`SystemBuffer`
memcpy(Irp->AssociatedIrp.SystemBuffer, &y, sizeof(y));
//相当于告诉IRP包我返回的数据长度
Irp->IoStatus.Information = OutputBufferLength;

这里注意一下R3->R0与R0->R3使用的是同一块缓冲区

这里我们单步看一下

可以看到在执行R0->R3之前缓冲区中存放的是R3->R0的数据

到这里就可以看到缓冲区已经变了,已经变成我们R0->R3传的500

也可以换一种调法

我们可以一开始就在这个地方下硬件写入断点

然后运行起来就可以看到又断下了

看一下栈

就可以看到我们的写入操作了

证明就是这个MEMCPY的写入操作

0环和3环双向通信完整代码

R3

#include <windows.h>
#include <stdio.h>
#include <winioctl.h>//必须引用,否则CTL_CODE找不到

//定义通讯码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )
//定义符号链接名称,三环下不写??,三环下是下面的写法
#define SYM_NAME "\\\\.\\mysym"

int main(int argc, char** argv)
{
	//创建设备句柄
	HANDLE hDevice = CreateFileA(SYM_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	int x = 100;//要穿进去的参数
	int y = 0;//返回的值
	DWORD p = 0;//定义返回码
	//通信
	BOOL b = DeviceIoControl(hDevice, TEST, &x, sizeof(x), &y, 4, &p, NULL);
	//通信完成后记得关闭句柄
	CloseHandle(hDevice);
	printf("%d\r\n", b);
	//打印出R0->R3传进来的数据
	printf("%d\r\n", y);

	system("pause");
	return 0;
}

R0

#include <ntifs.h>

#define DEVICE_NAME L"\\Device\\mydevice"//设备名称
#define SYM_NAME L"\\??\\mysym"//符号链接名称
//定义通讯码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )


NTSTATUS DefDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	//定义默认的派发函数
	//走个形式而已
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

NTSTATUS Dispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	//定义我们真正的派发函数
	PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);//拿到当前发送IRP包的栈
	DbgBreakPoint();
	//只有IRP包中的功能号是我们指定的派发函数的功能号才执行下面的操作
	if (ioStack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
	{
		//获取IRP包中的栈的信息的DeviceIoControl信息,进而获取从R3带进来的参数的长度
		int size = ioStack->Parameters.DeviceIoControl.InputBufferLength;

		//获取我需要返回给R3的数据长度
		int OutputBufferLength = ioStack->Parameters.DeviceIoControl.OutputBufferLength;

		//拿到R3的异步控制码,这个是R3传进来自定义,
		ULONG IoControlCode = ioStack->Parameters.DeviceIoControl.IoControlCode;
		//比如R3传进来异步控制码是1执行什么,传进来2执行什么
		switch (IoControlCode)
		{
		case TEST:
		{
			//栈中只有我们的控制码等输入输出的大小
			//我们R3通过IRP包传进来的参数在这里
			//这里假设我们传进来一个整数
			//R3->R0
			int* x = (int*)Irp->AssociatedIrp.SystemBuffer;
			DbgPrint("num: %x", *x);

			//R0->R3
			int y = 500;
			//将y的值写入`SystemBuffer`
			memcpy(Irp->AssociatedIrp.SystemBuffer, &y, sizeof(y));
			//相当于告诉IRP包我返回的数据长度
			Irp->IoStatus.Information = OutputBufferLength;
			break;
		}
		case 2:
			break;

		}
	}
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

VOID Unload(PDRIVER_OBJECT pDriver)
{
	//在这里又初始化了一遍符号链接,为了防止在接下来的去掉符号链接的时候符号链接没有初始化导致蓝屏
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	//去掉符号链接
	IoDeleteSymbolicLink(&uSymName);
	//去掉设备
	IoDeleteDevice(pDriver->DeviceObject);
	DbgPrint("unload\r\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();

	//创建设备对象
	UNICODE_STRING uName = { 0 };
	RtlInitUnicodeString(&uName, DEVICE_NAME);//初始化设备名称
	PDEVICE_OBJECT pDevice = NULL;
	NTSTATUS status = IoCreateDevice(pDriver, 0, &uName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((77, 0, "创建设备对象失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建设备对象成功\r\n");

	//创建符号链接
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	status = IoCreateSymbolicLink(&uSymName, &uName);//将符号名和设备名绑定
	if (!NT_SUCCESS(status))
	{
		IoDeleteDevice(pDevice);//删除设备对象
		KdPrintEx((77, 0, "创建符号链接失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建符号链接成功\r\n");
	pDevice->Flags &= ~DO_DEVICE_INITIALIZING;//设备对象初始化信息去掉
	pDevice->Flags |= DO_BUFFERED_IO;//设置通信方式为IO通信

	pDriver->MajorFunction[IRP_MJ_CREATE] = DefDispatch; //填充打开
	pDriver->MajorFunction[IRP_MJ_CLOSE] = DefDispatch; //填充关闭
	pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Dispatch; //填充通信
	return STATUS_SUCCESS;
}

隐蔽通信

这个隐蔽通讯的原理本质上是在R0使用MajorFunction[IRP_MJ_READ]派发函数,在R3调用ReadFile函数,来达成对同一片内存区域的读取和写入

R0

重新定义了一个派发函数MajorFunction[IRP_MJ_READ]

pDriver->MajorFunction[IRP_MJ_CREATE] = DefDispatch; //填充打开
pDriver->MajorFunction[IRP_MJ_CLOSE] = DefDispatch; //填充关闭
//pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = Dispatch; //填充通信
//隐蔽通信
pDriver->MajorFunction[IRP_MJ_READ] = WriteDispatch;

接下来是WriteDispatch

NTSTATUS WriteDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);//拿到当前发送IRP包的栈
	//偏移
	LARGE_INTEGER ByteOffset = ioStack->Parameters.Read.ByteOffset;
	//数据长度
	int Length = ioStack->Parameters.Read.Length;
	//获取R3传进来的数据
	int* x = Irp->AssociatedIrp.SystemBuffer;
    //通过修改内存的方式将数据修改成100
    *x = 100;
	Irp->IoStatus.Information = Length;
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

完整代码

#include <ntifs.h>

#define DEVICE_NAME L"\\Device\\mydevice"//设备名称
#define SYM_NAME L"\\??\\mysym"//符号链接名称
//定义通讯码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )


NTSTATUS DefDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	//定义默认的派发函数
	//走个形式而已
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

NTSTATUS ReadDispatch(_In_ struct _DEVICE_OBJECT* DeviceObject, _Inout_ struct _IRP* Irp)
{
	DbgBreakPoint();
    //拿到当前发送IRP包的栈
	PIO_STACK_LOCATION ioStack = IoGetCurrentIrpStackLocation(Irp);
	//偏移
	LARGE_INTEGER ByteOffset = ioStack->Parameters.Read.ByteOffset;
	//数据长度
	int Length = ioStack->Parameters.Read.Length;
	//获取R3传进来的数据
	int* x = Irp->AssociatedIrp.SystemBuffer;
	//通过修改内存的方式将数据修改成100
	*x = 100;
	Irp->IoStatus.Information = Length;
	Irp->IoStatus.Status = STATUS_SUCCESS;//设置通信状态为成功
	IoCompleteRequest(Irp, 0);//告诉客户端已经接收到通讯,返回0
	return STATUS_SUCCESS;
}

VOID Unload(PDRIVER_OBJECT pDriver)
{
	//在这里又初始化了一遍符号链接,为了防止在接下来的去掉符号链接的时候符号链接没有初始化导致蓝屏
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	//去掉符号链接
	IoDeleteSymbolicLink(&uSymName);
	//去掉设备
	IoDeleteDevice(pDriver->DeviceObject);
	DbgPrint("unload\r\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg)
{
	pDriver->DriverUnload = Unload;
	//DbgBreakPoint();

	//创建设备对象
	UNICODE_STRING uName = { 0 };
	RtlInitUnicodeString(&uName, DEVICE_NAME);//初始化设备名称
	PDEVICE_OBJECT pDevice = NULL;
	NTSTATUS status = IoCreateDevice(pDriver, 0, &uName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);
	if (!NT_SUCCESS(status))
	{
		KdPrintEx((77, 0, "创建设备对象失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建设备对象成功\r\n");

	//创建符号链接
	UNICODE_STRING uSymName = { 0 };
	RtlInitUnicodeString(&uSymName, SYM_NAME);//初始化符号连接名
	status = IoCreateSymbolicLink(&uSymName, &uName);//将符号名和设备名绑定
	if (!NT_SUCCESS(status))
	{
		IoDeleteDevice(pDevice);//删除设备对象
		KdPrintEx((77, 0, "创建符号链接失败[db]:status:%x\r\n", status));
		return status;
	}
	DbgPrint("创建符号链接成功\r\n");
	pDevice->Flags &= ~DO_DEVICE_INITIALIZING;//设备对象初始化信息去掉
	pDevice->Flags |= DO_BUFFERED_IO;//设置通信方式为IO通信

	pDriver->MajorFunction[IRP_MJ_CREATE] = DefDispatch; //填充打开
	pDriver->MajorFunction[IRP_MJ_CLOSE] = DefDispatch; //填充关闭
	//隐蔽通信
	pDriver->MajorFunction[IRP_MJ_READ] = ReadDispatch;
	return STATUS_SUCCESS;
}

R3

这回不使用DeviceIoControl进行通信了,改用ReadFile进行通讯

ReadFile函数原型

BOOL ReadFile(
  [in]                HANDLE       hFile,//传进来的设备对象
  [out]               LPVOID       lpBuffer,//等待传到R0的内存区域
  [in]                DWORD        nNumberOfBytesToRead,//等待传到R0的内存区域的大小
  [out, optional]     LPDWORD      lpNumberOfBytesRead,//返回值
  [in, out, optional] LPOVERLAPPED lpOverlapped//NULL
);

调用

int x = 100;//等待传到R0的内存区域
DWORD p = 0;//定义返回码
BOOL b = ReadFile(hDevice, &x, sizeof(x), &p, NULL);

完整代码:

#include <windows.h>
#include <stdio.h>
#include <winioctl.h>//必须引用,否则CTL_CODE找不到

//定义通讯码
#define TEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS )
//定义符号链接名称,三环下不写??,三环下是下面的写法
#define SYM_NAME "\\\\.\\mysym"

int main(int argc, char** argv)
{
	//创建设备句柄
	HANDLE hDevice = CreateFileA(SYM_NAME, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	int x = 500;//等待传到R0的内存区域
	DWORD p = 0;//定义返回码
	//Read通信
	ReadFile(hDevice, &x, sizeof(x), &p, NULL);
	//通信完成后记得关闭句柄
	CloseHandle(hDevice);
    //打印出在R0被直接通过内存修改改过的值
	printf("%d\r\n", x);
	system("pause");
	return 0;
}
posted @ 2024-05-18 17:20  MuRKuo  阅读(55)  评论(0编辑  收藏  举报