驱动开发:通过Async反向与内核通信

VSole2022-10-20 11:03:23

一、前言

这次学习的是通过运用Async异步模式实现的反向通信,反向通信机制在开发中时常被用到,例如一个杀毒软件如果监控到有异常进程运行或有异常注册表被改写后,该驱动需要主动的通知应用层进程让其知道,这就需要用到驱动反向通信的相关知识点,如下将循序渐进的实现一个反向通信案例。

在开始学习Async反向通信之前先来研究一个Sync正向通信案例,不论是正向反向通信其在通信模式上与《驱动开发:通过ReadFile与内核层通信》所介绍的通信模式基本一致,都是通过ReadFile触发驱动中的IRP_MJ_READ读取派遣。

唯一的区别是在传输数据时使用了MmGetSystemAddressForMdl方式,它将给定MDL描述的物理页面映射到系统空间,并调用RtlCopyMemory()将全局字符串复制到这个空间内,这样客户端就可以循环读取内核传出的数据。

我们来看驱动端代码是如何实现的这个功能,代码并没有什么特殊的无法理解的点,只是需要注意我们在驱动入口调用IoCreateDevice()时传入了第二个参数FILE_DEVICE_EXTENSION,该参数的作用是,创建设备时,指定设备扩展内存的大小,传一个值进去,就会给设备分配一块非页面内存。

#include <ntddk.h>#include <stdio.h> // 保存一段非分页内存,用于给全局变量使用#define FILE_DEVICE_EXTENSION 4096 // 定义全局字符串static int global_count = 0;static char global_char[5][128] = { 0 }; // 驱动绑定默认派遣函数NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp){    _pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;    _pIrp->IoStatus.Information = 0;    IoCompleteRequest(_pIrp, IO_NO_INCREMENT);    return _pIrp->IoStatus.Status;} // 驱动创建后触发NTSTATUS _SyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp){    _pIrp->IoStatus.Status = STATUS_SUCCESS;    _pIrp->IoStatus.Information = 0;    IoCompleteRequest(_pIrp, IO_NO_INCREMENT);    return _pIrp->IoStatus.Status;} // 应用层读数据后触发NTSTATUS _SyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp){    NTSTATUS status = STATUS_SUCCESS;    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);    PVOID pBuffer = NULL;    ULONG uBufferLen = 0;     do    {        // 读写请求使用的是直接I/O方式        pBuffer = MmGetSystemAddressForMdl(_pIrp->MdlAddress);        if (pBuffer == NULL)        {            status = STATUS_UNSUCCESSFUL;            break;        }        uBufferLen = pIrpStack->Parameters.Read.Length;        DbgPrint("读字节长度: %d \n", uBufferLen);         // 最大支持20字节读请求        uBufferLen = uBufferLen >= 20 ? 20 : uBufferLen;         // 输出五次字符串        if (global_count < 5)        {            RtlCopyMemory(pBuffer, global_char[global_count], uBufferLen);            global_count = global_count + 1;        }     } while (FALSE);     // 填写返回状态及返回大小    _pIrp->IoStatus.Status = status;    _pIrp->IoStatus.Information = uBufferLen;     // 完成IRP    IoCompleteRequest(_pIrp, IO_NO_INCREMENT);    return status;} // 卸载驱动VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject){    // 删除创建的设备    UNICODE_STRING  Win32DeviceName;    RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\LySharkSync");    IoDeleteDevice(_pDriverObject->DeviceObject);} // 驱动入口NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath){    UNICODE_STRING DeviceName, Win32DeivceName;    PDEVICE_OBJECT pDeviceObject = NULL;    NTSTATUS status;    HANDLE hThread;    OBJECT_ATTRIBUTES ObjectAttributes;     // 设置符号名    RtlInitUnicodeString(&DeviceName, L"\\Device\\LySharkSync");    RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\LySharkSync");     // 循环初始化IRP函数    for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)    {        _pDriverObject->MajorFunction[i] = _DefaultDispatch;    }     // 再次覆盖派遣函数    _pDriverObject->MajorFunction[IRP_MJ_CREATE] = _SyncCreateCloseDispatch;    _pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _SyncCreateCloseDispatch;    _pDriverObject->MajorFunction[IRP_MJ_READ] = _SyncReadDispatch;    _pDriverObject->DriverUnload = _UnloadDispatch;     // 分配一个自定义扩展 大小为sizeof(DEVEXT)    // By: LyShark.com    status = IoCreateDevice(_pDriverObject, sizeof(FILE_DEVICE_EXTENSION), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);    if (!NT_SUCCESS(status))        return status;     if (!pDeviceObject)        return STATUS_UNEXPECTED_IO_ERROR;     // 为全局变量赋值    strcpy(global_char[0], "hi,lyshark A");    strcpy(global_char[1], "hi,lyshark B");    strcpy(global_char[2], "hi,lyshark C");    strcpy(global_char[3], "hi,lyshark D");    strcpy(global_char[4], "hi,lyshark E");     // 指定读写方式为 直接I/O MDL模式    pDeviceObject->Flags |= DO_DIRECT_IO;     // 数据传输时地址校验大小    pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;    status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);     pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;    return STATUS_SUCCESS;}

