验证环境/使用工具: visual studio 2019、Windows XP、winobj、Dbjview
验证目标: 让R3和R0能通过读写交换数据,也就是驱动和R3做交互过程(NT框架包含了这个交互)
内核API(极简进化史):
- 系统 => 抽象驱动 => 硬件
- 抽象驱动是什么?
1). 简明:驱动框架 => 读、写操作 => 硬件
2).详细(进一步思考:文件的抽象,操作文件):驱动框架 => 文件(打开、读取、写入、控制、查询信息、关闭) - 下发配置: 应用程序 => ReadFile => 系统 => 驱动::读取 => 厂家驱动 => 硬件
- 获取信息: 应用程序 <= ReadFile <= 系统 <= 驱动::读取 <= 厂家驱动 <= 硬件
糟糕,出师不利,出现了一点点点的问题: 整理代码之后,不知道为什么,vs2019中driverentry 的结构体死活打不开,要看里面的成员啊。
解决方案: 经过一番设置之后,还是不行,重新创建个项目试下,果然还得是重新创建项目,项目正常了。果然还得是老话:“重装系统能够解决99.9%的问题”
两步走计划:
- 注册派遣函数
- 驱动绑定到 "设备",来调用驱动
- 完成R3和R0的数据交互
0x01 注册派遣函数
sample.h
#pragma once
#include <ntddk.h>
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath);
NTSTATUS DispatchCreat(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchClose(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchRead(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchWrite(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchControl(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
/*
IRP_MJ_DEVICE_CONTROL: 读写可以控制摄像头的打开关闭,控制则可以控制其他的,如:摄像头的旋转...[除了硬件厂商,其他人可不知道怎么让摄像头旋转]
是用来写不通用的功能; 读写的参数需要传一个缓冲区和一个长度,而control的参数,微软不知道传什么参数
control就是要传一些只有硬件厂家才能解析的数据
显卡(NVIDIA)的驱动安装之后,R3上有界面,设置显卡的一些特性,会调用DispatchControl这个函数,这里可以做一些硬件独有的功能;
有一种软件可以拿到硬件的各种信息:有没有可能把control的参数逆清楚,模仿参数发送数据,这样就可以拿到硬件的各种信息了
前几天在闲鱼买了太Macmini主机,最担心的就是有人通过这种方式来修改主机里的信息,如:8+256的主机刷成32+2t的,买家需要反复擦亮眼睛奥;
*/
sample.cpp
#include "sample.h"
#include <ntddk.h>
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("51[asm] Unload");
}
/*
驱动对象: 微软提供一个结构体,这个结构体里保存所有的驱动信息
入口函数第一个参数 PDRIVER_OBJECT 驱动对象,保存了结构体里的所有信息;
*/
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject, /* PDRIVER_OBJECT 这个结构体保存了驱动的所有信息 */
PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status = STATUS_SUCCESS;
/* 注册派遣函数 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreat;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
DriverObject->DriverUnload = Unload;
DbgPrint("51[asm] hello word! DriverEntry:%p,Unload:%p,&status:%p", DriverEntry, Unload, &status);
return status;
}
NTSTATUS DispatchCreat(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
0x02 驱动绑定到 "设备"
2.1 绑定步骤
驱动绑定到 "设备",通过"设备"来调用驱动
注意:
1) 驱动什么时候被调用?被谁调用?
那肯定是被设备调用,驱动绑定到哪个设备上?
微软抽象了 "设备" 的概念,然后把驱动绑定到设备上,"设备"被操作了,驱动就被调用了。
例如:可以绑定到键盘上,键盘驱动,键盘被操作的时候,驱动中的派遣函数就被调用了
2) 如果没有设备,OS拒绝通信;
OS考虑到内核驱动的问题,允许创建一个虚拟设备(提供结构体 DEVICE_OBJECT, 用来描述设备信息)
内核驱动和硬件驱动区别:
硬件设备驱动绑定的是硬件设备对象;
内核驱动绑定的是虚拟设备;
3) 内核提供的api叫创建设备;
OS在内核中创建 I/O 管理器(R3对硬件操作都发给IO管理器) =》 I/O 管理器就 根据设备找驱动来调用
驱动的交互由 I/O 管理器 负责;也包括派遣函数,也是 I/O 管理器调用;
那么,创建一个设备,也需要 I/O 管理器中创建;
4) 创建步骤: 官方文档中描述的添加设备的流程,按照这个流程来操作
2.2 实验验证
sample.cpp
#include "sample.h"
#include <ntddk.h>
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoDeleteDevice OK!",__FUNCTION__,__LINE__);
}
DbgPrint("51[asm][%s:%d] Unload", __FUNCTION__, __LINE__);
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject, /* PDRIVER_OBJECT 这个结构体保存了驱动的所有信息 */
PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS Status = STATUS_SUCCESS;
/* 注册派遣函数 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreat;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
/* rtl: run time library C库(运行库) 微软重新做的C库,这个库更加安全; 这里做字符串使用的初始化 */
UNICODE_STRING userDevName;
RtlInitUnicodeString(&userDevName, DEVICE_NAME);
/* 绑定设备 */
PDEVICE_OBJECT pDeviceObj = NULL;
Status = IoCreateDevice(DriverObject, 0, &userDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA, FALSE, &pDeviceObj);
if (!NT_SUCCESS(Status)) /* 函数返回值统一是Status,这里安全性检查 */
{
DbgPrint("51[asm][%s:%d] IoCreateDevice err status:%p", __FUNCTION__, __LINE__, Status);
DriverObject->DriverUnload = Unload; /* 卸载函数中一定要删除设备,否则卸载后无法再次启动,提示设备名已存在 */
return Status;
}
/*
PDEVICE_OBJECT pDeviceObj = NULL; // 结构体里的 PVOID DeviceExtension; 可以申请空间大小
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, // 驱动对象
IN ULONG DeviceExtensionSize, // 设备扩展: 微软定义了一个结构体 PDEVICE_OBJECT,如果硬件希望在结构体中增加一些成员,这里需要写成员的大小,OS会申请一段内存空间,那就就可以无限的给这个结构体增加成员;这里是额外申请一些空间
IN PUNICODE_STRING DeviceName OPTIONAL, // 给设备名字是必须唯一的,注册的名字和其他的冲突了,会创建失败;可以给个NULL,微软会随机一个名字; 文档中说明设备名必须是有格式的: \Device\DeviceName
// 注意: PUNICODE_STRING 这里的字符串Unicode字符串,字符串需要经过处理
IN DEVICE_TYPE DeviceType, // 选择未知设备就行,但是一定得给,否则无法通讯 FILE_DEVICE_UNKNOWN
IN ULONG DeviceCharacteristics, // 权限
IN BOOLEAN Exclusive, // 关键参数,是否独占的方式, 独占: 只允许一个人打开这个设备 (杀毒软件会独占)
OUT PDEVICE_OBJECT * DeviceObject
);
*/
DriverObject->DriverUnload = Unload;
DbgPrint("51[asm][%s:%d] hello word! DriverEntry:%p,Unload:%p,&status:%p,pDeviceObj:%p", __FUNCTION__, __LINE__, DriverEntry, Unload, &Status,&pDeviceObj);
return Status;
}
NTSTATUS DispatchCreat(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
return STATUS_SUCCESS;
}
2.3 结果分析
2.3.1 安装和卸载打印没问题
2.3.2 在winobj 中可以查看自己写的"设备"
2.3.3 卸载能够卸载干净(卸载后,发现找不到这个驱动,卸载完成)
2.4 注意
需要做另外一个事情:
回调函数来了不能直接返回真,比如:R3向键盘驱动要按键,但是按键没有按下,怎么给?
但是R3还在等,这时的OS的IO管理器会把R3的线程挂起,但是就感受到软件卡死,调一个api之后回不来了;
注意:这里返回success,代表操作是成功的,不代表操作完成了(类似数据读写完成)
这里必须告诉IO管理器,请求是否结束;
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/*
1. 这个函数表示 请求是否结束
2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数封装到这个结构体了
3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提;
4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束
5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理;
*/
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
0x03 创建R3进行通信
3.1 实验验证
3.1.1 创建一个控制台项目,R3让内核做的事情,发给驱动,让驱动来做,这样就形成了一个内核程序,继续和驱动通信,直接操作这个"设备"
3.1.2
Ring3.cpp
#include <stdio.h>
#include <windows.h>
int main()
{
// 打开设备, 这里文件的路径就是在驱动中创建的设备名字
HANDLE hFile = CreateFile("\\Device\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf("hFile:%p err:%p\n", hFile, GetLastError());
// 读写设备
// 控制设备
// 关闭设备
system("pause");
return 0;
}
3.1.3
注意: 把这个运行设置为静态库,因为虚拟机里没有静态库;
3.1.4
注意:用xp的版本,设置一下版本兼容
3.2 结果分析
3.2.1
验证程序是否正常执行,文件打开失败,产生3号错误…;
3.2.2
找不到指定的路径,是路径的问题嘛? 第一个参数是使用的驱动里的名字,并不是R3的名字,大逆不道,没有权限
3.3 符号链接
3.3.1
分析问题
驱动不存在“路径”的说法,为什么打不开? 没有权限
硬件的驱动设备决定着是否给3环提供功能,而现在驱动程序只允许OS操作,R3是不允许操作的,R3没权限啊;
那么,如果让驱动对R3公开,必须为R3创造一个名字
3.3.2
举例: 计算机中的"C:"盘
操作这个 "C:" 就相当于操作了硬盘,称为 符号链接
OS为硬盘创造的名字是固定的, 这个是硬盘在驱动中的名字,只有内核才能操作这个名字;
3.3.3
让R3操作这个设备,必须得有个盘符;把盘符映射到这个驱动设备上,这个就是链接 =》 符号链接
“C:" 是符号,操作的过程叫链接,所以是符号链接;
3.4 实验验证,更新驱动和R3,产生符号链接
3.4.1 =》 R3
Ring3.cpp
#include <stdio.h>
#include <windows.h>
int main()
{
// 打开设备, 这里文件的路径就是在驱动中创建的设备名字
HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf("hFile:%p err:%p\n", hFile, GetLastError());
// 读写设备
// 控制设备
// 关闭设备
system("pause");
return 0;
}
3.4.2 驱动
sample.cpp
#include "sample.h"
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoDeleteDevice OK!",__FUNCTION__,__LINE__);
}
DbgPrint("51[asm][%s:%d] Unload", __FUNCTION__, __LINE__);
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject, /* PDRIVER_OBJECT 这个结构体保存了驱动的所有信息 */
PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS Status = STATUS_SUCCESS;
/* 注册派遣函数 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreat;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
/* rtl: run time library C库(运行库) 微软重新做的C库,这个库更加安全; 这里做字符串使用的初始化 */
UNICODE_STRING userDevName;
RtlInitUnicodeString(&userDevName, DEVICE_NAME);
/* 绑定设备 */
PDEVICE_OBJECT pDeviceObj = NULL;
Status = IoCreateDevice(DriverObject, 0, &userDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA, FALSE, &pDeviceObj);
if (!NT_SUCCESS(Status)) /* 函数返回值统一是Status,这里安全性检查 */
{
DbgPrint("51[asm][%s:%d] IoCreateDevice err status:%p", __FUNCTION__, __LINE__, Status);
DriverObject->DriverUnload = Unload; /* 卸载函数中一定要删除设备,否则卸载后无法再次启动,提示设备名已存在 */
return Status;
}
/* IoCreateDevice 参数填写注意事项:
PDEVICE_OBJECT pDeviceObj = NULL; // 结构体里的 PVOID DeviceExtension; 可以申请空间大小
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, // 驱动对象
IN ULONG DeviceExtensionSize, // 设备扩展: 微软定义了一个结构体 PDEVICE_OBJECT,如果硬件希望在结构体中增加一些成员,这里需要写成员的大小,OS会申请一段内存空间,那就就可以无限的给这个结构体增加成员;这里是额外申请一些空间
IN PUNICODE_STRING DeviceName OPTIONAL, // 给设备名字是必须唯一的,注册的名字和其他的冲突了,会创建失败;可以给个NULL,微软会随机一个名字; 文档中说明设备名必须是有格式的: \Device\DeviceName
// 注意: PUNICODE_STRING 这里的字符串Unicode字符串,字符串需要经过处理
IN DEVICE_TYPE DeviceType, // 选择为止设备就行,但是一定得给,否则无法通讯 FILE_DEVICE_UNKNOWN
IN ULONG DeviceCharacteristics, // 权限
IN BOOLEAN Exclusive, // 关键参数,是否独占的方式, 独占: 只允许一个人打开这个设备 (杀毒软件会独占)
OUT PDEVICE_OBJECT * DeviceObject
);
*/
/* 创建符号链接 */
UNICODE_STRING strDosDeviceName;
RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME);
Status = IoCreateSymbolicLink(&strDosDeviceName, &userDevName); /* R3使用的名字在文档中选:MS-DOS Device Names R0使用的名字在文档中选:NT Device Names */
if (!NT_SUCCESS(Status))
{
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
IoDeleteDevice(DriverObject->DeviceObject);
IoDeleteSymbolicLink(&strDosDeviceName);
DbgPrint("51[asm][%s:%d] IoCreateSymbolicLink err!", __FUNCTION__, __LINE__);
}
return Status;
}
DriverObject->DriverUnload = Unload;
DbgPrint("51[asm][%s:%d] hello word! DriverEntry:%p,Unload:%p,&status:%p,pDeviceObj:%p", __FUNCTION__, __LINE__, DriverEntry, Unload, &Status,&pDeviceObj);
return Status;
}
NTSTATUS DispatchCreat(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/*
1. 这个函数表示 请求是否结束
2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了
3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提;
4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束
5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理;
*/
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
3.5 结果分析
出问题了:
第一次安装驱动成功,操作R3成功调用了回调函数
卸载驱动,这个卸载的函数没有调用,卸载打印输出内容不对; 第二次安装驱动失败!!!
猜想:可能是因为符号没有删除,符号名字重复了;
3.5.1 解决问题
解决驱动卸载后无法安装的问题: 在Unload中删除符号链接
sample.cpp
#include "sample.h"
char g_zMsg[] = "hello"; // 代表OS的任何数据
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
/* 删除符号链接 */
UNICODE_STRING strDosDeviceName;
RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME);
IoDeleteSymbolicLink(&strDosDeviceName);
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoDeleteDevice OK!",__FUNCTION__,__LINE__);
}
DbgPrint("51[asm][%s:%d] Unload", __FUNCTION__, __LINE__);
}
3.6 通过R3来调用内核的read函数
Ring3.cpp
#include <stdio.h>
#include <windows.h>
int main()
{
// 打开设备, 这里文件的路径就是在驱动中创建的设备名字
HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf("hFile:%p err:%p\n", hFile, GetLastError());
// 读写设备
char acBuf[60] = { 0 };
DWORD dwBytes = 0;
BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL);
printf("bRet:%d dwBytes:%d acBuf:%s", bRet, dwBytes, acBuf);
// 控制设备
// 关闭设备
system("pause");
return 0;
}
3.7 结果分析
调到了内核驱动里的read了
注意: 返回值成功,但是返回数据是0,因为请求完成了,内核没有向R3拷贝数据
0x04 R3和R0交互
- 驱动中DispatchRead函数里有R3的地址,对比输出验证;
- 从驱动中拷贝数据给R3;
4.1 实验验证
Ring3.cpp
#include <stdio.h>
#include <windows.h>
int main()
{
// 打开设备, 这里文件的路径就是在驱动中创建的设备名字
HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf("hFile:%p err:%p\n", hFile, GetLastError());
// 读写设备
char acBuf[60] = { 0 };
printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */
DWORD dwBytes = 0;
BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL);
printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf);
// 控制设备
// 关闭设备
system("pause");
return 0;
}
sample.cpp
#include "sample.h"
char g_zMsg[] = "hello"; // 代表OS的任何数据
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
/* 删除符号链接 */
UNICODE_STRING strDosDeviceName;
RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME);
IoDeleteSymbolicLink(&strDosDeviceName);
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoDeleteDevice OK!",__FUNCTION__,__LINE__);
}
DbgPrint("51[asm][%s:%d] Unload", __FUNCTION__, __LINE__);
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject, /* PDRIVER_OBJECT 这个结构体保存了驱动的所有信息 */
PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS Status = STATUS_SUCCESS;
/* 注册派遣函数 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreat;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
/* rtl: run time library C库(运行库) 微软重新做的C库,这个库更加安全; 这里做字符串使用的初始化 */
UNICODE_STRING userDevName;
RtlInitUnicodeString(&userDevName, DEVICE_NAME);
/* 绑定设备 */
PDEVICE_OBJECT pDeviceObj = NULL;
Status = IoCreateDevice(DriverObject, 0, &userDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA, FALSE, &pDeviceObj);
if (!NT_SUCCESS(Status)) /* 函数返回值统一是Status,这里安全性检查 */
{
DbgPrint("51[asm][%s:%d] IoCreateDevice err status:%p", __FUNCTION__, __LINE__, Status);
DriverObject->DriverUnload = Unload; /* 卸载函数中一定要删除设备,否则卸载后无法再次启动,提示设备名已存在 */
return Status;
}
/* IoCreateDevice 参数填写注意事项:
PDEVICE_OBJECT pDeviceObj = NULL; // 结构体里的 PVOID DeviceExtension; 可以申请空间大小
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, // 驱动对象
IN ULONG DeviceExtensionSize, // 设备扩展: 微软定义了一个结构体 PDEVICE_OBJECT,如果硬件希望在结构体中增加一些成员,这里需要写成员的大小,OS会申请一段内存空间,那就就可以无限的给这个结构体增加成员;这里是额外申请一些空间
IN PUNICODE_STRING DeviceName OPTIONAL, // 给设备名字是必须唯一的,注册的名字和其他的冲突了,会创建失败;可以给个NULL,微软会随机一个名字; 文档中说明设备名必须是有格式的: \Device\DeviceName
// 注意: PUNICODE_STRING 这里的字符串Unicode字符串,字符串需要经过处理
IN DEVICE_TYPE DeviceType, // 选择为止设备就行,但是一定得给,否则无法通讯 FILE_DEVICE_UNKNOWN
IN ULONG DeviceCharacteristics, // 权限
IN BOOLEAN Exclusive, // 关键参数,是否独占的方式, 独占: 只允许一个人打开这个设备 (杀毒软件会独占)
OUT PDEVICE_OBJECT * DeviceObject
);
*/
/* 创建符号链接 */
UNICODE_STRING strDosDeviceName;
RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME);
Status = IoCreateSymbolicLink(&strDosDeviceName, &userDevName); /* R3使用的名字在文档中选:MS-DOS Device Names R0使用的名字在文档中选:NT Device Names */
if (!NT_SUCCESS(Status))
{
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
/* 删除符号链接 */
IoDeleteSymbolicLink(&strDosDeviceName);
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoCreateSymbolicLink err!", __FUNCTION__, __LINE__);
}
return Status;
}
DriverObject->DriverUnload = Unload;
DbgPrint("51[asm][%s:%d] hello word! DriverEntry:%p,Unload:%p,&status:%p,pDeviceObj:%p", __FUNCTION__, __LINE__, DriverEntry, Unload, &Status,&pDeviceObj);
return Status;
}
NTSTATUS DispatchCreat(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/* 验证这里是R3的缓存地址,输出一下 */
DbgPrint("51[asm][%s:%d] Irp->UserBuffer:%p", __FUNCTION__, __LINE__, Irp->UserBuffer);
RtlCopyMemory(Irp->UserBuffer,g_zMsg,sizeof(g_zMsg));
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/*
1. 这个函数表示 请求是否结束
2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了
3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提;
4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束
5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理;
*/
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
4.2 结果分析
结果读取到了数据,但是读取的字节数位0 ???
4.3 读取R0的数据
sample.cpp
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/* 验证这里是R3的缓存地址,输出一下 */
DbgPrint("51[asm][%s:%d] Irp->UserBuffer:%p", __FUNCTION__, __LINE__, Irp->UserBuffer);
RtlCopyMemory(Irp->UserBuffer,g_zMsg,sizeof(g_zMsg));
Irp->IoStatus.Status = STATUS_SUCCESS; /* 告诉R3返回的状态 */
Irp->IoStatus.Information = sizeof(g_zMsg); /* 告诉R3读取的字节数 */
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
4.4 结果
读取到6个字节的数据
0x04 完整代码记录:
R3:
Ring3.cpp
#include <stdio.h>
#include <windows.h>
int main()
{
// 打开设备, 这里文件的路径就是在驱动中创建的设备名字
HANDLE hFile = CreateFile("\\\\.\\\\51asmMark", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
printf("hFile:%p err:%p\n", hFile, GetLastError());
// 读写设备
char acBuf[60] = { 0 };
printf("acBuf:%p\n", acBuf); /* 驱动要输出R3的缓存地址,这里作为对照 */
DWORD dwBytes = 0;
BOOL bRet = ReadFile(hFile, acBuf, sizeof(acBuf), &dwBytes, NULL);
printf("bRet:%d dwBytes:%d acBuf:%s\n", bRet, dwBytes, acBuf);
// 控制设备
// 关闭设备
system("pause");
return 0;
}
驱动:
sample.cpp
#include "sample.h"
char g_zMsg[] = "hello"; // 代表OS的任何数据
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
/* 删除符号链接 */
UNICODE_STRING strDosDeviceName;
RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME);
IoDeleteSymbolicLink(&strDosDeviceName);
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoDeleteDevice OK!",__FUNCTION__,__LINE__);
}
DbgPrint("51[asm][%s:%d] Unload", __FUNCTION__, __LINE__);
}
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject, /* PDRIVER_OBJECT 这个结构体保存了驱动的所有信息 */
PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS Status = STATUS_SUCCESS;
/* 注册派遣函数 */
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreat;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
/* rtl: run time library C库(运行库) 微软重新做的C库,这个库更加安全; 这里做字符串使用的初始化 */
UNICODE_STRING userDevName;
RtlInitUnicodeString(&userDevName, DEVICE_NAME);
/* 绑定设备 */
PDEVICE_OBJECT pDeviceObj = NULL;
Status = IoCreateDevice(DriverObject, 0, &userDevName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN | FILE_READ_ONLY_DEVICE | FILE_WRITE_ONCE_MEDIA, FALSE, &pDeviceObj);
if (!NT_SUCCESS(Status)) /* 函数返回值统一是Status,这里安全性检查 */
{
DbgPrint("51[asm][%s:%d] IoCreateDevice err status:%p", __FUNCTION__, __LINE__, Status);
DriverObject->DriverUnload = Unload; /* 卸载函数中一定要删除设备,否则卸载后无法再次启动,提示设备名已存在 */
return Status;
}
/* IoCreateDevice 参数填写注意事项:
PDEVICE_OBJECT pDeviceObj = NULL; // 结构体里的 PVOID DeviceExtension; 可以申请空间大小
IoCreateDevice(
IN PDRIVER_OBJECT DriverObject, // 驱动对象
IN ULONG DeviceExtensionSize, // 设备扩展: 微软定义了一个结构体 PDEVICE_OBJECT,如果硬件希望在结构体中增加一些成员,这里需要写成员的大小,OS会申请一段内存空间,那就就可以无限的给这个结构体增加成员;这里是额外申请一些空间
IN PUNICODE_STRING DeviceName OPTIONAL, // 给设备名字是必须唯一的,注册的名字和其他的冲突了,会创建失败;可以给个NULL,微软会随机一个名字; 文档中说明设备名必须是有格式的: \Device\DeviceName
// 注意: PUNICODE_STRING 这里的字符串Unicode字符串,字符串需要经过处理
IN DEVICE_TYPE DeviceType, // 选择为止设备就行,但是一定得给,否则无法通讯 FILE_DEVICE_UNKNOWN
IN ULONG DeviceCharacteristics, // 权限
IN BOOLEAN Exclusive, // 关键参数,是否独占的方式, 独占: 只允许一个人打开这个设备 (杀毒软件会独占)
OUT PDEVICE_OBJECT * DeviceObject
);
*/
/* 创建符号链接 */
UNICODE_STRING strDosDeviceName;
RtlInitUnicodeString(&strDosDeviceName, SYMBOL_NAME);
Status = IoCreateSymbolicLink(&strDosDeviceName, &userDevName); /* R3使用的名字在文档中选:MS-DOS Device Names R0使用的名字在文档中选:NT Device Names */
if (!NT_SUCCESS(Status))
{
/* 驱动对象和设备对象绑定之后,设备对象会被记录到这里 DriverObject->DeviceObject; 需要判断对象是否绑定成功*/
if (DriverObject->DeviceObject != NULL)
{
/* 删除符号链接 */
IoDeleteSymbolicLink(&strDosDeviceName);
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("51[asm][%s:%d] IoCreateSymbolicLink err!", __FUNCTION__, __LINE__);
}
return Status;
}
DriverObject->DriverUnload = Unload;
DbgPrint("51[asm][%s:%d] hello word! DriverEntry:%p,Unload:%p,&status:%p,pDeviceObj:%p", __FUNCTION__, __LINE__, DriverEntry, Unload, &Status,&pDeviceObj);
return Status;
}
NTSTATUS DispatchCreat(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
// 这样返回的数据才是完整的
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchClose(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
// 这样返回的数据才是完整的
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchRead(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/* 验证这里是R3的缓存地址,输出一下 */
DbgPrint("51[asm][%s:%d] Irp->UserBuffer:%p", __FUNCTION__, __LINE__, Irp->UserBuffer);
RtlCopyMemory(Irp->UserBuffer,g_zMsg,sizeof(g_zMsg));
Irp->IoStatus.Status = STATUS_SUCCESS; /* 告诉R3返回的状态 */
Irp->IoStatus.Information = sizeof(g_zMsg); /* 告诉R3读取的字节数 */
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS; /* R3的ReadFile的返回值 */
}
NTSTATUS DispatchWrite(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
// 这样返回的数据才是完整的
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DispatchControl(_DEVICE_OBJECT* DeviceObject, _IRP* Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
/*
1. 这个函数表示 请求是否结束
2. 第一个参数:Irp(I/O Request Packet) 把所有的请求参数疯转到这个结构体了
3. 第二个参数,让挂起的线程快速恢复,把线程的优先级往上提;
4. 当前IO_NO_INCREMENT: 不要提升优先级,什时候唤醒就什么时候结束
5. 除了入口函数和卸载函数外,其他全部函数都是一样的,要这样处理;
*/
DbgPrint("51[asm][%s:%d]", __FUNCTION__, __LINE__);
// 这样返回的数据才是完整的
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
sample.h
#pragma once
#include <ntddk.h>
#define DEVICE_NAME L"\\Device\\51asmMark"
#define SYMBOL_NAME L"\\DosDevices\\51asmMark"
//#define SYMBOL_NAME L"\\??\\51asmMark"
VOID Unload(__in struct _DRIVER_OBJECT* DriverObject);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath);
NTSTATUS DispatchCreat(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchClose(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchRead(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchWrite(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
NTSTATUS DispatchControl(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp);
/*
IRP_MJ_DEVICE_CONTROL: 读写可以控制摄像头的打开关闭,控制则可以控制其他的,如:摄像头的旋转...[除了硬件厂商,其他人可不知道怎么让摄像头旋转]
是用来写不通用的功能; 读写的参数需要传一个缓冲区和一个长度,而control的参数,微软不知道传什么参数
control就是要传一些只有硬件厂家才能解析的数据
显卡(NVIDIA)的驱动安装之后,R3上有界面,设置显卡的一些特性,会调用DispatchControl这个函数,这里可以做一些硬件独有的功能;
有一种软件可以拿到硬件的各种信息:有没有可能把control的参数逆清楚,模仿参数发送数据,这样就可以拿到硬件的各种信息了
前几天在闲鱼买了太Macmini主机,最担心的就是有人通过这种方式来修改主机里的信息,如:8+256的主机刷成32+2t的,买家需要反复擦亮眼睛奥;
*/