3环程序代码
#include <stdio.h>
#include <windows.h>
// 定义自定义消息
// 参数:设备类型,IOCTL码(自定义),读写方式,权限
#define BufferWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define DirectWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x802, METHOD_IN_DIRECT, FILE_ANY_ACCESS )
#define NeitherWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS )
int main()
{
// 可以使用 CreateFile 符号名去打开一个设备对象,要求管理员权限
HANDLE DeviceHandle = CreateFile(L"\\\\.\\chengsym", GENERIC_ALL,
NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
char buff[100] = { "1234" };
char buff1[100] = {"7890"};
DWORD bytes = 0;
// 向设备对象发送消息
// 句柄-通知码-输入缓冲区-输入缓冲区大小-输出缓冲区
// -输出缓冲区大小-实际传输的字节数-是否是OVERLAPPED
DeviceIoControl(DeviceHandle, BufferWay, buff, 100, buff1, 100, &bytes, NULL);
// 输出结果
printf("buff:%s\n", buff);
printf("buff1:%s\n", buff1);
printf("bytes:%d\n", bytes);
// 关闭句柄,会相应到 IRP 的 CLOSE 消息
CloseHandle(DeviceHandle);
system("pause");
return 0;
}
0环驱动代码
#include <ntddk.h>
// 设备类型,IOCTL码,读写方式,权限
#define BufferWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS )
#define DirectWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x802, METHOD_IN_DIRECT, FILE_ANY_ACCESS )
#define NeitherWay CTL_CODE( FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS )
// 创建设备对象,接收的参数是所属的驱动对象
NTSTATUS CreateDevice(PDRIVER_OBJECT DriverObject)
{
// 创建需要用到的一些变量
NTSTATUS nstatus = STATUS_SUCCESS;
PDEVICE_OBJECT DeviceObject = NULL;
// 初始化设备对象的名称,名称必须是 \\Device\\xxx 的形式
UNICODE_STRING DeviceName =RTL_CONSTANT_STRING(L"\\Device\\cheng");
nstatus = IoCreateDevice(
DriverObject, // 所属驱动对象,创建后会被添加到它的 DeviceObject 链表中
0, // 设备的扩展空间大小,分配的空间会被 DeviceExtension 指向
&DeviceName, // 设备对象的名称
FILE_DEVICE_UNKNOWN, // 一般说明它是一个和硬件无关的虚拟设备
0, // 设备的属性信息
TRUE, // 是否独占 \ R3是否可访问
&DeviceObject); // 创建出的设备对象被保存到的地方
if (!NT_SUCCESS(nstatus))
{
KdPrint(("设备对象创建失败"));
return nstatus;
}
// 设备对象的名称只能被内核程序解析,为了让R3应用识别
// 需要设置符号链接名: \\DosDevices\\xxx 或 \\??\\xxx
UNICODE_STRING SymLinkName = RTL_CONSTANT_STRING(L"\\??\\chengsym");
//创建符号与设备进行连接
nstatus = IoCreateSymbolicLink(&SymLinkName, &DeviceName);
if (!NT_SUCCESS(nstatus))
{
IoDeleteDevice(DeviceObject);
KdPrint(("符号连接失败"));
return nstatus;
}
// 创建成功后,可以通过设置 Flags 指定读写的方式,如果没有指定
// 读写方式,就会采用两者都不的形式,此时访问的直接是用户空间
return nstatus;
}
// 用于实现默认的消息派遣函数
NTSTATUS DefaultDispatch(
PDEVICE_OBJECT DeviceObject, // 表示当前的消息是那个设备对象产生的
PIRP Irp) // IRP,对应的是三环程序的消息,保存了一些附加参数
{
UNREFERENCED_PARAMETER(DeviceObject);
// 设置消息的处理状态: 成功或失败 -> GetLastError
Irp->IoStatus.Status = STATUS_SUCCESS;
// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
Irp->IoStatus.Information = 0;
// 通知操作已经完成,完成后不提高当前的 IRQL
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// 返回当前处理的整体结果是成功还是失败
return STATUS_SUCCESS;
}
// 用于实现自定义的消息派遣函数
NTSTATUS DeviceIoControlDispatch(
PDEVICE_OBJECT DeviceObject, // 表示当前的消息是那个设备对象产生的
PIRP Irp) // IRP,对应的是三环程序的消息,保存了一些附加参数
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp);
//读取内容
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
KdPrint(("Length: %d\n", IrpStack->Parameters.Read.Length));
// 可以通过 irp 栈获取到 DeviceIoControl 的通知码
switch (Stack->Parameters.DeviceIoControl.IoControlCode)
{
//使用BUFFER方式读取
case BufferWay:
DbgBreakPoint();
// 当用户层调用 ReadFile 函数的时候,用户层的写入的内容
// 会被拷贝到 Irp->AssociatedIrp.SystemBuffer 空间中
KdPrint(("Buffer: %s\n", Irp->AssociatedIrp.SystemBuffer));
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, "hello", 6);
// 设置实际读取到的字节数量
Irp->IoStatus.Information = 14;
// 设置消息的处理状态: 成功或失败 -> GetLastError
Irp->IoStatus.Status = STATUS_SUCCESS;
// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
Irp->IoStatus.Information = 4;
// 通知操作已经完成,完成后不提高当前的 IRQL
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// 返回当前处理的整体结果是成功还是失败
return STATUS_SUCCESS;
break;
//使用MDL方式读取
case DirectWay:
DbgBreakPoint();
// 当用户层调用 ReadFile 函数的时候,用户层传入的缓冲区
// 会被重新通过 MDL 进行关联,需要我们自己获取地址 获取缓冲区并输出
CHAR* Buffer = (CHAR*)MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
KdPrint(("Buffer: %s\n", Buffer));
// 设置实际读取到的字节数量
Irp->IoStatus.Information = strlen(Buffer) + 1;
// 设置消息的处理状态: 成功或失败 -> GetLastError
Irp->IoStatus.Status = STATUS_SUCCESS;
// 通知操作已经完成,完成后不提高当前的 IRQL
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// 返回当前处理的整体结果是成功还是失败
return STATUS_SUCCESS;
break;
// 使用两者都不得方式读取
case NeitherWay:
DbgBreakPoint();
__try {
RtlCopyMemory(Irp->UserBuffer, "hello", 6);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
KdPrint(("产生了异常,捕获了就不蓝屏,否则蓝屏"));
}
// 设置实际读取到的字节数量
Irp->IoStatus.Information = 14;
UNREFERENCED_PARAMETER(DeviceObject);
// 设置消息的处理状态: 成功或失败 -> GetLastError
Irp->IoStatus.Status = STATUS_SUCCESS;
// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
Irp->IoStatus.Information = 5;
// 通知操作已经完成,完成后不提高当前的 IRQL
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// 返回当前处理的整体结果是成功还是失败
return STATUS_SUCCESS;
break;
}
// 设置消息的处理状态: 成功或失败 -> GetLastError
Irp->IoStatus.Status = STATUS_SUCCESS;
// 设置消息处理的内容长度: ReadFile 或者 WriteFIle 的实际操作长度
Irp->IoStatus.Information = 0;
// 通知操作已经完成,完成后不提高当前的 IRQL
IoCompleteRequest(Irp, IO_NO_INCREMENT);
// 返回当前处理的整体结果是成功还是失败
return STATUS_SUCCESS;
}
// 驱动卸载的回调函数,会在卸载的时候被调用
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
// 删除设备对象的时候,必须先删符号名,再删设备对象
UNICODE_STRING SymLinkName;
RtlInitUnicodeString(&SymLinkName, L"\\??\\chengsym");
//删除符号
IoDeleteSymbolicLink(&SymLinkName);
// 可以通过驱动对象的链表找到设备对象,进行删除
IoDeleteDevice(DriverObject->DeviceObject);
KdPrint(("down...\n"));
UNREFERENCED_PARAMETER(DriverObject);
}
// 2. 提供驱动程序的入口函数
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject, // 驱动对象,类似实例句柄
PUNICODE_STRING RegistryPath) // 字符串指针,保存驱动在注册表的路径
{
// 参数可以通过下面的宏解除警告。
UNREFERENCED_PARAMETER(RegistryPath);
UNREFERENCED_PARAMETER(DriverObject);
// 通过 DbgBreakPoint 可以为驱动程序设置一个断点
//DbgBreakPoint();
//设置回调函数
DriverObject->DriverUnload = DriverUnload;
// 当驱动程序处于 Check 版本时,进行输出
KdPrint(("up...\n"));
// 创建设备对象
CreateDevice(DriverObject);
// 为所有的 IRP 消息设置默认的派遣函数
for (int i = 0; i < 28; ++i)
DriverObject->MajorFunction[i] = DefaultDispatch;
// 创建设备对象之后,可以通过驱动对象设置消息的派遣函数(消息响应)
// 消息是由设备对象产生的,但统一由驱动对象进行处理,对于下面的
// 几个消息,如果不提供,就不能完成相应的函数调用
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControlDispatch; // DeviceIoControl(自定义消息)
// 3. 必须需要提供一个返回值,如果返回成功
// 那么就会加载成功,其他值都是加载失败
return STATUS_SUCCESS;
}