对于应用层来说并没有什么特别的,同样调用ReadFile读取内核中的参数,同样for循环读取五次,代码如下:

#include <stdio.h>#include <Windows.h> int main(int argc, char *argv[]){    HANDLE hFile;    char Buffer[10] = { 0 };    DWORD dwRet = 0;    BOOL bRet;     hFile = CreateFileA("\\\\.\\LySharkSync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    if (hFile == INVALID_HANDLE_VALUE)        return 0;     for (int x = 0; x < 5; x++)    {        bRet = ReadFile(hFile, Buffer, 20, &dwRet, NULL);        if (!bRet)        {            CloseHandle(hFile);            return 0;        }        printf("读入数据: %s -> 读取长度: %d \n", Buffer, dwRet);    }    return 0;}

这段代码运行效果如下:

与同步模式不同,异步模式虽然同样使用ReadFile实现通信,但在通信中引入了Event事件通知机制,这也是异步与同步最大的区别所在,用户层可以分别创建多个Event事件,等待内核依次做出相应并最终一并返回。

首先驱动内定义了_DeviceExtension自定义接口,该接口用于保存此次事件所对应的Irp以及其所对应的DPC时间等。

异步分发函数_AsyncReadDispatch同样是被IRP_MJ_READ派遣函数触发的,触发后其内部会首先IoGetCurrentIrpStackLocation得到当前IRP的堆栈信息,然后设置IoMarkIrpPending()并最终将该IRP通过InsertTailList()插入到IRP链表内等待被处理。

IoMarkIrpPending

用于标记指定的IRP,标志着某个驱动的分发例程(分发函数)因需要被其他的驱动程序进一步处理最终返回STATUS_PENDING状态。

函数_CustomDpc则是定时器内部要执行的具体操作,在DriverEntry驱动入口处做了如下初始化,初始化了链表,并初始化了一个定时器,最后启动这个定时器每隔1秒都会执行一次_CustomDpc如果我们的IRP链表内IsListEmpty() 检测存在数据,则会主动拷贝内存RtlCopyMemory并推送到应用层。

// 初始化IRP链表InitializeListHead(&pDevExt->IrpList);// 初始化定时器KeInitializeTimer(&(pDevExt->timer));// 初始化DPC pDevExt是传给_CustomDpc函数的参数KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt); // 设置定时时间位1spDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);// 启动定时器KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);

驱动层完成代码如下所示:

