目录
驱动调用驱动.其实就是两个内核内核驱动之间的通信. 比如应用程序和驱动程序通信就算为一种通信. 应用程序可以 发送 IRP_MJ_READ
请求(ReadFile
) 发送给 DrvierA
程序. 然后DriverA
进行相应的 IRP处理
操作. 当然发送 IRP_MJ_READ
请求的时候可以发送同步请求
或者异步请求
.这就看DriverA
如何处理这些请求了.是否支持异步
.
而驱动程序调用驱动程序
也是一样的. 也是 DriverA
发送请求给DriverB
然后DriverB
来处理DriverA
的请求. 如果 DriverB
支持异步,那么DriverA
也可以进行异步读取.
如图,应用程序调用 ReadFile的时候 就会产生 IRP_MJ_READ
请求. 此时这个请求就会发送到DriverA
. DriverA
进行处理,处理的方式是它读取DriverB
的内容(调用ZwReadFile
) 此时DriverB
处理DriverA
的请求,并且将数据返回. DriverA
得到数据之后就处理 应用程序的请求.比如将DriverB
返回的数据填充到应用程序的Buffer中.并且返回.
ZwReadFile ZwWriteFile ZwDeviceIoControlFile
都可以进行通信.在文件句柄一讲中.只介绍ZwReadFile
方式.其它方式是一样的. 参数调用可以参考Ring3. ZwReadFile
方式搞懂了.那么其它的就会了.
在应用层我们访问驱动层并且进行通信的时候. 第一步就是 CreateFile
打开符号链接. 返回一个文件句柄
,然后使用ReadFile /WriteFile/DeviceIoControl
等函数来操作这个句柄
发送对应的 IRP 请求
给驱动.然后驱动响应这些请求来进行处理.
所谓文件句柄形式就是如上所说. 内核层也是一样的. 不过注意的是 内核层可以使用设备名直接打开一个驱动来进行操作.
所以我们需要准备一个 DriverB
驱动并将其加载.
以文件句柄方式打开设备
并且进行操作.支持同步
和异步
. 如果要使用异步
.那么我们的DrvierB
也要支持异步
处理.
文件句柄方式的内核操作API如下:
NTSYSAPI NTSTATUS ZwCreateFile(
[out] PHANDLE FileHandle,
[in] ACCESS_MASK DesiredAccess,
[in] POBJECT_ATTRIBUTES ObjectAttributes,
[out] PIO_STATUS_BLOCK IoStatusBlock,
[in, optional] PLARGE_INTEGER AllocationSize,
[in] ULONG FileAttributes,
[in] ULONG ShareAccess,
[in] ULONG CreateDisposition,
[in] ULONG CreateOptions,
[in, optional] PVOID EaBuffer,
[in] ULONG EaLength
);
如果是同步进行驱动调用驱动操作. 那么下面几点必须要注意.
DesiredAccess
: 必须有此 SYNCHRONIZE
标志.
CreateOptions
: 必须有 FILE_SYNCHRONOUS_IO_NONALERT
或者 FILE_SYNCHRONOUS_IO_NONALERT
标志
NTSYSAPI NTSTATUS ZwReadFile(
[in] HANDLE FileHandle,
[in, optional] HANDLE Event,
[in, optional] PIO_APC_ROUTINE ApcRoutine,
[in, optional] PVOID ApcContext,
[out] PIO_STATUS_BLOCK IoStatusBlock,
[out] PVOID Buffer,
[in] ULONG Length,
[in, optional] PLARGE_INTEGER ByteOffset,
[in, optional] PULONG Key
);
其实操作DriverB 和应用层操作DriverA一样.都是利用的文件API.
首先准备一个DriverB
驱动. 并且建立符号链接与设备
. 并且进行加载
. DriverB
可以处理 IRP_MJ_READ
的请求. 比如请求后直接填充HelloWorld
并且返回.
下载Winobj 查看设备是否创建成功.如下图:
可以看到DrvierB
已经创建了. 其设备名字为: \Device\DriverB
这个也是我们内核层需要打开的.
DriverB 和正常驱动一样.建立符号链接.建立设备链接. 设置缓冲区读取方式 初始化派遣函数 设置卸载函数.
所以这里之贴出 IRP_MJ_READ
的读请求处理的代码.
NTSTATUS CompleteReadRequest(PDEVICE_OBJECT device, PIRP irp)
{
UNREFERENCED_PARAMETER(irp);
if (config.GetMySelfDevice() == device && irp != nullptr)
{
KdBreakPoint();
PIO_STACK_LOCATION irp_stack = IoGetCurrentIrpStackLocation(irp);
if (irp->MdlAddress && irp_stack)
{
bool is_read = false;
PVOID pBuffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
ULONG uReadLen = irp_stack->Parameters.Read.Length;
size_t srclen = (wcslen(L"HelloWorld")+1) * 2;
if (uReadLen > srclen)
{
is_read = true;
RtlCopyMemory(pBuffer, L"HelloWorld", srclen);
}
if (is_read)
{
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = srclen;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
else
{
irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
}
return STATUS_SUCCESS;
}
可以看到我是用的 DO_DIRECT_IO
方式,并不是缓冲区方式. 这一点在创建设备并且设置缓冲区方式
的时候设置的. 不了解 DO_DIRECT_IO
方式的 请看下其它资料.
DriverA
就很简单了.直接 ZwCreateFile
打开DriverB
的设备.然后ZwReadFile
读取数据即可.
这样触发的IRP_MJ_READ
请求就会被DriverB
捕获.然后DriverB
就填充HelloWorld
返回.DriverA
自然就得到了 HelloWorld
字符串了.
在上面我们 建立一个标准的驱动通讯图. 也就是1.2小节
. 那是由 应用层往下发请求
来操作DriverA
的.然后DriverA
读取DriverB
. 而在这里我们为了简单.直接在DrierA
里面就读取DriverB
. 读取的数据由 DbgPrint
函数打印. 可以不使用应用层来通知我们.
代码如下:
void CallDriverMethod1()
{
/*
同步调用Driver B
1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
2.CreateFile指定同步方式打开设备 DesiredAccess:SYNCHRONIZE CreateOptions:
FILE_SYNCHRONOUS_IO_NONALERT or
FILE_SYNCHRONOUS_IO_ALERT
3.调用ReadFile发送 IRP_MJ_READ 请求即可
*/
HANDLE device_handle = NULL;
OBJECT_ATTRIBUTES driver_name = { 0 };
IO_STATUS_BLOCK status_block = { 0 };
UNICODE_STRING uc_driver_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
InitializeObjectAttributes(&driver_name, &uc_driver_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
NTSTATUS status = STATUS_UNSUCCESSFUL;
TCHAR read_buf[100] = { 0 };
do
{
KdBreakPoint();
status = ZwCreateFile(&device_handle,
FILE_GENERIC_READ | SYNCHRONIZE, //或者直接使用 FILE_GENERIC_READ
&driver_name,
&status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT, //必须带有 FILE_SYNCHRONOUS_IO_ALERT
// 或 FILE_SYNCHRONOUS_IO_NONALERT
NULL,
0);
if (!NT_SUCCESS(status))
break;
status = ZwReadFile(
device_handle, //文件句柄
nullptr,
nullptr,
nullptr,
&status_block, //读取的字节数 以及返回值
read_buf, //缓冲区
sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
0, //读取的偏移
nullptr);
if (!NT_SUCCESS(status))
break;
DbgPrint("[A] --> read value is %ws \r\n" , read_buf);
} while (false);
if (device_handle != nullptr)
{
ZwClose(device_handle);
device_handle = nullptr;
}
return;
}
学过IRP同步与异步方式
.那么我们就知道异步方式
更为方便也更为贴合系统底层设计. 但是偏一点复杂. 在之前 IRP同步与异步
篇章中我们有讲到,应用层如何进行异步处理的
. 分了两种方式.
回顾第一种 ReadFile
方式. 提供一个 OVERLAPPED
和初始化里面的一个事件. 打开设备
的时候指定为异步方式
. 当ReadFile
调用完毕之后 会触发 IRP_MJ_READ
请求. 请求发送给DriverA
. DriverA挂起IRP
的请求. 并记录
当前挂起的IRP
,然后在IRP_MJ_CLEARN
请求中来完成挂起的IRP请求
. 此时因为操作系统自身的机制.会设置ring3初始化的事件
. 然后代码回退到ring3
这边. ring3
这边就等待这个事件
.一旦被设置
就会代表请求执行完成
了. 此时就可以继续往下操作了.
第二种方式跟第一种方式相似. 唯一不同的就是 调用 ReadFileEx
函数 而这个函数可以提供一个回调函数
. 当内核异步处理完毕之后会调用我们的回调函数.
而我们也不需要设置 OVERLAPPED 事件了
.
内核下其实和应用层的异步处理方式一样. 借用了 ReadFileEx的回调函数方式,同时又借用了第一种方式的事件形式.
简单来说就是 我们需要提供一个回调函数
.并初始化一个事件
. 当DriverB异步处理完成
之后就会调用我们提供的回调函数
. 但是我们需要在回调函数里面设置这个事件
. 设置之后我们 读(ZwReadFile) 下面就等待
这个事件.
其实也是利用了事件
和回调函数
的机制.
DriverB
驱动挂起IRP请求
. 并建立一个 Timer
和Dpc
定时器.三秒执行一次Irp请求
.
这点和 IRP 同步与异步一篇中讲解的 IRP超时处理是一样的,唯一不同的就是超时处理是取消IRP. 而我们这里是完成IRP.
其中这里只提供DriverB的两个核心处理函数. 至于 初始化timer DPC
以及停止计时器 请移步 IRP 同步与异步
一篇中的 IRP超时处理小节.
DriverB
读请求的处理.
NTSTATUS CompleteReadRequest(PDEVICE_OBJECT device, PIRP irp)
{
UNREFERENCED_PARAMETER(irp);
if (config.GetMySelfDevice() == device && irp != nullptr)
{
//挂起IRP
IoMarkIrpPending(irp);
//记录此irp
PDEVICE_EXTENSION device_ex = nullptr;
device_ex = (PDEVICE_EXTENSION)device->DeviceExtension;
if (device_ex)
{
device_ex->irp_ptr = irp;
}
else
{
device_ex->irp_ptr = nullptr;
}
//设置超时处理
LARGE_INTEGER timeout = { 0 };
timeout.QuadPart = -10 * 3000000;
KeSetTimer(&device_ex->timer,
timeout,
&device_ex->dpc);
//返回Pending状态
return STATUS_PENDING;
}
return STATUS_SUCCESS;
}
返回 pending
状态,并且在设备扩展中记录此IRP
.
延迟处理函数.
//延迟处理
VOID DelayDpc(
_In_ struct _KDPC* Dpc,
_In_opt_ PVOID DeferredContext,
_In_opt_ PVOID SystemArgument1,
_In_opt_ PVOID SystemArgument2
)
{
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
/*
1.通过context得到当前的DeviceObje对象
2.通过DeviceObj对象得到当前的设备扩展
3.通过设备扩展找到记录的IRP并且取消IRP
*/
KdBreakPoint();
if (DeferredContext != nullptr)
{
PDEVICE_OBJECT device = nullptr;
PDEVICE_EXTENSION device_ex = nullptr;
device = (PDEVICE_OBJECT)DeferredContext;
if (device->DeviceExtension != nullptr)
{
device_ex = (PDEVICE_EXTENSION)device->DeviceExtension;
PIRP irp = device_ex->irp_ptr;
//完成IRP
PVOID pBuffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
RtlStringCbCopyNW((NTSTRSAFE_PWSTR)pBuffer, 100, L"HelloWorld", (wcslen(L"HelloWorld") + 1) * 2);
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = (wcslen(L"HelloWorld")+1)*2;
IoCompleteRequest(irp, IO_NO_INCREMENT);
DbgPrint("[B]-->延迟完成Irp请求\r\n");
//讲device_ex记录的 Irp置空.
device_ex->irp_ptr = nullptr;
}
else
{
DbgPrint("Irp操作函数执行中判断的附加数据为空\r\n");
}
}
else
{
DbgPrint("参数为空\r\n");
}
}
要进行异步读取,首先我们之前所说的 ZwCreateFile
参数中就不能带有 同步
的标志了
其次要进行异步读取的时候 ZwReadFile中要读取的偏移量必须给定
否则函数会返回 STATUS_INVALID_PARAMETER
也就是错误码: ((NTSTATUS)0xC000000DL)
其次我们因为是异步读取,当底层驱动处理完毕之后会调用我们的回调函数. 所以我们需要提供一个回调函数. 我们还需要提供一个事件. 在我们回调函数里面设置事件. 这样就能通过事件进行通讯了.
代码如下:
//回调函数
VOID NTAPI Complete (
_In_ PVOID ApcContext,
_In_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ ULONG Reserved
)
{
UNREFERENCED_PARAMETER(Reserved);
KdBreakPoint();
DbgPrint("[A]--> 异步函数被执行\r\n");
DbgPrint("[A]--->完成函数->读取的字节数 = %d \r\n", IoStatusBlock->Information);
KeSetEvent((PKEVENT)ApcContext, IO_NO_INCREMENT, FALSE);
}
//异步调用
void CallDriverMethod2()
{
/*
异步调用Driver B
1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
2.CreateFile 异步打开 DesiredAccess:不能带有SYNCHRONIZE
CreateOptions:不能带有 FILE_SYNCHRONOUS_IO_NONALERT or FILE_SYNCHRONOUS_IO_ALERT
3.初始化事件 设置回调函数 调用ReadFile发送 IRP_MJ_READ 请求
*/
HANDLE device_handle = NULL;
OBJECT_ATTRIBUTES driver_name = { 0 };
IO_STATUS_BLOCK status_block = { 0 };
UNICODE_STRING uc_driver_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
InitializeObjectAttributes(&driver_name, &uc_driver_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
NTSTATUS status = STATUS_UNSUCCESSFUL;
TCHAR read_buf[100] = { 0 };
do
{
KdBreakPoint();
status = ZwCreateFile(&device_handle,
FILE_READ_ATTRIBUTES,
&driver_name,
&status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
0,
NULL,
0);
if (!NT_SUCCESS(status))
break;
KEVENT event = { 0 }; //初始化一个自动无信号的事件
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
LARGE_INTEGER offset = { 0 };
status = ZwReadFile(
device_handle, //文件句柄
nullptr,
Complete, //设置一个完成回调
&event, //给完成回调传一个event句柄.context
&status_block, //读取的字节数 以及返回值
read_buf, //缓冲区
sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
&offset, //读取的偏移 比如给定.
nullptr);
if (status == STATUS_PENDING)
{
DbgPrint("[A]---> 底层驱动正在进行异步操作\r\n");
//采用无限等待方式进行等待
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, 0);
DbgPrint("[A]---> 数据读取完成\r\n");
DbgPrint("[A] --> read value is %ws \r\n", read_buf);
}
} while (false);
if (device_handle != nullptr)
{
ZwClose(device_handle);
device_handle = nullptr;
}
return;
}
第二种方式是利用了 文件句柄
每打开一个设备
,都会伴随着一个FILE_OBJCE
,这个结构里面记录着文件对
象,而这个对象里面有个事件域
. 我们可以利用这个事件域来进行异步
. 当DriverB执行完毕之后则会设置这个事件域.
其中结构如下,简单了解.
kd> dt _FILE_OBJECT
nt!_FILE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x008 DeviceObject : Ptr64 _DEVICE_OBJECT //记录着设备
+0x010 Vpb : Ptr64 _VPB
+0x018 FsContext : Ptr64 Void
+0x020 FsContext2 : Ptr64 Void
+0x028 SectionObjectPointer : Ptr64 _SECTION_OBJECT_POINTERS
+0x030 PrivateCacheMap : Ptr64 Void
+0x038 FinalStatus : Int4B
+0x040 RelatedFileObject : Ptr64 _FILE_OBJECT
+0x048 LockOperation : UChar
+0x049 DeletePending : UChar
+0x04a ReadAccess : UChar
+0x04b WriteAccess : UChar
+0x04c DeleteAccess : UChar
+0x04d SharedRead : UChar
+0x04e SharedWrite : UChar
+0x04f SharedDelete : UChar
+0x050 Flags : Uint4B
+0x058 FileName : _UNICODE_STRING //记录了文件的名字
+0x068 CurrentByteOffset : _LARGE_INTEGER
+0x070 Waiters : Uint4B
+0x074 Busy : Uint4B
+0x078 LastLock : Ptr64 Void
+0x080 Lock : _KEVENT
+0x098 Event : _KEVENT //此事件
+0x0b0 CompletionContext : Ptr64 _IO_COMPLETION_CONTEXT
+0x0b8 IrpListLock : Uint8B
+0x0c0 IrpList : _LIST_ENTRY
+0x0d0 FileObjectExtension : Ptr64 Void
要通过文件句柄
获取FILE_OBJECT
文件对象,那么我们离不开下面几个API.
NTSTATUS ObReferenceObjectByHandle(
[in] HANDLE Handle, //句柄
[in] ACCESS_MASK DesiredAccess,//权限
[in, optional] POBJECT_TYPE ObjectType,//要获取的对象类型
[in] KPROCESSOR_MODE AccessMode,//用户还是内核模式
[out] PVOID *Object,//成功之后返回的对象类型的指针
[out, optional] POBJECT_HANDLE_INFORMATION HandleInformation//句柄信息
);
此API就是通过句柄查找对应的对象结构. 比如你是线程句柄那你就可以通过线程句柄查找线程对象, 进程句柄那么就可以查找进程对象. 文件句柄就可以查找文件对象. 等等.
它支持的类型如下,而我们只是使用到了文件句柄.
ObjectType 参数 | 对象指针类型 |
---|---|
*ExEventObjectType | PKEVENT |
*ExSemaphoreObjectType | PKSEMAPHORE |
*IoFileObjectType | PFILE_OBJECT |
*PsProcessType | PEPROCESS 或 PKPROCESS |
*PsThreadType | PETHREAD 或 PKTHREAD |
*SeTokenObjectType | PACCESS_TOKEN |
*TmEnlistmentObjectType | PKENLISTMENT |
*TmResourceManagerObjectType | 2013 年 1 月 3 日 |
*TmTransactionManagerObjectType | PKTM |
*TmTransactionObjectType | PKTRANSACTION |
注意当获取对象成功之后其对象的引用计数会+1. 所以我们还需要配合下面的函数来减少引用计数.
void ObDereferenceObject(
[in] a //指向对象的指针,减少引用计数.
);
//异步调用2
void CallDriverMethod3()
{
/*
异步调用Driver B
1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
2.CreateFile 异步打开 DesiredAccess:不能带有SYNCHRONIZE
CreateOptions:不能带有 FILE_SYNCHRONOUS_IO_NONALERT or FILE_SYNCHRONOUS_IO_ALERT
3.初始化事件 设置回调函数 调用ReadFile发送 IRP_MJ_READ 请求
*/
HANDLE device_handle = NULL;
OBJECT_ATTRIBUTES driver_name = { 0 };
IO_STATUS_BLOCK status_block = { 0 };
UNICODE_STRING uc_driver_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
InitializeObjectAttributes(&driver_name, &uc_driver_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
NTSTATUS status = STATUS_UNSUCCESSFUL;
TCHAR read_buf[100] = { 0 };
do
{
KdBreakPoint();
status = ZwCreateFile(&device_handle,
FILE_READ_ATTRIBUTES,
&driver_name,
&status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
0,
NULL,
0);
if (!NT_SUCCESS(status))
break;
LARGE_INTEGER offset = { 0 };
status = ZwReadFile(
device_handle, //文件句柄
nullptr,
nullptr,
nullptr,
&status_block, //读取的字节数 以及返回值
read_buf, //缓冲区
sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
&offset, //读取的偏移
nullptr);
if (status == STATUS_PENDING)
{
DbgPrint("[A]---> 底层驱动正在进行异步操作\r\n");
PFILE_OBJECT fileobj = nullptr;
status = ObReferenceObjectByHandle(device_handle, EVENT_MODIFY_STATE, *IoFileObjectType, KernelMode,(PVOID*)&fileobj, nullptr);
if (NT_SUCCESS(status))
{
DbgPrint("[A]---> 获取文件对象成功,检查事件域\r\n");
//采用无限等待方式进行等待
KeWaitForSingleObject(&fileobj->Event, Executive, KernelMode, FALSE, 0);
DbgPrint("[A]---> 数据读取完成\r\n");
DbgPrint("[A] --> read value is %ws \r\n", read_buf);
ObDereferenceObject(fileobj); //减少引用
}
else
{
DbgPrint("[A]---> 文件对象等待失败\r\n");
}
}
} while (false);
if (device_handle != nullptr)
{
ZwClose(device_handle);
device_handle = nullptr;
}
return;
}
在进行驱动通信的时候,有时候并不能通过设备名来打开设备. 这点尤其在WDM中常见.
所以我们可以通过查找符号链接里面的设备名来打开设备从而进行通信.
主要使用了两个API.
打开符号链接对象,通过符号链接名字.
NTSYSAPI NTSTATUS ZwOpenSymbolicLinkObject(
[out] PHANDLE LinkHandle, //打开成功之后传出的句柄
[in] ACCESS_MASK DesiredAccess,//权限
[in] POBJECT_ATTRIBUTES ObjectAttributes//要打开的符号链接
);
通过符号链接名字查找设备名字
NTSYSAPI NTSTATUS ZwQuerySymbolicLinkObject(
[in] HANDLE LinkHandle, //符号链接句柄
[in, out] PUNICODE_STRING LinkTarget, //传出的找到的设备名字,必须外面申请
[out, optional] PULONG ReturnedLength//传出的返回值
);
所以下面只是用这两个API进行 设备的查找.代码也就是填充这两个API所需要的参数.
至于同步异步等 跟文件句柄方式一样.
注意: 关闭句柄的时候 请用 ZwClose
.内核下要注意资源的释放.
void CallDriverMethod4()
{
/*
异步调用Driver B
1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
2.CreateFile 异步打开 DesiredAccess:不能带有SYNCHRONIZE
CreateOptions:不能带有 FILE_SYNCHRONOUS_IO_NONALERT or FILE_SYNCHRONOUS_IO_ALERT
3.初始化事件 设置回调函数 调用ReadFile发送 IRP_MJ_READ 请求
*/
HANDLE device_handle = NULL;
OBJECT_ATTRIBUTES symbolic_name = { 0 };
OBJECT_ATTRIBUTES device_name_attribute = { 0 };
IO_STATUS_BLOCK status_block = { 0 };
UNICODE_STRING uc_symbolic_name = RTL_CONSTANT_STRING(L"\\??\\DriverB");
InitializeObjectAttributes(&symbolic_name, &uc_symbolic_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
NTSTATUS status = STATUS_UNSUCCESSFUL;
TCHAR read_buf[100] = { 0 };
HANDLE link_handle = nullptr;
UNICODE_STRING uc_device_name = { 0 };
ULONG device_name_real_size = 100;
do
{
KdBreakPoint();
status = ZwOpenSymbolicLinkObject(&link_handle, FILE_ALL_ACCESS, &symbolic_name);
if (!NT_SUCCESS(status))
break;
uc_device_name.Buffer = (PWCH)ExAllocatePoolWithTag(NonPagedPool, device_name_real_size, (ULONG)0);
if (uc_device_name.Buffer == nullptr)
{
break;
}
uc_device_name.Length = (USHORT)device_name_real_size;
uc_device_name.MaximumLength = (USHORT)device_name_real_size;
status = ZwQuerySymbolicLinkObject(link_handle, &uc_device_name, &device_name_real_size);
if (!NT_SUCCESS(status))
break;
DbgPrint("通过符号链接名查找设备成功\r\n");
DbgPrint("设备名字 = %wZ\r\n",&uc_device_name);
InitializeObjectAttributes(&device_name_attribute, &uc_device_name, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr);
status = ZwCreateFile(&device_handle,
FILE_READ_ATTRIBUTES,
&device_name_attribute,
&status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
0,
NULL,
0);
if (!NT_SUCCESS(status))
break;
LARGE_INTEGER offset = { 0 };
status = ZwReadFile(
device_handle, //文件句柄
nullptr,
nullptr,
nullptr,
&status_block, //读取的字节数 以及返回值
read_buf, //缓冲区
sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
&offset, //读取的偏移
nullptr);
if (status == STATUS_PENDING)
{
DbgPrint("[A]---> 底层驱动正在进行异步操作\r\n");
PFILE_OBJECT fileobj = nullptr;
status = ObReferenceObjectByHandle(device_handle, EVENT_MODIFY_STATE, *IoFileObjectType, KernelMode, (PVOID*)&fileobj, nullptr);
if (NT_SUCCESS(status))
{
DbgPrint("[A]---> 获取文件对象成功,检查事件域\r\n");
//采用无限等待方式进行等待
KeWaitForSingleObject(&fileobj->Event, Executive, KernelMode, FALSE, 0);
DbgPrint("[A]---> 数据读取完成\r\n");
DbgPrint("[A] --> read value is %ws \r\n", read_buf);
ObDereferenceObject(fileobj); //减少引用
}
else
{
DbgPrint("[A]---> 文件对象等待失败\r\n");
}
}
} while (false);
if (uc_device_name.Buffer != nullptr)
{
ExFreePoolWithTag(uc_device_name.Buffer, 0);
uc_device_name.Buffer = 0;
uc_device_name.Length = 0;
uc_device_name.MaximumLength = 0;
}
if (link_handle != nullptr)
{
ZwClose(link_handle);
link_handle = nullptr;
}
if (device_handle != nullptr)
{
ZwClose(device_handle);
device_handle = nullptr;
}
return;
}
所谓IRP方式就是自己申请IRP
然后发送IRP请求去调用DriverB
程序
也就是说让我们自己实现 ZwCreateFile ZwReadFile (ZwWriteFile ZwDeviceIoControFile)
等API.
ZwCreateFile
被调用的时候,内部会产生 IRP_MJ_CREATE
请求 而ZwReadFile
则会产生一个IRP_MJ_READ
的请求. 并且将其发送给DriverB
的派遣函数中.
所以我们就要模拟它们的调用
模拟它们的调用就需要一下几个API了. 如下:
1.通过设备名获取设备结构以及文件结构
NTSTATUS IoGetDeviceObjectPointer(
[in] PUNICODE_STRING ObjectName,
[in] ACCESS_MASK DesiredAccess,
[out] PFILE_OBJECT *FileObject,
[out] PDEVICE_OBJECT *DeviceObject
);
2.手动创建IRP请求.
__drv_aliasesMem PIRP IoBuildSynchronousFsdRequest(
[in] ULONG MajorFunction, //功能号
[in] PDEVICE_OBJECT DeviceObject, //要发送的给的设备
[in, out] PVOID Buffer, //对于功能号IRP_MJ_READ 则戴代表输入缓冲区
[in, optional] ULONG Length, //长度
[in, optional] PLARGE_INTEGER StartingOffset,//读取的偏移
[in] PKEVENT Event, //事件域
[out] PIO_STATUS_BLOCK IoStatusBlock //状态
);
其实你就可以把这个函数看作是 ZwReadFile
或者 ZwWriteFile
此函数的功能号只支持
IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_PNP IRP_MJ_FLUSH_BUFFERS IRP_MJ_SHUTDOWN
通过这些功能号也就知道变相的等于一个函数顶替了几个函数.
值得注意的是这个函数是创建同步类型的IRP
如何保证同步,那就要用到这个函数中的事件域的参数.
3.获取下一层IO堆栈
__drv_aliasesMem PIO_STACK_LOCATION IoGetNextIrpStackLocation(
[in] PIRP Irp
);
通过IRP返回他的下一层的堆栈.因为要模拟调用.所以我们要必须填写IRP的结构. 好在使用
IoBuildSynchronousFsdRequest
函数的时候我们并不需要填写很多. 而是由这个函数填写好了. 我们只需要填写必要的即可.
4.发送IRP请求
其实这个是核心
NTSTATUS IofCallDriver(
PDEVICE_OBJECT DeviceObject, //要给那个设备发送
__drv_aliasesMem PIRP Irp //要发送的IRP
);
在驱动中我们是使用的宏.这也是为了兼容各个版本. 参数参考上面的
#define IoCallDriver(a,b) \
IofCallDriver(a,b)
);
首先模拟调用就是 通过获取设备指针
.和文件结构对象
. 然后申请对应的IRP事件
. 最后设置一下IRP
. 然后进行IoCallDriver
调用.
总结一下,分为如下几个步骤.
IoGetDeviceObjectPointer
通过设备名获取设备对象指针和文件结构对象
设备名
--->如果设备名不好找则可以参考上面说的通过符号链接来查找
除了调用IoGetDeviceObjectPointer
也可以调用ZwCreateFile
. 只不过我们获取的是一个句柄
,我们要根据这个句柄
来使用 ObReferenceByHandle
获取设备结构和文件结构对象.
IoBuildSynchronousFsdRequest
IoGetNextIrpStackLocation
获取下一层堆栈,然后将堆栈中的文件对象结构的域设置为第一步获取出来的文件对象结构
IoCallDriver
发送IRP请求.
IoGetDeviceObjectPointer
会对对象进行引用计数+1(第一次为打开以后就是引用计数+1) 所以我们必须释放对象的引用.
DriverB
还是一直在使用的支持异步挂起
的驱动.核心在于DriverA
的编写.
代码如下:
//直接IRP调用-同步方式
void CallDriverMethod5()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
IO_STATUS_BLOCK status_block = { 0 };
TCHAR read_buf[100] = { 0 };
PFILE_OBJECT file_object;
PDEVICE_OBJECT device_obj;
do
{
KdBreakPoint();
//1.构建设备名字
UNICODE_STRING uc_device_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
//2.根据设备名字获取实际的设备对象和文件对象,等价于直接调用
//CreateFile(返回的句柄可以通过句柄找对象方式获得设备对象和文件对象)
status = IoGetDeviceObjectPointer(&uc_device_name, FILE_ALL_ACCESS, &file_object, &device_obj);
if (!NT_SUCCESS(status))
break;
//3.申请 IRP接口 可以生成 IRP_MJ_READ 请求
LARGE_INTEGER offset = { 0 };
KEVENT event = { 0 };
KeInitializeEvent(&event, NotificationEvent, FALSE);
PIRP irp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, device_obj, read_buf, 100, &offset, &event, &status_block);
PIO_STACK_LOCATION irp_stack = IoGetNextIrpStackLocation(irp);
irp_stack->FileObject = file_object;
status = IoCallDriver(device_obj, irp);
if (status == STATUS_PENDING)
{
DbgPrint("[A]---> 获取文件对象成功,检查事件\r\n");
//采用无限等待方式进行等待
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, 0);
DbgPrint("[A]---> 数据读取完成\r\n");
DbgPrint("[A] --> read value is %ws \r\n", read_buf);
}
} while (false);
ObDereferenceObject(file_object); //减少引用
ObDereferenceObject(device_obj);
return;
}
在这里生成的功能号是 IRP_MJ_READ
所以变相的是相当于我们的 IoBuildSynchronousFsdRequest
函数是 ZwReadFile
.
最后程序直接调用 IoCallDriver进行了发送.
其中对于API来说. 在同步调用里面都已经说了. 异步调用方式就是申请IRP的方式的不同.采用的API不同.
API如下:
__drv_aliasesMem PIRP IoBuildAsynchronousFsdRequest(
[in] ULONG MajorFunction,
[in] PDEVICE_OBJECT DeviceObject,
[in, out] PVOID Buffer,
[in, optional] ULONG Length,
[in, optional] PLARGE_INTEGER StartingOffset,
[in, optional] PIO_STATUS_BLOCK IoStatusBlock
);
唯一不同的就是没有事件域
了.对于 IoBuildAsynchronousFsdRequest
创建的IRP请求,当请求结束的时候,操作系统不会在进行事件通知了. 不过我们想要接受到事件的通知. 那么就要使用 IRP->UserEvent
操作系统会检查这个域是否为空,如果不是空则设置. 所以这个地方当我们的同步点使用. 当请求结束的时候(DriverB --> IoCompleteRequest则会设置这个域
) 则会被设置.
void CallDriverMethod6()
{
NTSTATUS status = STATUS_UNSUCCESSFUL;
IO_STATUS_BLOCK status_block = { 0 };
TCHAR read_buf[100] = { 0 };
PFILE_OBJECT file_object;
PDEVICE_OBJECT device_obj;
do
{
KdBreakPoint();
//1.构建设备名字
UNICODE_STRING uc_device_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
//2.根据设备名字获取实际的设备对象和文件对象,等价于直接调用
//CreateFile(返回的句柄可以通过句柄找对象方式获得设备对象和文件对象)
status = IoGetDeviceObjectPointer(&uc_device_name, FILE_ALL_ACCESS, &file_object, &device_obj);
if (!NT_SUCCESS(status))
break;
//3.申请 IRP接口 可以生成 IRP_MJ_READ 请求
LARGE_INTEGER offset = { 0 };
KEVENT event = { 0 };
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
PIRP irp = IoBuildAsynchronousFsdRequest(IRP_MJ_READ, device_obj, read_buf, 100, &offset, &status_block);
irp->UserEvent = &event; //注意此位置,设置一个事件对象的值
PIO_STACK_LOCATION irp_stack = IoGetNextIrpStackLocation(irp);
irp_stack->FileObject = file_object;
status = IoCallDriver(device_obj, irp);
if (status == STATUS_PENDING)
{
DbgPrint("[A]---> 获取文件对象成功,检查事件\r\n");
//采用无限等待方式进行等待
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, 0);
DbgPrint("[A]---> 数据读取完成\r\n");
DbgPrint("[A] --> read value is %ws \r\n", read_buf);
}
} while (false);
ObDereferenceObject(file_object); //减少引用
ObDereferenceObject(device_obj); //减少引用
return;
}
效果同上.
未完待续