#include <ntddk.h> // 自定义接口扩展typedef struct _DeviceExtension{    LIST_ENTRY IrpList;    KTIMER timer;    LARGE_INTEGER liDueTime;    KDPC dpc;}DEV_EXT, *PDEV_EXT; // 默认派遣函数NTSTATUS _DefaultDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp){    _pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;    _pIrp->IoStatus.Information = 0;    IoCompleteRequest(_pIrp, IO_NO_INCREMENT);    return _pIrp->IoStatus.Status;} // 创建派遣函数NTSTATUS _AsyncCreateCloseDispatch(PDEVICE_OBJECT _pDevcieObject, PIRP _pIrp){    _pIrp->IoStatus.Status = STATUS_SUCCESS;    _pIrp->IoStatus.Information = 0;    IoCompleteRequest(_pIrp, IO_NO_INCREMENT);    return  _pIrp->IoStatus.Status;} // 读取派遣函数NTSTATUS _AsyncReadDispatch(PDEVICE_OBJECT _pDeviceObject, PIRP _pIrp){    NTSTATUS status;    PIO_STACK_LOCATION pIrpStack = IoGetCurrentIrpStackLocation(_pIrp);    PDEV_EXT pDevExt = (PDEV_EXT)_pDeviceObject->DeviceExtension;     IoMarkIrpPending(_pIrp);     // 将IRP插入自定义链表中插入的是ListEntry    InsertTailList(&pDevExt->IrpList, &_pIrp->Tail.Overlay.ListEntry);     // 返回pending 主要返回给I/O管理器的值必须和IRP的Pending标志位一致    // By: LyShark.com    // 即调用iomarkirppending和返回值要一致    return STATUS_PENDING;} // DPC线程VOID _CustomDpc(PKDPC Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2){    PIRP pIrp;    PDEV_EXT pDevExt = (PDEV_EXT)DeferredContext;    PVOID pBuffer = NULL;    ULONG uBufferLen = 0;    PIO_STACK_LOCATION pIrpStack = NULL;     do    {        if (!pDevExt)        {            break;        }         // 检查尾端IRP链表是否为空 为空则跳出        if (IsListEmpty(&pDevExt->IrpList))        {            break;        }         // 从IRP链表中取出一个IRP并完成该IRP 取出的是ListEntry的地址        PLIST_ENTRY pListEntry = (PLIST_ENTRY)RemoveHeadList(&pDevExt->IrpList);        if (!pListEntry)            break;         pIrp = (PIRP)CONTAINING_RECORD(pListEntry, IRP, Tail.Overlay.ListEntry);        pIrpStack = IoGetCurrentIrpStackLocation(pIrp);         DbgPrint("当前DPC Irp: 0x%x\n", pIrp);         // 驱动程序的读写方式位直接I/O        pBuffer = MmGetSystemAddressForMdl(pIrp->MdlAddress);        if (pBuffer == NULL)        {            pIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;            pIrp->IoStatus.Information = 0;            IoCompleteRequest(pIrp, IO_NO_INCREMENT);             break;        }        uBufferLen = pIrpStack->Parameters.Read.Length;        DbgPrint("读取DPC长度: %d\n", uBufferLen);         // 支持5字节以下的读请求        uBufferLen = uBufferLen > 13 ? 13 : uBufferLen;         // 复制请求内容        RtlCopyMemory(pBuffer, "hello lyshark", uBufferLen);         pIrp->IoStatus.Status = STATUS_SUCCESS;        pIrp->IoStatus.Information = uBufferLen;         // 完成该IRP        IoCompleteRequest(pIrp, IO_NO_INCREMENT);    } while (FALSE);     // 重新设置定时器    KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);} // 卸载驱动VOID _UnloadDispatch(PDRIVER_OBJECT _pDriverObject){    UNICODE_STRING Win32DeviceName;    PDEV_EXT pDevExt = (PDEV_EXT)_pDriverObject->DeviceObject->DeviceExtension;     RtlInitUnicodeString(&Win32DeviceName, L"\\DosDevices\\LySharkAsync");     // 删除定时器    // LyShark    KeCancelTimer(&pDevExt->timer);    // 删除创建的设备    IoDeleteDevice(_pDriverObject->DeviceObject);} // 驱动入口NTSTATUS DriverEntry(PDRIVER_OBJECT _pDriverObject, PUNICODE_STRING _pRegistryPath){    UNICODE_STRING DeviceName, Win32DeivceName;    PDEVICE_OBJECT pDeviceObject = NULL;    NTSTATUS status;    PDEV_EXT pDevExt = NULL;    HANDLE hThread;    OBJECT_ATTRIBUTES ObjectAttributes;    CLIENT_ID CID;     RtlInitUnicodeString(&DeviceName, L"\\Device\\LySharkAsync");    RtlInitUnicodeString(&Win32DeivceName, L"\\DosDevices\\LySharkAsync");     for (ULONG i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)    {        _pDriverObject->MajorFunction[i] = _DefaultDispatch;    }     _pDriverObject->MajorFunction[IRP_MJ_CREATE] = _AsyncCreateCloseDispatch;    _pDriverObject->MajorFunction[IRP_MJ_CLOSE] = _AsyncCreateCloseDispatch;    _pDriverObject->MajorFunction[IRP_MJ_READ] = _AsyncReadDispatch;    _pDriverObject->DriverUnload = _UnloadDispatch;     // 分配自定义扩展    status = IoCreateDevice(_pDriverObject, sizeof(DEV_EXT), &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);    if (!NT_SUCCESS(status))        return status;    if (!pDeviceObject)        return STATUS_UNEXPECTED_IO_ERROR;     pDeviceObject->Flags |= DO_DIRECT_IO;    pDeviceObject->AlignmentRequirement = FILE_WORD_ALIGNMENT;    status = IoCreateSymbolicLink(&Win32DeivceName, &DeviceName);     pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;    pDevExt = (PDEV_EXT)pDeviceObject->DeviceExtension;     // 初始化IRP链表    InitializeListHead(&pDevExt->IrpList);    // 初始化定时器    KeInitializeTimer(&(pDevExt->timer));    // 初始化DPC pDevExt是传给_CustomDpc函数的参数    KeInitializeDpc(&pDevExt->dpc, (PKDEFERRED_ROUTINE)_CustomDpc, pDevExt);     // 设置定时时间位1s    pDevExt->liDueTime = RtlConvertLongToLargeInteger(-10000000);    // 启动定时器    KeSetTimer(&pDevExt->timer, pDevExt->liDueTime, &pDevExt->dpc);     return STATUS_SUCCESS;}

驱动层说完了,接下来是应用层,对于应用层来说,需要使用CreateEvent打开通知事件,或者叫做事件对象,然后当有通知时,则直接使用ReadFile读取对应的缓冲区,当所有读取全部结束WaitForMultipleObjects等待结束即输出结果。

#include <stdio.h>#include <Windows.h> int main(int argc, char *argv[]){    HANDLE hFile;    char Buffer[3][32] = { 0 };    DWORD dwRet[3] = { 0 };    OVERLAPPED ol[3] = { 0 };    HANDLE hEvent[3] = { 0 };     // By:LyShark    hFile = CreateFileA("\\\\.\\LySharkAsync", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,        NULL);    if (INVALID_HANDLE_VALUE == hFile)        return  0;     // event用来通知请求完成    hEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);    ol[0].hEvent = hEvent[0];     hEvent[1] = CreateEvent(NULL, TRUE, FALSE, NULL);    ol[1].hEvent = hEvent[1];     hEvent[2] = CreateEvent(NULL, TRUE, FALSE, NULL);    ol[2].hEvent = hEvent[2];     // 读取事件内容到缓存    ReadFile(hFile, Buffer[0], 13, &dwRet[0], &ol[0]);    ReadFile(hFile, Buffer[1], 13, &dwRet[1], &ol[1]);    ReadFile(hFile, Buffer[2], 13, &dwRet[2], &ol[2]);     // 等待三个事件执行完毕    WaitForMultipleObjects(3, hEvent, TRUE, INFINITE);     // 输出结果    printf("缓存LyShark A: %s \n", Buffer[0]);    printf("缓存LyShark B: %s \n", Buffer[1]);    printf("缓存LyShark C: %s \n", Buffer[2]);     CloseHandle(hFile);    return  0;}

这段代码最终运行效果如下:

通信char函数
本作品采用《CC 协议》,转载必须注明作者和本文链接
本文更多的是根据调试Windows Server 2003,分析漏洞成因。但是AD域并没有对其进行强校验。通过建立与域控同名却不以\$结尾的机器账户,即DC,对域控进行欺骗。至此便得到了高权限ST。从上图中可以很明确的看到域控的机器名为WINSRVSERVER$,之后会使用WINSRVSERVER作为机器账户名进行欺骗。攻击准备工作相关准备工作不是本文重点,可以在noPac项目中学习。
项目的bug和不足作者只实现了TCP-client的代码,并且x86下测试通过,但是在x64模式下连接到服务端时出现了错误。
在开始学习Async反向通信之前先来研究一个Sync正向通信案例,不论是正向反向通信其在通信模式上与《驱动开发:通过ReadFile与内核层通信》所介绍的通信模式基本一致,都是通过ReadFile触发驱动中的IRP_MJ_READ读取派遣。唯一的区别是在传输数据时使用了MmGetSystemAddressForMdl方式,它将给定MDL描述的物理页面映射到系统空间,并调用RtlCopyMemory()将全局字符串复制到这个空间内,这样客户端就可以循环读取内核传出的数据。
MITM Fuzz下图是用户层与内核层实现通信的过程,可以看到,最后是通过NtDeviceIoControlFile来分发给相应驱动对象的派遣函数的,因此,可以通过对该函数进行HOOK操作。如果将修改以后的数据发送给NtDeviceIoControlFile函数以后,发生了内核崩溃或蓝屏,往往预示着该驱动程序可能存在内核漏洞。
由于RPC的规范非常复杂,且相关内容很少,所以我也只是根据文档和调式尽可能地将我的理解贴上来,如果有误欢迎指正。MyRPCServer向外只导出一个函数,并打印出4个传入的参数内容.MyRPCClient是一个最简单的RPC客户端,它调用MyRPCServer向外导出的HelloProc函数,并传入4个参数。
BEServer - BattlEye服务器,收集上传的信息,并判定作弊行为。本次分析的是BEDaisy,也就是BE内核驱动中的各种检测。上传的内容主要是一些黑名单特征,这些特征应该是从服务器下发的,因此可以在不重新编译驱动的情况下,动态调整检测的特征。没有给定偏移量的特征,BE在检测时会按子串匹配的方式尝试所有位置进行匹配。
一看题目给出的sys文件,可以确定是本人最擅长的Windows内核和注入相关题目,必须要把这个驱动安排的明明白白的。
linux跟踪技术之ebpf
2022-12-30 10:51:15
eBPF是一项革命性的技术,起源于 Linux 内核,可以在操作系统内核等特权上下文中运行沙盒程序。它可以安全有效地扩展内核的功能,而无需更改内核源代码或加载内核模块。比如,使用ebpf可以追踪任何内核导出函数的参数,返回值,以实现kernel hook 的效果;通过ebpf,还可以在网络封包到达内核协议栈之前就进行处理,这可以实现流量控制,甚至隐蔽通信
windows消息机制详解
2022-06-10 16:15:55
要想深入理解windows,消息机制的知识是必不可少的。
在Windows大部分应用都是基于消息机制,他们都拥有一个消息过程函数,根据不同消息完成不同功能,windows通过钩子机制来截获和监视系统中的这些消息。一般钩子分局部钩子与全局钩子,局部钩子一般用于某个线程,而全局钩子一般通过dll文件实现相应的钩子函数
VSole
网络安全专家