主防拦截,弹窗

本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正

拦截,弹窗

  • 应用场景:假设系统中了勒索病毒,开始加密整个磁盘,这时候内核监控到并拦截(Hook,驱动过滤,回调)了这些操作,进行数据收集弹窗给用户等待处理。

弹窗

整体思路

  1. 驱动截获攻击操作,进行数据收集
  • 驱动为了拿到应用处理完弹窗的返回的结果,建立一个WaitList(等待链表,结点包括一个标识ID(用来区分攻击事件),一个event,一个等待结果的域),通过WaitList上等待应用层处理弹窗的结果。
  • 驱动为了把收集数据传给应用层,建立一个OperList(操作链表,进程Pid,进程全路径,目标对象等), 建立一个PendingIrpList来应对OperList为空情况。把收集数据存为一个结构体放在OperList,传给应用。或者优先满足PendingIrpList的Irp。
    • 如果此时内容可读(链表不为空),则该独立线程向驱动发出一个读Irp来读取OperList一个结点的数据,弹出一个弹窗提示用户(比如某某进程正在往system32释放DLL文件)并倒计时等待用户操作,对弹窗处理的结果通过DeviceIoControl返回给驱动,把结果存放在WaitList里面,结点上的事件就会通知驱动来拿数据,驱动从WaitList拿到结果做出相应操作(阻止/执行某个进程的操作)
    • 如果此时没有内容可读(链表为空,系统比较健康,攻击事件比较少),则该独立线程向驱动发出的读Irp会被挂起,放在PendingIrpList中,当下次驱动截获到攻击操作的时候,不会把收集数据存为一个结构体放在OperList,而是会直接把收集的数据直接给PendingIrpList的Irp(此时PendingIrpList不为空,优先满足挂起的Irp请求),弹出一个弹窗提示用户(比如某某进程正在往system32释放DLL文件)并倒计时等待用户操作,对弹窗处理的结果通过DeviceIoControl返回给驱动,把结果存放在WaitList里面,结点上的事件就会通知驱动来拿数据,驱动从WaitList拿到结果做出相应操作(阻止/运行某个进程的操作)
  • 驱动拿到用户操作结果做出相应的动作。
  1. 应用层开一个独立线程去异步读出(overlapped)驱动中OperList中的内容

实战

驱动
  • 流程:
    • 1.驱动截获到攻击(在驱动IOCTL分发函数中(IOCTL_XXX_ATTACK)),生成一个WaitList结点插入等待队列,等待R3下发结果,收集信息放到一个结构体中,查看是否有未完成的pendingIRP,如果有,直接将该OperInfo传给R3;否则插入OperList,等待R3来读取
    • 2.应用层通过DipatchRead()读取R0的数据,如果为空,就把当前读Irp放到PendingIrpList中去,并注册一个CommonIrpCancel函数;不为空,直接读取数据
    • 3.在驱动IOCTL分发函数中(IOCTL_SEND_RESULT_TO_R0),将用户结果放入链表WaitList中同WaitID的结构体中,设置EVENT事件,唤醒内核层GetResultFromUser()里的等待事件
    • 4.驱动拿到用户操作结果,摘掉链表中的结点,并获取操作中的结果
    • 5.驱动根据操作结果判断是否允许或者阻止,下发结果
#include <ntddk.h>
#include "Ioctlcmd.h"
#include "main.h"

#define		DEVICE_NAME		L"\\device\\PopupDrv"
#define		LINK_NAME		L"\\dosDevices\\PopupDrv"

LIST_ENTRY g_OperList; ///< 操作链表
ERESOURCE  g_OperListLock; ///< 保护全局资源的读写锁共享锁,允许多人读,一个人写

LIST_ENTRY g_WaitList; ///< 等待链表
ERESOURCE  g_WaitListLock; ///< 保护全局资源的读写锁共享锁,允许多人读,一个人写

LIST_ENTRY g_PendingIrpList; ///< Irp挂起链表
ERESOURCE  g_PendingIrpListLock; ///< 保护全局资源的读写锁共享锁,允许多人读,一个人写

ERESOURCE  g_GetResultFromUserLock; ///< 保护全局资源的读写锁共享锁,允许多人读,一个人写

ULONG g_ulCurrentWaitID = 0; ///< Student 当前攻击事件的ID

/// __stdcall表示 1.参数从右向左压入堆栈 2.函数被调用者负责栈平衡
/// 拿写锁
VOID __stdcall LockWrite(ERESOURCE* lpLock)
{
    KeEnterCriticalRegion();
    ExAcquireResourceExclusiveLite(lpLock, TRUE);
}

/// 释放写锁
VOID __stdcall UnLockWrite(ERESOURCE* lpLock)
{
    ExReleaseResourceLite(lpLock);
    KeLeaveCriticalRegion();
}

/// 拿读锁
VOID __stdcall LockRead(ERESOURCE* lpLock)
{
    KeEnterCriticalRegion();
    ExAcquireResourceSharedLite(lpLock, TRUE);
}

/// 以让等待读优先的方式去拿写锁,StarveWriter,使写进程饥饿
VOID __stdcall LockReadStarveWriter(ERESOURCE* lpLock)
{
    KeEnterCriticalRegion();
    ExAcquireSharedStarveExclusive(lpLock, TRUE);
}

/// 释放读锁
VOID __stdcall UnLockRead(ERESOURCE* lpLock)
{
    ExReleaseResourceLite(lpLock);
    KeLeaveCriticalRegion();
}

/// 初始化锁
VOID __stdcall InitLock(ERESOURCE* lpLock)
{
    ExInitializeResourceLite(lpLock);
}

/// 删除锁
VOID __stdcall DeleteLock(ERESOURCE* lpLock)
{
    ExDeleteResourceLite(lpLock);
}

/// 初始化链表
VOID __stdcall InitList(LIST_ENTRY* list)
{
    InitializeListHead(list);
}

/// 用来取消Irp的例程
VOID CommonIrpCancel(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp
)
{
    KIRQL CancelOldIrql = Irp->CancelIrql;

    IoReleaseCancelSpinLock(DISPATCH_LEVEL);
    KeLowerIrql(CancelOldIrql);

    LockWrite(&g_PendingIrpListLock);
    RemoveEntryList(&Irp->Tail.Overlay.ListEntry);
    UnLockWrite(&g_PendingIrpListLock);

    Irp->IoStatus.Status = STATUS_CANCELLED; //把Irp的执行状态设置成STATUS_CANCELLED
    Irp->IoStatus.Information = 0;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
}

/// 将Irp挂起,并为Irp注册一个CommonIrpCancel
VOID PendingIrpToList(PIRP pIrp, PLIST_ENTRY pIrpList, PDRIVER_CANCEL pfnCancelRoutine)
{
    InsertTailList(pIrpList, &pIrp->Tail.Overlay.ListEntry); //把Irp插入到PendingIrpToList
    IoMarkIrpPending(pIrp); //把Irp标记成pending状态
    /// //为Irp设置一个CancelRoutine回调函数,R3调用CancelIo(handle)会触发CancelRoutine例程(假设Irp一直没有得到满足,直到客户端退出或者系统关机了还没有拿到数据,这时候只能把Irp取消掉,不然系统可能会蓝屏)
    //CancelIo from r3  or IoCancelIrp to call
    IoSetCancelRoutine(pIrp, pfnCancelRoutine);
}

//处理应用层的read()函数
NTSTATUS DispatchRead(
    IN PDEVICE_OBJECT	pDevObj,
    IN PIRP	lpIrp)
{
    NTSTATUS			ntStatus = STATUS_SUCCESS;
    ULONG				ulLength = 0;
    PIO_STACK_LOCATION	lpIrpStack = IoGetCurrentIrpStackLocation(lpIrp);
    OP_INFO* lpOpInfoEntry = NULL;
    LIST_ENTRY* lpOpInfoList = NULL;

    /// 做一个简单的参数判断,读的数据长度
    if (lpIrpStack->Parameters.Read.Length < sizeof(RING3_OP_INFO))
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        ulLength = 0;
        goto Completed;
    }

    LockWrite(&g_OperListLock);
    /// 如果为空,就把当前读Irp放到PendingIrpList中去,并注册一个CommonIrpCancel函数,
    if (IsListEmpty(&g_OperList) == TRUE)
    {
        UnLockWrite(&g_OperListLock);

        LockWrite(&g_PendingIrpListLock);
        PendingIrpToList(lpIrp, &g_PendingIrpList, CommonIrpCancel); //将Irp挂起,并为Irp注册一个CommonIrpCancel
        UnLockWrite(&g_PendingIrpListLock);

        goto Pended;
    }
    /// 不为空,直接读取数据
    lpOpInfoList = g_OperList.Flink;
    lpOpInfoEntry = CONTAINING_RECORD(lpOpInfoList, OP_INFO, m_List); //拿到一个结点
    RemoveEntryList(lpOpInfoList);
    UnLockWrite(&g_OperListLock);

    RtlCopyMemory(lpIrp->AssociatedIrp.SystemBuffer, lpOpInfoEntry, sizeof(RING3_OP_INFO));
    ntStatus = STATUS_SUCCESS;
    ulLength = sizeof(RING3_OP_INFO);

    ExFreePool(lpOpInfoEntry);

Completed:

    lpIrp->IoStatus.Status = ntStatus;
    lpIrp->IoStatus.Information = ulLength;
    IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
    return ntStatus;

Pended:
    return STATUS_PENDING;
}

WAIT_LIST_ENTRY*
FindWaitEntryByID(PLIST_ENTRY pListHead, ULONG ulWaitID)
{
    PLIST_ENTRY	pList = NULL;
    WAIT_LIST_ENTRY* pEntry = NULL;

    for (pList = pListHead->Flink; pList != pListHead; pList = pList->Flink)
    {
        pEntry = CONTAINING_RECORD(pList, WAIT_LIST_ENTRY, m_List);
        if (pEntry->m_ulWaitID == ulWaitID)
        {
            return pEntry;
        }
    }
    return NULL;
}

/// @brief       MakeWaitID 用来创建一个WaitID,用来区分攻击事件;
/// @return      ULONG 返回WaitID,4个字节
ULONG MakeWaitID()
{
    InterlockedIncrement(&g_ulCurrentWaitID); //原子操作,对全局变量+1。
    return g_ulCurrentWaitID;
}

BOOLEAN
CompletePendingIrp(LIST_ENTRY* pIrpListHead, OP_INFO* pOpInfo)
{
    LIST_ENTRY* lpIrpList = NULL;
    PIRP				lpIrp = NULL;
    BOOLEAN				bFound = FALSE;
    BOOLEAN				bReturn = FALSE;

    /// 如果PendingIrpList为空,即当前没有读Irp在等待
    if (IsListEmpty(pIrpListHead) == TRUE)
    {
        return bReturn;
    }
    /// 找到Irp
    for (lpIrpList = pIrpListHead->Flink; lpIrpList != pIrpListHead; lpIrpList = lpIrpList->Flink)
    {
        lpIrp = CONTAINING_RECORD(lpIrpList, IRP, Tail.Overlay.ListEntry); //拿到一个结点
        if (IoSetCancelRoutine(lpIrp, NULL)) //把当前Irp设置成NULL并且returns the previous value of Irp->CancelRoutine(在Irp被取消的时候被调用(程序退出,系统重启等),如果注册了CancelRoutine则是之前被挂起Irp). no Cancel routine, or cancellation in progress, returns NULL.
        {
            RemoveEntryList(lpIrpList);
            bFound = TRUE;
            break;
        }
    }

    if (bFound == FALSE)
    {
        return bReturn;
    }

    /// 把OperList结点的数据copy到pIrp->AssociatedIrp.SystemBuffer,进程就可以拿到数据了
    RtlCopyMemory(lpIrp->AssociatedIrp.SystemBuffer, pOpInfo, sizeof(RING3_OP_INFO)); //只拷贝进程全路径,进程PID,攻击事件ID,

    lpIrp->IoStatus.Information = sizeof(RING3_OP_INFO);
    lpIrp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(lpIrp, IO_NO_INCREMENT);
    bReturn = TRUE;

    return bReturn;
}

R3_RESULT __stdcall GetResultFromUser()
{
    R3_RESULT			NotifyResult = R3Result_Pass;
    BOOLEAN				bSuccess = FALSE;
    NTSTATUS			Status = STATUS_SUCCESS;
    LARGE_INTEGER		WaitTimeOut = { 0 };
    OP_INFO* lpNewOpInfo = NULL;
    WAIT_LIST_ENTRY* lpNewWaitEntry = NULL;
    ULONG_PTR ulPtr = 0;
    LockWrite(&g_GetResultFromUserLock);
    /// 生成一个结点,是个OP_INFO结构体,存放进程的名字,进程的PID,攻击事件的ID,前面三个数据放在LIST_ENTRY之前是为了方便拷贝数据
    lpNewOpInfo = (OP_INFO*)ExAllocatePool(PagedPool, sizeof(OP_INFO));
    /// memset(lpNewOpInfo, 0, sizeof(OP_INFO));
    if (lpNewOpInfo == NULL)
    {
        UnLockWrite(&g_GetResultFromUserLock);
        return NotifyResult;
    }

    /// 设置事件相关的数据,发送给R3,比如进程ID,名字,路径,以及具体操作(创建,修改,删除)等等
    /// 这里只是简单的捕捉了进程的ID或者名字
    ulPtr = (ULONG_PTR)PsGetCurrentProcessId();
    lpNewOpInfo->m_ulProcessID = (ULONG_PTR)ulPtr;

    /// @todo 通过进程pid拿到进程路径,需要完善

    lpNewOpInfo->m_ulWaitID = MakeWaitID(); //生成WatitID,用来区别不同事件的ID
    /// 生成Wait结点,WaitID,WaitEvent(用来同步,当应用层把弹窗结果放在Blocked之后,设置成有信号状态,驱动就通过这个信号,知道应用层已经发结果下来了,就去WaitList上拿结果)
    lpNewWaitEntry = (WAIT_LIST_ENTRY*)ExAllocatePool(NonPagedPool, sizeof(WAIT_LIST_ENTRY));
    if (lpNewWaitEntry == NULL)
    {
        goto End;
    }

    lpNewWaitEntry->m_ulWaitID = lpNewOpInfo->m_ulWaitID;
    KeInitializeEvent(&lpNewWaitEntry->m_ulWaitEvent, SynchronizationEvent, FALSE);

    // 插入等待队列,等待R3下发结果
    LockWrite(&g_WaitListLock);
    InsertTailList(&g_WaitList, &lpNewWaitEntry->m_List);
    UnLockWrite(&g_WaitListLock);

    LockWrite(&g_PendingIrpListLock);
    bSuccess = CompletePendingIrp(&g_PendingIrpList, lpNewOpInfo);//查看是否有未完成的pendingIRP,直接将该OperInfo传给R3
    UnLockWrite(&g_PendingIrpListLock);

    if (bSuccess == FALSE)	//完成pending irp失败(当前没有读Irp在等待),将lpNewOpInfo插入Operlist
    {
        LockWrite(&g_OperListLock);
        InsertTailList(&g_OperList, &lpNewOpInfo->m_List); //插入OperList,等待R3来读取
        UnLockWrite(&g_OperListLock);

        lpNewOpInfo = NULL;
    }

    // 等40秒,环3是30秒超时
    WaitTimeOut.QuadPart = -40 * 10000000;
    Status = KeWaitForSingleObject(&lpNewWaitEntry->m_ulWaitEvent,
        Executive, KernelMode, FALSE, &WaitTimeOut);//等待R3下发允许或阻止操作

    LockWrite(&g_WaitListLock);
    RemoveEntryList(&lpNewWaitEntry->m_List); //把当前WaitList结点摘除
    UnLockWrite(&g_WaitListLock);

    if (Status != STATUS_TIMEOUT)
    {
        if (lpNewWaitEntry->m_bBlocked == TRUE)
        {
            NotifyResult = R3Result_Block; ///< 阻止
        }
        else
        {
            NotifyResult = R3Result_Pass; ///< 放行
        }
    }
    else
    {
        NotifyResult = R3Result_DefaultNon;
    }

End:
    if (lpNewWaitEntry != NULL)
    {
        ExFreePool(lpNewWaitEntry);
    }
    if (lpNewOpInfo != NULL)
    {
        ExFreePool(lpNewOpInfo);
    }
    UnLockWrite(&g_GetResultFromUserLock);
    return NotifyResult;
}

//处理应用层的DeviceIoControl()
NTSTATUS DispatchControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
)
{
    PIO_STACK_LOCATION      	lpIrpStack = NULL;
    PVOID                   	inputBuffer = NULL;
    PVOID                   	outputBuffer = NULL;
    ULONG                   	inputBufferLength = 0;
    ULONG                   	outputBufferLength = 0;
    ULONG                   	ioControlCode = 0;
    NTSTATUS		     		ntStatus = STATUS_SUCCESS;

    ntStatus = Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    //获取当前IRP堆栈位置
    lpIrpStack = IoGetCurrentIrpStackLocation(Irp);
    //获得输入缓冲和长度
    inputBuffer = Irp->AssociatedIrp.SystemBuffer;
    inputBufferLength = lpIrpStack->Parameters.DeviceIoControl.InputBufferLength;
    //获得输出缓冲和长度
    outputBuffer = Irp->AssociatedIrp.SystemBuffer;
    outputBufferLength = lpIrpStack->Parameters.DeviceIoControl.OutputBufferLength;
    //获取控制码
    ioControlCode = lpIrpStack->Parameters.DeviceIoControl.IoControlCode;

    switch (ioControlCode)
    {
    case IOCTL_SEND_RESULT_TO_R0://R3向内核传递弹窗结果,将对应的WaitID事件设置成用户选择结果
    {
        RING3_REPLY* lpReply = NULL;
        WAIT_LIST_ENTRY* lpWaitEntry = NULL;

        if (lpIrpStack->Parameters.DeviceIoControl.InputBufferLength < sizeof(RING3_REPLY))
        {
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
            break;
        }
        /// RING3_REPLY结构体包含WaitID和Blocked这两个成员
        lpReply = (RING3_REPLY*)Irp->AssociatedIrp.SystemBuffer;

        LockWrite(&g_WaitListLock);
        lpWaitEntry = FindWaitEntryByID(&g_WaitList, lpReply->m_ulWaitID);//根据WaitID,找到对应的拦截事件

        if (lpWaitEntry != NULL)
        {
            lpWaitEntry->m_bBlocked = lpReply->m_ulBlocked; //将对应的WaitList结点设置成用户选择结果
            KeSetEvent(&lpWaitEntry->m_ulWaitEvent, 0, FALSE);//设置EVENT事件,唤醒内核层GetResultFromUser()里的等待事件
        }

        UnLockWrite(&g_WaitListLock);

        Irp->IoStatus.Information = 0;
        ntStatus = Irp->IoStatus.Status = STATUS_SUCCESS;
    }
    break;

    case IOCTL_XXX_ATTACK://攻击拦截模仿
    {
        R3_RESULT notifyResult = R3Result_DefaultNon;

        //LockWrite(&g_GetResultFromUserLock); //最好是在函数内部。除非你在任何调用这个函数的地方都加锁
        //KeEnterCriticalRegion(); //改成串行
        notifyResult = GetResultFromUser();//这里最长会等待40s,收集数据封装成一个结构体结点,放在OperList或者满足PendingIrpList中等待的Irp,从R3获得弹框结果,是阻止还是放过
        //KeLeaveCriticalRegion();
        //UnLockWrite(&g_GetResultFromUserLock);

        if (notifyResult == R3Result_Block)
        {
            DbgPrint("阻止\n");
            *(ULONG*)outputBuffer = 0;
            ntStatus = STATUS_SUCCESS;
        }
        else if (notifyResult == R3Result_Pass)
        {
            DbgPrint("允许\n");
            *(ULONG*)outputBuffer = 1;
            ntStatus = STATUS_SUCCESS;
        }
        else
        {
            DbgPrint("超时允许\n");
            *(ULONG*)outputBuffer = 1;
            ntStatus = STATUS_SUCCESS;
        }
    }
    Irp->IoStatus.Information = sizeof(ULONG);
    Irp->IoStatus.Status = ntStatus;
    break;

    default:
        break;
    }

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return ntStatus;
}

//驱动Unload()函数
VOID DriverUnload(
    IN PDRIVER_OBJECT	pDriverObject)
{
    UNICODE_STRING         deviceLink = { 0 };

    RtlInitUnicodeString(&deviceLink, LINK_NAME);
    IoDeleteSymbolicLink(&deviceLink);
    IoDeleteDevice(pDriverObject->DeviceObject);
    DeleteLock(&g_GetResultFromUserLock);
    DeleteLock(&g_OperListLock);
    DeleteLock(&g_WaitListLock);
    DeleteLock(&g_PendingIrpListLock);
    return;
}

//处理应用层的create()函数
NTSTATUS DispatchCreate(
    IN PDEVICE_OBJECT	pDevObj,
    IN PIRP	pIrp)
{
    //设置IO状态信息
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    //完成IRP操作,不向下层驱动发送
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

//处理应用层的close()函数
NTSTATUS DispatchClose(
    IN PDEVICE_OBJECT	pDevObj,
    IN PIRP	pIrp)
{
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

//驱动程序入口,完成各种初始化工作,创建设备对象
NTSTATUS DriverEntry(
    IN PDRIVER_OBJECT pDriverObject,
    IN PUNICODE_STRING pRegistryPath)
{
    NTSTATUS 		status = STATUS_SUCCESS;
    PDEVICE_OBJECT 	pDevObj = NULL;
    UNICODE_STRING 	uDevName = { 0 };
    UNICODE_STRING 	uLinkName = { 0 };
    DbgPrint("Driver Load begin!\n");

    /// 初始化锁
    InitLock(&g_OperListLock);
    InitLock(&g_WaitListLock);
    InitLock(&g_PendingIrpListLock);
    InitLock(&g_GetResultFromUserLock);

    /// 初始化链表
    InitList(&g_OperList);
    InitList(&g_WaitList);
    InitList(&g_PendingIrpList);

    //初始化各个例程

    pDriverObject->MajorFunction[IRP_MJ_CREATE] =
        DispatchCreate;
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] =
        DispatchClose;
    pDriverObject->MajorFunction[IRP_MJ_READ] =
        DispatchRead;//read operlist data
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
        DispatchControl;//get r3 result&attack
    pDriverObject->DriverUnload =
        DriverUnload;

    RtlInitUnicodeString(&uDevName, DEVICE_NAME);
    //创建驱动设备
    status = IoCreateDevice(pDriverObject,
        0,//sizeof(DEVICE_EXTENSION)
        &uDevName,
        FILE_DEVICE_UNKNOWN,
        0, FALSE,
        &pDevObj);
    if (!NT_SUCCESS(status))
    {
        DbgPrint("IoCreateDevice Failed:%x\n", status);
        return status;
    }

    pDevObj->Flags |= DO_BUFFERED_IO;
    RtlInitUnicodeString(&uLinkName, LINK_NAME);
    //创建符号链接
    status = IoCreateSymbolicLink(&uLinkName, &uDevName);
    if (!NT_SUCCESS(status))
    {
        //STATUS_INSUFFICIENT_RESOURCES 	资源不足
        //STATUS_OBJECT_NAME_EXISTS 		指定对象名存在
        //STATUS_OBJECT_NAME_COLLISION 	    对象名有冲突
        DbgPrint("IoCreateSymbolicLink Failed:%x\n", status);
        IoDeleteDevice(pDevObj);
        return status;
    }
    DbgPrint("Driver Load success!\n");
    return status;
}
应用
  • 流程
    • 1.加载驱动
    • 2.打开设备
    • 3.新建一个读线程,如果有数据读出来弹窗,如果没有数据可读则等待
// PopupClientDlg.cpp : implementation file
//

#include "stdafx.h"
#include "PopupClient.h"
#include "PopupClientDlg.h"

#include "ioctlcmd.h"
#include <shlwapi.h>
#include "PopupDlg.h"

#pragma comment(lib, "shlwapi.lib")

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define DRIVER_NAME _T("PopupDrv")
#define DRIVER_PATH _T(".\\PopupDrv.sys")

HANDLE gh_Device = INVALID_HANDLE_VALUE;

CWinThread *g_hReadThread = NULL;
BOOL g_bToExitThread = FALSE;
HANDLE g_hOverlappedEvent = NULL;

BOOL LoadDriver(TCHAR *lpszDriverName, TCHAR *lpszDriverPath)
{
	TCHAR szDriverImagePath[256] = {0};
	//得到完整的驱动路径
	GetFullPathName(lpszDriverPath, 256, szDriverImagePath, NULL);

	BOOL bRet = FALSE;

	SC_HANDLE hServiceMgr = NULL; // SCM管理器的句柄
	SC_HANDLE hServiceDDK = NULL; // NT驱动程序的服务句柄

	//打开服务控制管理器
	hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

	if (hServiceMgr == NULL)
	{
		// OpenSCManager失败
		// printf( "OpenSCManager() Failed %d ! \n", GetLastError() );
		bRet = FALSE;
		goto BeforeLeave;
	}
	else
	{
		OpenSCManager成功
		printf("OpenSCManager() ok ! \n");
	}

	//创建驱动所对应的服务
	hServiceDDK = CreateService(hServiceMgr,
								lpszDriverName,		   //驱动程序的在注册表中的名字
								lpszDriverName,		   // 注册表驱动程序的 DisplayName 值
								SERVICE_ALL_ACCESS,	   // 加载驱动程序的访问权限
								SERVICE_KERNEL_DRIVER, // 表示加载的服务是驱动程序
								SERVICE_DEMAND_START,  // 注册表驱动程序的 Start 值
								SERVICE_ERROR_IGNORE,  // 注册表驱动程序的 ErrorControl 值
								szDriverImagePath,	   // 注册表驱动程序的 ImagePath 值
								NULL,				   // GroupOrder HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList
								NULL,
								NULL,
								NULL,
								NULL);

	DWORD dwRtn;
	//判断服务是否失败
	if (hServiceDDK == NULL)
	{
		dwRtn = GetLastError();
		if (dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_EXISTS)
		{
			//由于其他原因创建服务失败
			// printf( "CrateService() Failed %d ! \n", dwRtn );
			bRet = FALSE;
			goto BeforeLeave;
		}
		else
		{
			//服务创建失败,是由于服务已经创立过
			printf("CrateService() Faild Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! \n");
		}

		// 驱动程序已经加载,只需要打开
		hServiceDDK = OpenService(hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS);
		if (hServiceDDK == NULL)
		{
			//如果打开服务也失败,则意味错误
			dwRtn = GetLastError();
			// printf( "OpenService() Failed %d ! \n", dwRtn );
			bRet = FALSE;
			goto BeforeLeave;
		}
		else
		{
			// printf( "OpenService() ok ! \n" );
		}
	}
	else
	{
		// printf( "CrateService() ok ! \n" );
	}

	//开启此项服务
	bRet = StartService(hServiceDDK, NULL, NULL);
	if (!bRet)
	{
		DWORD dwRtn = GetLastError();
		if (dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_ALREADY_RUNNING)
		{
			// printf( "StartService() Failed %d ! \n", dwRtn );
			bRet = FALSE;
			goto BeforeLeave;
		}
		else
		{
			if (dwRtn == ERROR_IO_PENDING)
			{
				//设备被挂住
				// printf( "StartService() Failed ERROR_IO_PENDING ! \n");
				bRet = FALSE;
				goto BeforeLeave;
			}
			else
			{
				//服务已经开启
				// printf( "StartService() Failed ERROR_SERVICE_ALREADY_RUNNING ! \n");
				bRet = TRUE;
				goto BeforeLeave;
			}
		}
	}
	bRet = TRUE;
//离开前关闭句柄
BeforeLeave:
	if (hServiceDDK)
	{
		CloseServiceHandle(hServiceDDK);
	}
	if (hServiceMgr)
	{
		CloseServiceHandle(hServiceMgr);
	}
	return bRet;
}

//卸载驱动程序
BOOL UnloadDriver(TCHAR *szSvrName)
{
	BOOL bRet = FALSE;
	SC_HANDLE hServiceMgr = NULL; // SCM管理器的句柄
	SC_HANDLE hServiceDDK = NULL; // NT驱动程序的服务句柄
	SERVICE_STATUS SvrSta;
	//打开SCM管理器
	hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hServiceMgr == NULL)
	{
		//带开SCM管理器失败
		printf("OpenSCManager() Failed %d ! \n", GetLastError());
		bRet = FALSE;
		goto BeforeLeave;
	}
	else
	{
		//带开SCM管理器失败成功
		printf("OpenSCManager() ok ! \n");
	}
	//打开驱动所对应的服务
	hServiceDDK = OpenService(hServiceMgr, szSvrName, SERVICE_ALL_ACCESS);

	if (hServiceDDK == NULL)
	{
		//打开驱动所对应的服务失败
		printf("OpenService() Failed %d ! \n", GetLastError());
		bRet = FALSE;
		goto BeforeLeave;
	}
	else
	{
		printf("OpenService() ok ! \n");
	}
	//停止驱动程序,如果停止失败,只有重新启动才能,再动态加载.
	if (!ControlService(hServiceDDK, SERVICE_CONTROL_STOP, &SvrSta))
	{
		printf("ControlService() Failed %d !\n", GetLastError());
	}
	else
	{
		//打开驱动所对应的失败
		printf("ControlService() ok !\n");
	}
	//动态卸载驱动程序.
	if (!DeleteService(hServiceDDK))
	{
		//卸载失败
		printf("DeleteSrevice() Failed %d !\n", GetLastError());
	}
	else
	{
		//卸载成功
		printf("DelServer:eleteSrevice() ok !\n");
	}
	bRet = TRUE;
BeforeLeave:
	//离开前关闭打开的句柄
	if (hServiceDDK)
	{
		CloseServiceHandle(hServiceDDK);
	}
	if (hServiceMgr)
	{
		CloseServiceHandle(hServiceMgr);
	}
	return bRet;
}

HANDLE OpenDevice()
{
	//测试驱动程序
	HANDLE hDevice = CreateFile(_T("\\\\.\\PopupDrv"),
								GENERIC_WRITE | GENERIC_READ,
								0,
								NULL,
								OPEN_EXISTING,
								0, //这里要传入FILE_FLAG_OVERAPPED才是真正异步读
								NULL);
	if (hDevice != INVALID_HANDLE_VALUE)
	{
		printf("Create Device ok ! \n");
	}
	else
	{
		printf("Create Device faild %d ! \n", GetLastError());
		return NULL;
	}

	return hDevice;
}

typedef struct _R3_REPLY
{
	ULONG m_ulWaitID;
	ULONG m_ulBlocked;
} R3_REPLY;

typedef struct _OP_INFO
{
	WCHAR m_ProcessName[MAX_PATH];
	DWORD m_ulProcessID;
	ULONG m_ulWaitID;

} OP_INFO, *POP_INFO;

VOID SendResultToR0(ULONG ulWaitID, BOOL bBlocked)
{
	if (gh_Device == INVALID_HANDLE_VALUE)
	{
		return;
	}

	R3_REPLY R3Reply;
	R3Reply.m_ulWaitID = ulWaitID;
	R3Reply.m_ulBlocked = bBlocked;

	ULONG ulRet = 0;
	::DeviceIoControl(gh_Device, IOCTL_SEND_RESULT_TO_R0, &R3Reply, sizeof(R3_REPLY), NULL, 0, &ulRet, NULL); //把R3弹窗处理的结果返回给R0

	return;
}
BOOL HandleData(OP_INFO *pOpInfoData)
{

	CPopupDlg dlg;

	dlg.SetProcess(pOpInfoData->m_ProcessName); //进程名字
	dlg.SetDetail(_T("有进程正在非法攻击"));	//弹窗

	dlg.DoModal(); //倒计时30s

	if (dlg.m_bAllow == 0) //允许还是阻止
	{
		return FALSE;
	}

	return TRUE;
}

void PopupInfoToUser(OP_INFO *pOpInfo, int Num)
{
	OP_INFO *currData = pOpInfo;
	CString szNum;

	for (int i = 0; i < Num; i++)
	{
		BOOL bResult = HandleData(currData); // 此处可以弹框获得用户的结果
		if (bResult)
		{
			SendResultToR0(pOpInfo->m_ulWaitID, TRUE); //把弹窗结果返回给Ro0		}
			else
			{
				SendResultToR0(pOpInfo->m_ulWaitID, FALSE);
			}
			currData++;
		}
	}

	UINT ReadThreadProc(LPVOID lpContext)
	{
		OVERLAPPED Overlapped;

		g_hOverlappedEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

		if (g_hOverlappedEvent == NULL || gh_Device == INVALID_HANDLE_VALUE)
		{
			return -1;
		}

		memset(&Overlapped, 0, sizeof(OVERLAPPED));

		ULONG ulReturn = 0;
		ULONG ulBytesReturn = 0;

		OP_INFO OpInfo;
		Overlapped.hEvent = g_hOverlappedEvent;

		::SleepEx(1, TRUE);

		while (TRUE)
		{
			ulReturn = ReadFile(gh_Device, &OpInfo, sizeof(OP_INFO), &ulBytesReturn, &Overlapped); //异步读.该函数会立即返回

			if (g_bToExitThread == TRUE)
			{
				break;
			}

			if (ulReturn == 0) //没有读到数据
			{
				if (GetLastError() == ERROR_IO_PENDING)
				{
					ULONG ulApiReturn = WaitForSingleObject(Overlapped.hEvent, INFINITE); //在这个事件上进行等待

					if (ulApiReturn == WAIT_FAILED)
					{
						break;
					}
					if (g_bToExitThread == TRUE)
					{
						break;
					}
				}
				else
				{
					continue;
				}
			}
			if (ulBytesReturn == sizeof(OP_INFO))
			{
				PopupInfoToUser(&OpInfo, 1); //弹窗
			}
		}

		return 0;
	}

	/
	// CAboutDlg dialog used for App About

	class CAboutDlg : public CDialog
	{
	public:
		CAboutDlg();

		// Dialog Data
		//{{AFX_DATA(CAboutDlg)
		enum
		{
			IDD = IDD_ABOUTBOX
		};
		//}}AFX_DATA

		// ClassWizard generated virtual function overrides
		//{{AFX_VIRTUAL(CAboutDlg)
	protected:
		virtual void DoDataExchange(CDataExchange *pDX); // DDX/DDV support
														 //}}AFX_VIRTUAL

		// Implementation
	protected:
		//{{AFX_MSG(CAboutDlg)
		//}}AFX_MSG
		DECLARE_MESSAGE_MAP()
	};

	CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
	{
		//{{AFX_DATA_INIT(CAboutDlg)
		//}}AFX_DATA_INIT
	}

	void CAboutDlg::DoDataExchange(CDataExchange * pDX)
	{
		CDialog::DoDataExchange(pDX);
		//{{AFX_DATA_MAP(CAboutDlg)
		//}}AFX_DATA_MAP
	}

	BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
	// No message handlers
	//}}AFX_MSG_MAP
	END_MESSAGE_MAP()

	/
	// CPopupClientDlg dialog

	CPopupClientDlg::CPopupClientDlg(CWnd * pParent /*=NULL*/)
		: CDialog(CPopupClientDlg::IDD, pParent)
	{
		//{{AFX_DATA_INIT(CPopupClientDlg)
		// NOTE: the ClassWizard will add member initialization here
		//}}AFX_DATA_INIT
		// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
		m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	}

	void CPopupClientDlg::DoDataExchange(CDataExchange * pDX)
	{
		CDialog::DoDataExchange(pDX);
		//{{AFX_DATA_MAP(CPopupClientDlg)
		// NOTE: the ClassWizard will add DDX and DDV calls here
		//}}AFX_DATA_MAP
	}

	BEGIN_MESSAGE_MAP(CPopupClientDlg, CDialog)
	//{{AFX_MSG_MAP(CPopupClientDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_STOP, OnButtonStop)
	ON_WM_CLOSE()
	ON_COMMAND(ID_MENU_EXIT, OnMenuExit)
	ON_COMMAND(ID_MENU_RESTORE, OnMenuRestore)
	ON_WM_DESTROY()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_ICON_NOTIFY, OnTrayNotification)
	ON_BN_CLICKED(IDOK, &CPopupClientDlg::OnBnClickedOk)
	END_MESSAGE_MAP()

	/
	// CPopupClientDlg message handlers

	BOOL CPopupClientDlg::OnInitDialog()
	{
		CDialog::OnInitDialog();

		::BringWindowToTop(m_hWnd);
		::SetWindowPos(
			m_hWnd,
			HWND_TOPMOST,
			0, 0, 0, 0,
			SWP_NOMOVE | SWP_NOSIZE);

		// Add "About..." menu item to system menu.

		// IDM_ABOUTBOX must be in the system command range.
		ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
		ASSERT(IDM_ABOUTBOX < 0xF000);

		CMenu *pSysMenu = GetSystemMenu(FALSE);
		if (pSysMenu != NULL)
		{
			CString strAboutMenu;
			strAboutMenu.LoadString(IDS_ABOUTBOX);
			if (!strAboutMenu.IsEmpty())
			{
				pSysMenu->AppendMenu(MF_SEPARATOR);
				pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
			}
		}

		// Set the icon for this dialog.  The framework does this automatically
		//  when the application's main window is not a dialog
		SetIcon(m_hIcon, TRUE);	 // Set big icon
		SetIcon(m_hIcon, FALSE); // Set small icon

		// TODO: Add extra initialization here

		NOTIFYICONDATA m_tnid;

		m_tnid.cbSize = sizeof(NOTIFYICONDATA);			  //设置结构大小//
		m_tnid.hWnd = this->m_hWnd;						  //设置图标对应的窗口
		m_tnid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; //图标属性
		m_tnid.uCallbackMessage = WM_ICON_NOTIFY;		  //应用程序定义的回调消息ID

		CString szToolTip;
		szToolTip = _T("HIPS -- 客户端程序");
		_tcscpy(m_tnid.szTip, szToolTip); //帮助信息
		m_tnid.uID = IDR_MAINFRAME;		  //应用程序图标
		m_tnid.hIcon = m_hIcon;			  //图标句柄
		PNOTIFYICONDATA m_ptnid = &m_tnid;
		::Shell_NotifyIcon(NIM_ADD, m_ptnid); //增加图标到系统盘

		GetDlgItem(IDOK)->EnableWindow(TRUE);
		GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(FALSE);

		return TRUE; // return TRUE  unless you set the focus to a control
	}

	void CPopupClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
	{
		if ((nID & 0xFFF0) == IDM_ABOUTBOX)
		{
			CAboutDlg dlgAbout;
			dlgAbout.DoModal();
		}
		else
		{
			CDialog::OnSysCommand(nID, lParam);
		}

		if (nID == SC_MINIMIZE)
		{
			ShowWindow(FALSE); //隐藏窗口
		}
	}

	// If you add a minimize button to your dialog, you will need the code below
	//  to draw the icon.  For MFC applications using the document/view model,
	//  this is automatically done for you by the framework.

	void CPopupClientDlg::OnPaint()
	{
		if (IsIconic())
		{
			CPaintDC dc(this); // device context for painting

			SendMessage(WM_ICONERASEBKGND, (WPARAM)dc.GetSafeHdc(), 0);

			// Center icon in client rectangle
			int cxIcon = GetSystemMetrics(SM_CXICON);
			int cyIcon = GetSystemMetrics(SM_CYICON);
			CRect rect;
			GetClientRect(&rect);
			int x = (rect.Width() - cxIcon + 1) / 2;
			int y = (rect.Height() - cyIcon + 1) / 2;

			// Draw the icon
			dc.DrawIcon(x, y, m_hIcon);
		}
		else
		{
			CDialog::OnPaint();
		}
	}

	// The system calls this to obtain the cursor to display while the user drags
	//  the minimized window.
	HCURSOR CPopupClientDlg::OnQueryDragIcon()
	{
		return (HCURSOR)m_hIcon;
	}

	void CPopupClientDlg::OnButtonStop()
	{
		// TODO: Add your control notification handler code here
		g_bToExitThread = TRUE;
		if (g_hOverlappedEvent != NULL)
		{
			ResetEvent(g_hOverlappedEvent);

			if (g_hReadThread != NULL)
			{
				if (WaitForSingleObject(g_hReadThread->m_hThread, 3000) == WAIT_TIMEOUT)
				{
					TerminateThread(g_hReadThread->m_hThread, 0);
				}
				delete g_hReadThread;
				g_hReadThread = NULL;
			}

			CloseHandle(g_hOverlappedEvent);
			g_hOverlappedEvent = NULL;
		}
		if (gh_Device != INVALID_HANDLE_VALUE)
		{
			CloseHandle(gh_Device);
			gh_Device = INVALID_HANDLE_VALUE;
		}
		// UnloadDriver(DRIVER_NAME);

		GetDlgItem(IDOK)->EnableWindow(TRUE);
		GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(FALSE);
	}

	void CPopupClientDlg::OnOK()
	{
		// TODO: Add extra validation here

		DWORD dwThreadID = 0;
		g_bToExitThread = FALSE;
		//加载驱动
		BOOL bRet = LoadDriver(DRIVER_NAME, DRIVER_PATH);
		if (!bRet)
		{
			MessageBox(_T("加载驱动失败"), _T("Error"), MB_OK);
			return;
		}

		gh_Device = OpenDevice();
		if (gh_Device == NULL)
		{
			MessageBox(_T("打开设备失败"), _T("Error"), MB_OK);
			return;
		}

		g_hReadThread = AfxBeginThread(ReadThreadProc, this); //创建一个读线程

		g_hReadThread->SuspendThread();
		g_hReadThread->m_bAutoDelete = FALSE;
		g_hReadThread->ResumeThread();

		if (g_hReadThread == NULL)
		{
			CloseHandle(gh_Device);
			gh_Device = INVALID_HANDLE_VALUE;
			UnloadDriver(DRIVER_NAME);
			return;
		}
		GetDlgItem(IDOK)->EnableWindow(FALSE);
		GetDlgItem(IDC_BUTTON_STOP)->EnableWindow(TRUE);
	}

	LRESULT CPopupClientDlg::OnTrayNotification(WPARAM wParam, LPARAM lParam)
	{
		switch (lParam)
		{
		case WM_LBUTTONDOWN:
		{
			AfxGetApp()->m_pMainWnd->ShowWindow(SW_SHOWNORMAL);
			SetForegroundWindow();
			break;
		}
		case WM_RBUTTONUP:
		{
			POINT point;
			HMENU hMenu, hSubMenu;
			GetCursorPos(&point); //鼠标位置
			hMenu = LoadMenu(NULL,
							 MAKEINTRESOURCE(IDR_MENU_TRAY)); // 加载菜单
			hSubMenu = GetSubMenu(hMenu, 0);				  //得到子菜单(因为弹出式菜单是子菜单)
			SetMenuDefaultItem(hSubMenu, -1, FALSE);		  //设置缺省菜单项,-1为无缺省项
			SetForegroundWindow();							  // 激活窗口并置前

			TrackPopupMenu(hSubMenu, 0,
						   point.x, point.y, 0, m_hWnd, NULL);
		}
		}
		return 1;
	}

	void CPopupClientDlg::OnClose()
	{

		NOTIFYICONDATA nd = {0};

		nd.cbSize = sizeof(NOTIFYICONDATA);
		nd.hWnd = m_hWnd;
		nd.uID = IDR_MAINFRAME;
		nd.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
		nd.uCallbackMessage = WM_ICON_NOTIFY;
		nd.hIcon = m_hIcon;

		Shell_NotifyIcon(NIM_DELETE, &nd);

		CDialog::OnCancel();
	}

	void CPopupClientDlg::OnMenuExit()
	{
		OnClose();
	}

	void CPopupClientDlg::OnMenuRestore()
	{
		ShowWindow(SW_SHOWNORMAL);
		SetForegroundWindow();
	}

	void CPopupClientDlg::OnDestroy()
	{
		CDialog::OnDestroy();
	}

	void CPopupClientDlg::OnBnClickedOk()
	{
		// TODO: Add your control notification handler code here
		OnOK();
	}

模拟攻击者
  • 向驱动发送一个IOCTL_XXX_ATTACK控制码
// attacker.cpp : Defines the entry point for the console application.
//
#include "StdAfx.h"
#include <windows.h>
#include <winsvc.h>
#include <conio.h>
#include <stdio.h>
#include <winioctl.h>

#define DRIVER_NAME "PopupDrv"
#define DRIVER_PATH ".\\PopupDrv.sys"

#define IOCTL_BASE 0x800
#define MY_CTL_CODE(i) \
	CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTL_BASE + i, METHOD_BUFFERED, FILE_ANY_ACCESS)

#define IOCTL_XXX_ATTACK MY_CTL_CODE(2)

//装载NT驱动程序
BOOL LoadDriver(char *lpszDriverName, char *lpszDriverPath)
{
	char szDriverImagePath[256] = {0};
	//得到完整的驱动路径
	GetFullPathName(lpszDriverPath, 256, szDriverImagePath, NULL);

	BOOL bRet = FALSE;

	SC_HANDLE hServiceMgr = NULL; // SCM管理器的句柄
	SC_HANDLE hServiceDDK = NULL; // NT驱动程序的服务句柄

	//打开服务控制管理器
	hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

	if (hServiceMgr == NULL)
	{
		// OpenSCManager失败
		printf("OpenSCManager() Failed %d ! \n", GetLastError());
		bRet = FALSE;
		goto BeforeLeave;
	}
	else
	{
		OpenSCManager成功
		printf("OpenSCManager() ok ! \n");
	}

	//创建驱动所对应的服务
	hServiceDDK = CreateService(hServiceMgr,
								lpszDriverName,		   //驱动程序的在注册表中的名字
								lpszDriverName,		   // 注册表驱动程序的 DisplayName 值
								SERVICE_ALL_ACCESS,	   // 加载驱动程序的访问权限
								SERVICE_KERNEL_DRIVER, // 表示加载的服务是驱动程序
								SERVICE_DEMAND_START,  // 注册表驱动程序的 Start 值
								SERVICE_ERROR_IGNORE,  // 注册表驱动程序的 ErrorControl 值
								szDriverImagePath,	   // 注册表驱动程序的 ImagePath 值
								NULL,				   // GroupOrder HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList
								NULL,
								NULL,
								NULL,
								NULL);

	DWORD dwRtn;
	//判断服务是否失败
	if (hServiceDDK == NULL)
	{
		dwRtn = GetLastError();
		if (dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_EXISTS)
		{
			//由于其他原因创建服务失败
			printf("CrateService() Failed %d ! \n", dwRtn);
			bRet = FALSE;
			goto BeforeLeave;
		}
		else
		{
			//服务创建失败,是由于服务已经创立过
			printf("CrateService() Faild Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! \n");
		}

		// 驱动程序已经加载,只需要打开
		hServiceDDK = OpenService(hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS);
		if (hServiceDDK == NULL)
		{
			//如果打开服务也失败,则意味错误
			dwRtn = GetLastError();
			printf("OpenService() Failed %d ! \n", dwRtn);
			bRet = FALSE;
			goto BeforeLeave;
		}
		else
		{
			printf("OpenService() ok ! \n");
		}
	}
	else
	{
		printf("CrateService() ok ! \n");
	}

	//开启此项服务
	bRet = StartService(hServiceDDK, NULL, NULL);
	if (!bRet)
	{
		DWORD dwRtn = GetLastError();
		if (dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_ALREADY_RUNNING)
		{
			printf("StartService() Failed %d ! \n", dwRtn);
			bRet = FALSE;
			goto BeforeLeave;
		}
		else
		{
			if (dwRtn == ERROR_IO_PENDING)
			{
				//设备被挂住
				printf("StartService() Failed ERROR_IO_PENDING ! \n");
				bRet = FALSE;
				goto BeforeLeave;
			}
			else
			{
				//服务已经开启
				printf("StartService() Failed ERROR_SERVICE_ALREADY_RUNNING ! \n");
				bRet = TRUE;
				goto BeforeLeave;
			}
		}
	}
	bRet = TRUE;
//离开前关闭句柄
BeforeLeave:
	if (hServiceDDK)
	{
		CloseServiceHandle(hServiceDDK);
	}
	if (hServiceMgr)
	{
		CloseServiceHandle(hServiceMgr);
	}
	return bRet;
}

//卸载驱动程序
BOOL UnloadDriver(char *szSvrName)
{
	BOOL bRet = FALSE;
	SC_HANDLE hServiceMgr = NULL; // SCM管理器的句柄
	SC_HANDLE hServiceDDK = NULL; // NT驱动程序的服务句柄
	SERVICE_STATUS SvrSta;
	//打开SCM管理器
	hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hServiceMgr == NULL)
	{
		//带开SCM管理器失败
		printf("OpenSCManager() Failed %d ! \n", GetLastError());
		bRet = FALSE;
		goto BeforeLeave;
	}
	else
	{
		//带开SCM管理器失败成功
		printf("OpenSCManager() ok ! \n");
	}
	//打开驱动所对应的服务
	hServiceDDK = OpenService(hServiceMgr, szSvrName, SERVICE_ALL_ACCESS);

	if (hServiceDDK == NULL)
	{
		//打开驱动所对应的服务失败
		printf("OpenService() Failed %d ! \n", GetLastError());
		bRet = FALSE;
		goto BeforeLeave;
	}
	else
	{
		printf("OpenService() ok ! \n");
	}
	//停止驱动程序,如果停止失败,只有重新启动才能,再动态加载.
	if (!ControlService(hServiceDDK, SERVICE_CONTROL_STOP, &SvrSta))
	{
		printf("ControlService() Failed %d !\n", GetLastError());
	}
	else
	{
		//打开驱动所对应的失败
		printf("ControlService() ok !\n");
	}
	//动态卸载驱动程序.
	if (!DeleteService(hServiceDDK))
	{
		//卸载失败
		printf("DeleteSrevice() Failed %d !\n", GetLastError());
	}
	else
	{
		//卸载成功
		printf("DelServer:DeleteSrevice() ok !\n");
	}
	bRet = TRUE;
BeforeLeave:
	//离开前关闭打开的句柄
	if (hServiceDDK)
	{
		CloseServiceHandle(hServiceDDK);
	}
	if (hServiceMgr)
	{
		CloseServiceHandle(hServiceMgr);
	}
	return bRet;
}

void TestDriver()
{
	//测试驱动程序
	HANDLE hDevice = CreateFile("\\\\.\\PopupDrv",
								GENERIC_WRITE | GENERIC_READ,
								0,
								NULL,
								OPEN_EXISTING,
								0,
								NULL);
	if (hDevice != INVALID_HANDLE_VALUE)
	{
		printf("Create Device ok ! \n");
	}
	else
	{
		printf("Create Device failed %d ! \n", GetLastError());
		return;
	}

	CHAR bufInput[1024] = "Hello, world";
	DWORD dwAttackRes = 0;
	DWORD dwRet = 0;
	printf("Begin to attack\n");
	/// 得到的设备对象,通过DeviceIoControl发送一个控制码,模拟攻击
	DeviceIoControl(hDevice, IOCTL_XXX_ATTACK, bufInput, sizeof(bufInput), &dwAttackRes, sizeof(dwAttackRes), &dwRet, NULL);
	if (dwAttackRes == 0)
	{
		printf("Attack denied\n");
	}
	else
	{
		printf("Attack OK\n");
	}

	CloseHandle(hDevice);
}

int main(int argc, char *argv[])
{
	//加载驱动
	BOOL bRet = LoadDriver(DRIVER_NAME, DRIVER_PATH);
	if (!bRet)
	{
		printf("LoadNTDriver error\n");
		return 0;
	}
	//加载成功

	// printf( "press any to create device!\n" );
	// getch();

	TestDriver();

	//这时候你可以通过注册表,或其他查看符号连接的软件验证.
	// printf( "press any to unload the driver!\n" );
	// getch();

	//卸载驱动
	// 	bRet = UnloadDriver(DRIVER_NAME);
	// 	if (!bRet)
	// 	{
	// 		printf("UnloadNTDriver error\n");
	// 		return 0;
	// 	}

	return 0;
}

弹窗程序待完善的问题

1.超时问题

  • 同时模拟两个攻击(几乎同时运行两个attcker.exe),对GetResultFromUserLock()加了锁后,驱动中调用GetResultFromUser()应该是串行的,第一个弹窗倒计时30s后,自动关闭,attcker.exe退出,第二弹窗开始倒计时也需要倒计时满30s后,atccker.exe才退出
原因
  • 内核层程序是支持多线程并发的(可以同时(并发不是并行)拦截多个攻击事件)
  • 应用层程序只有一个线程处理
  • 假设内核层同时监测到3个攻击,发给应用层,每个攻击同时等待40s,应用层只有一个线程,只能依次把攻击事件弹窗,倒计时30s。这时候用户可能在第一个30s内只处理完第一弹窗的话,30s过后第2、3个攻击事件可能来不及弹窗,因为超时都消失了,用户来不及处理。
解决方法
  • 方法1:应用层改为多线程,当内核层在很短时间间隔内截获较多攻击事件,应用层为每个攻击事件单独开一个线程来弹窗处理,但这样会导致短时间内较多弹窗,用户体验不佳
  • 方法2:将内核层发送攻击事件改为串行的
    • 方式1:把攻击事件加到队列
    • 方式2:把GetResultFromUser()设为临界区,这样一次只能有一个线程调用GetResultFromUser()
/// 加放锁放入函数内部
        //LockWrite(&g_GetResultFromUserLock); //最好是在函数内部。除非你在任何调用这个函数的地方都加锁
        //KeEnterCriticalRegion(); //改成串行
        notifyResult = GetResultFromUser();//这里最长会等待40s,收集数据封装成一个结构体结点,放在OperList或者满足PendingIrpList中等待的Irp,从R3获得弹框结果,是阻止还是放过
        //KeLeaveCriticalRegion();
        //UnLockWrite(&g_GetResultFromUserLock);

@todo 共享资源锁并没有生效,还需要用windbg R3-R0联调一下

2.不是真正异步读
原因
  • CreateFile()倒数第二个参数是0

  • 异步编程:线程在调用函数的时候,不管这个函数有没有拿到数据,会立即返回,后面通过回调函数或者是事件机制来拿到函数执行的结果。异步比同步效率要高

  • 同步编程:线程在调用函数的时候,只要这个函数没有拿到数据,就一直在哪里等待,不会立即返回,直到拿到数据才会返回。

  • eg:A在微信上向女盆友B发消息之后,A去做其他事情了,B给A回消息,会有消息声音提醒A,A打开微信拿到B回复的消息

  • eg1:完成端口模型

  • 同步阻塞编程(Block)

    • 基于锁的(lock-based)
    • 阻塞的意思是,会睡眠让出CPU,有结果的时候就会被唤醒,一般用于读之前不确定有没有结果数据,使用阻塞比较好。
    • eg:A在微信上向女盆友B发消息之后,A抱着手机睡着了,直到B给A回消息,会有消息声音吵醒A,A醒来打开微信拿到B回复的消息
  • 同步非阻塞编程(Non-blocking Synchronization) :根据粒度不同分为以下几类

    • 非阻塞的意思是,不会睡眠,一直在等,直到拿到结果,一般用于读之前明确知道的是有结果数据的,而且很快能拿到结果。
      • eg:女盆友B在微信上向A发消息之前确定A是在线的,B发完消息后,A秒回B,B直接看到到A回复的消息
      • eg1:网络中的select模型、epoll模型,多路复用,多个IO复用同一个Event,只有Event会阻塞,读写不会阻塞
解决方法
  • CreateFile()倒数第二个参数设置成FILE_FLAG_OVERAPPED
HANDLE OpenDevice()
{
	//测试驱动程序
	HANDLE hDevice = CreateFile(_T("\\\\.\\PopupDrv"),
								GENERIC_WRITE | GENERIC_READ,
								0,
								NULL,
								OPEN_EXISTING,
								0, //这里要传入FILE_FLAG_OVERAPPED才是真正异步读
								NULL);
	if (hDevice != INVALID_HANDLE_VALUE)
	{
		printf("Create Device ok ! \n");
	}
	else
	{
		printf("Create Device faild %d ! \n", GetLastError());
		return NULL;
	}

	return hDevice;
}
3.获取攻击进程的全路径还没完成
根据PID获得进程全路径思路
  • 根据PID获得进程全路径
    • PID→exprocess→KeStackAttachProcess→ZwQuerylnformationProcess->ProcesslmageFileName→ZwCreateFile
  • 设备对象名转换成符号链接名
    • ->ObReferenceObjectByHandle→RtIVolumeDeviceToDosName->ZwQuerylnformationFile
实战
/**
*  @brief       GetProcessFullNameByPid 获取进程的全路径;
*  @param[in]   nPid 进程pid;
*  @param[out]  FullPath 进程的全路径;
*  @return      ntStatus 成功返回0,否则为非0,如果例程成功,则它必须返回 STATUS_SUCCESS。
*                        否则,它必须返回在 ntstatus中定义的错误状态值之一;
*/
NTSTATUS GetProcessFullNameByPid(HANDLE nPid, PUNICODE_STRING FullPath)
{

    HANDLE hFile = NULL;
    ULONG nNeedSize = 0;
    NTSTATUS nStatus = STATUS_SUCCESS;
    NTSTATUS nDeviceStatus = STATUS_DEVICE_DOES_NOT_EXIST;
    PEPROCESS Process = NULL;
    KAPC_STATE ApcState = { 0 };
    PVOID lpBuffer = NULL;
    OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
    IO_STATUS_BLOCK IoStatus = { 0 };
    PFILE_OBJECT FileObject = NULL;
    PFILE_NAME_INFORMATION FileName = NULL;
    WCHAR FileBuffer[MAX_PATH] = { 0 };
    DECLARE_UNICODE_STRING_SIZE(ProcessPath, MAX_PATH);
    DECLARE_UNICODE_STRING_SIZE(DosDeviceName, MAX_PATH);

    PAGED_CODE();
    /// 通过pid拿到进程的eprocess结构
    nStatus = PsLookupProcessByProcessId(nPid, &Process);
    if (NT_ERROR(nStatus))
    {
        KdPrint(("%s error PsLookupProcessByProcessId.\n", __FUNCTION__));
        return nStatus;
    }

    __try
    {
        /// 切换到当前进程的上下文
        KeStackAttachProcess(Process, &ApcState);

        nStatus = ZwQueryInformationProcess(
            NtCurrentProcess(),
            ProcessImageFileName,
            NULL,
            NULL, //第一次传NULL是为了获取进程路径的大小
            &nNeedSize);

        if (STATUS_INFO_LENGTH_MISMATCH != nStatus)
        {
            KdPrint(("%s NtQueryInformationProcess error.\n", __FUNCTION__));
            nStatus = STATUS_MEMORY_NOT_ALLOCATED;
            __leave;
        }
        /// 分配一个内存来存放进程的全路径
        lpBuffer = ExAllocatePoolWithTag(NonPagedPool, nNeedSize, 'GetP');
        if (lpBuffer == NULL)
        {
            KdPrint(("%s ExAllocatePoolWithTag error.\n", __FUNCTION__));
            nStatus = STATUS_MEMORY_NOT_ALLOCATED;
            __leave;
        }

        nStatus = ZwQueryInformationProcess(
            NtCurrentProcess(),
            ProcessImageFileName,
            lpBuffer, //在查询一遍就获得进程的全路径了
            nNeedSize,
            &nNeedSize);

        if (NT_ERROR(nStatus))
        {
            KdPrint(("%s NtQueryInformationProcess error2.\n", __FUNCTION__));
            __leave;
        }
        //获得的路径是\device\harddiskvolume3\program files\qq.exe这种格式,需要转化成用户熟悉的盘符
        //\??\c:\program files\qq.exe
        RtlCopyUnicodeString(&ProcessPath, (PUNICODE_STRING)lpBuffer);
        InitializeObjectAttributes(
            &ObjectAttributes,
            &ProcessPath,
            OBJ_CASE_INSENSITIVE,
            NULL,
            NULL);
        /// 打开路径得到handle
        nStatus = ZwCreateFile(
            &hFile,
            FILE_READ_ATTRIBUTES,
            &ObjectAttributes,
            &IoStatus,
            NULL,
            FILE_ATTRIBUTE_NORMAL,
            0,
            FILE_OPEN,
            FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
            NULL,
            0);

        if (NT_ERROR(nStatus))
        {
            hFile = NULL;
            __leave;
        }

        nStatus = ObReferenceObjectByHandle(
            hFile,
            NULL,
            *IoFileObjectType,
            KernelMode,
            (PVOID*)&FileObject, //得到handle对应的FileObject
            NULL);

        if (NT_ERROR(nStatus))
        {
            FileObject = NULL;
            __leave;
        }

        FileName = (PFILE_NAME_INFORMATION)FileBuffer;

        nStatus = ZwQueryInformationFile(
            hFile,
            &IoStatus,
            FileName,
            sizeof(WCHAR) * MAX_PATH,
            FileNameInformation);

        if (NT_ERROR(nStatus))
        {
            __leave;
        }

        if (FileObject->DeviceObject == NULL) //通过FileObject得到DeviceObject
        {
            nDeviceStatus = STATUS_DEVICE_DOES_NOT_EXIST;
            __leave;
        }
        /// 求出进程文件所在盘符的符号链接
        nDeviceStatus = RtlVolumeDeviceToDosName(FileObject->DeviceObject, &DosDeviceName);
    }
    __finally
    {
        if (NULL != FileObject)
        {
            ObDereferenceObject(FileObject);
        }

        if (NULL != hFile)
        {
            ZwClose(hFile);
        }

        if (NULL != lpBuffer)
        {
            ExFreePool(lpBuffer);
        }

        KeUnstackDetachProcess(&ApcState);
    }

    if (NT_SUCCESS(nStatus))
    {
        RtlInitUnicodeString(&ProcessPath, FileName->FileName);

        if (NT_SUCCESS(nDeviceStatus))
        {
            /// 拼接DosDeviceName + ProcessPath
            RtlCopyUnicodeString(FullPath, &DosDeviceName);
            RtlUnicodeStringCat(FullPath, &ProcessPath);
        }
        else
        {
            RtlCopyUnicodeString(FullPath, &ProcessPath);
        }
    }

    return nStatus;
}
4.也可以在内核层弹窗

不仅可以应用层弹窗。内核层也调用ExRaiseHardError()来弹窗(但可定制性不高,没有计时功能,比如在反截屏中,可以弹窗提示用户)

主防的几个经典问题

弹窗弹的是什么?

A把B怎么了?
  • A:R3某进程。
    • 忽略内核操作,互不干涉,内核和内核对战是没必要的,主要是阻止进程进入内核。比如五大常任理事国都有核武器,五大常任理事国一旦打架,地球重启。共同阻止其他非常任理事国突破核武器。朝鲜。
  • B:文件/注册表/驱动/进程
  • 怎么了:创建/删除/修改/重命名/启动
允许还是阻止
弹框的不同颜色警示危险等级
  • 做得太弱智,老弹窗,用户会厌烦,用户也可能误判
  • 拦截到攻击自己判断处理最好,也防止用户误判,但太智能也不行,一次都没有弹窗,用户会怀疑你行不行,所以还是偶尔弹一下绿框告诉用户,拦截到某个攻击事件已经自动处理,一闪而过刷一下存在感

模式匹配

正则匹配
  • 为了性能和安全的平衡,不可能监控所有文件,监控重要的文件比较合理,比如sytem32文件夹等。这时候就需要使用模式匹配了
  • *表示零次或者任意次字符
  • 表示零次或下一次字符
  • eg:
BOOLEAN IsPatternMatch(PUNICODE STRING Expression,
	PUNICODE_STRING Name, BOOLEAN IgnoreCase)
{
	/// 如果匹配成功return True,但也有局限性,只支持文件路径的正则匹配,比如注册表就不支持了
	return FsRtllsNamelnExpression(
		Expression, //正则式
		Name, //具体某个文件的路径,
		IgnoreCase, //如果这里设置为TRUE,那么Expression必须是大写的
		NULL
	);
}

/// 比如现在有两条规则:假设忽略大小写
L'C:\\WINDOWS\\SYSTEM32\\*.SYS"
L"C:\\WINDOWS\\SYSTEM32\\*\\*.SYS"
/// 有路径:
L"C:\\Window\\system32\\122222\\2.sys"
/// 就会匹配第一、第二条规则
/// 如果区分分文件夹,只想命中第二条规则,可以把第一条规则改成
L'C:\\WINDOWS\\SYSTEM32/*.SYS" 
实战:支持任何字符串的正则匹配
#include <Ntifs.h>
#include <ntddk.h>
#include <windef.h>

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
	DbgPrint("Goodbye!\n");
}

BOOLEAN IsPatternMatch(PUNICODE_STRING Expression, PUNICODE_STRING Name, BOOLEAN IgnoreCase)
{
	return FsRtlIsNameInExpression(
    			Expression,
    			Name,
    			IgnoreCase,//如果这里设置为TRUE,那么Expression必须是大写的
    			NULL
    			); 

}

BOOL PatternMatch(WCHAR * pat, WCHAR * str)
{
   register WCHAR * s;
   register WCHAR * p;
   BOOL star = FALSE;

loopStart:
   for (s = str, p = pat; *s; ++s, ++p) {
      switch (*p) {
         case L'?':
            if (*s == L'.') goto starCheck;
            break;
         case L'*':
            star = TRUE;
            str = s, pat = p;
            if (!*++pat) return TRUE;
            goto loopStart;
         default:
            //if (Lower(*s) != Lower(*p))
               goto starCheck;
            break;
      } 
   } 
   if (*p == L'*') ++p;
   return (!*p);

starCheck:
   if (!star) return FALSE;
   str++;
   goto loopStart;
}

BOOL PatternNMatch(WCHAR * pat, WCHAR * str, DWORD count)
{
   register WCHAR * s;
   register WCHAR * p;
   BOOL star = FALSE;
   DWORD dwCount = count;

loopStart:
   for (s = str, p = pat; dwCount>0; --dwCount, ++s, ++p) {
      switch (*p) {
         case L'?':
            if (*s == L'.') goto starCheck;
            break;
         case L'*':
            star = TRUE;
            str = s, pat = p;
            if (!*++pat) return TRUE;
            goto loopStart;
         default:
            //if (Lower(*s) != Lower(*p))
               goto starCheck;
            break;
      } 
   } 
   if (*p == L'*') ++p;
   return (!*p);

starCheck:
   if (!star) return FALSE;
   str++;
   dwCount--;
   goto loopStart;
}


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{

	UNICODE_STRING uExpression = {0};
	UNICODE_STRING uName	   = {0};

	RtlInitUnicodeString(&uExpression, L"C:\\WINDOWS\\SYSTEM32\\*.SYS");
	RtlInitUnicodeString(&uName, L"c:\\Windows\\system32\\122222\\2.sys");

	if (IsPatternMatch(&uExpression, &uName, TRUE))
	{
		DbgPrint("Matched\n");
	}
	else
	{
		DbgPrint("Not Matched\n");
	}
	pDriverObject->DriverUnload = DriverUnload;
	
	return STATUS_SUCCESS;

}


格式转换
获得目标对象的全路径
  • 内核中有多种对象,对象不同获取全路径的方式不同
  • Handle
  • ObReferenceObjectByHandle→ loQueryFileDosDeviceName
  • ObReferenceObjectBylHandle→ ObQueryNameString(强删文件例子里)
  • IRP查询(FILEMON4.34中FilemonQueryFile函数)
  • FltGetName(minifilter)
获得文件短名的长名
  • windows用dir /x 查看短名
    • dos环境下的83格式(文件名总数<=8,扩展名<=3,太长的文件名中间部分会被~替换)
  • 统一使用长名来做规则匹配
    • 所以需要短名转化成长名
  • 思路:
    • 把文件名按~来分割,从左往右依次调用ZwQueryDirectoryFile()获得长名,直至所有短名都转换成功拼接在一起就得到长名
实战
#include <ntifs.h>
#include <ntstrsafe.h>
#include <ntddk.h>
#include <windef.h>

BOOL IsRootDirecotry(WCHAR * wszDir)
{
	SIZE_T length = wcslen(wszDir);
	
	// c:
	if((length == 2) && (wszDir[1] == L':'))
		return TRUE;
	//\\??\\c:
	if((length == 6) && 
		(_wcsnicmp(wszDir, L"\\??\\", 4) == 0) &&
		(wszDir[5] == L':'))
		return TRUE;
	//\\DosDevices\\c:
	if((length == 14) && 
		(_wcsnicmp(wszDir, L"\\DosDevices\\", 12) == 0) &&
		(wszDir[13] == L':'))
		return TRUE;
	//\\Device\\HarddiskVolume1
	if((length == 23) &&
		(_wcsnicmp(wszDir, L"\\Device\\HarddiskVolume", 22) == 0))
		return TRUE;
	
	
	return FALSE;
}


BOOL IsDirectorySep(WCHAR ch) 
{
    return (ch == L'\\' || ch == L'/');
}

//C:\\Program\\123456~1
//wszRootdir为:c:\\Program
//wszShortName为:123456~1

BOOL QueryDirectoryForLongName(
					  WCHAR * wszRootDir, 
					  WCHAR * wszShortName, 
					  WCHAR *wszLongName, 
					  ULONG ulSize)
{
	UNICODE_STRING				ustrRootDir		= {0};
	UNICODE_STRING				ustrShortName	= {0};
	UNICODE_STRING				ustrLongName	= {0};
	OBJECT_ATTRIBUTES			oa				= {0};
	IO_STATUS_BLOCK				Iosb			= {0};
	NTSTATUS					ntStatus		= 0;
	HANDLE						hDirHandle		= 0;
	BYTE						*Buffer			= NULL;
	WCHAR						*wszRoot		= NULL;
	PFILE_BOTH_DIR_INFORMATION	pInfo			= NULL;

	RtlZeroMemory(&Iosb, sizeof(IO_STATUS_BLOCK));
	Iosb.Status = STATUS_NO_SUCH_FILE;

	wszRoot = ExAllocatePoolWithTag(PagedPool,
								  MAX_PATH * sizeof(WCHAR),
								  'L2S');
	if(wszRoot == NULL)
	{
		return FALSE;
	}

	RtlZeroMemory(wszRoot, MAX_PATH * sizeof(WCHAR));

	wcsncpy(wszRoot, wszRootDir, MAX_PATH);

	RtlInitUnicodeString(&ustrRootDir, wszRoot);
	RtlInitUnicodeString(&ustrShortName, wszShortName);

	if(IsRootDirecotry(wszRoot))
		RtlAppendUnicodeToString(&ustrRootDir, L"\\");

	InitializeObjectAttributes(&oa,
							   &ustrRootDir,
							   OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
							   0, 
							   0);  
	
	ntStatus = ZwCreateFile(&hDirHandle,
							GENERIC_READ | SYNCHRONIZE,
							&oa,
							&Iosb,
							0, 
							FILE_ATTRIBUTE_DIRECTORY, 
							FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 
							FILE_OPEN, 
							FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT , 
							0,
							0);

	if (!NT_SUCCESS(ntStatus)) 
	{ 
		ExFreePool(wszRoot);
		return FALSE;
	}

	ExFreePool(wszRoot);

	Buffer = ExAllocatePoolWithTag(PagedPool,
						  1024,
						  'L2S');
	if(Buffer == NULL)
	{
		ZwClose(hDirHandle);
		return FALSE;
	}

	RtlZeroMemory(Buffer, 1024);

	ntStatus = ZwQueryDirectoryFile(hDirHandle,
								NULL,
								0,
								0,
								&Iosb,
								Buffer,
								1024,
								FileBothDirectoryInformation,
								TRUE,
								&ustrShortName, //传回与 ustrShortName Match的项
								TRUE);

	if (!NT_SUCCESS(ntStatus)) 
	{
		ExFreePool(Buffer);
		ZwClose(hDirHandle);
		return FALSE;
	}

	ZwClose(hDirHandle);

	pInfo = (PFILE_BOTH_DIR_INFORMATION) Buffer;
	
	if(pInfo->FileNameLength == 0)
	{
		ExFreePool(Buffer);
		return FALSE;
	}

	ustrShortName.Length  = (USHORT)pInfo->FileNameLength;
	ustrShortName.MaximumLength = (USHORT)pInfo->FileNameLength;
	ustrShortName.Buffer = pInfo->FileName;	//长名

	if(ulSize < ustrShortName.Length)
	{	
		ExFreePool(Buffer);
		return FALSE;
	}

	ustrLongName.Length = 0;
	ustrLongName.MaximumLength = (USHORT)ulSize;
	ustrLongName.Buffer = wszLongName;

	RtlCopyUnicodeString(&ustrLongName, &ustrShortName);
	ExFreePool(Buffer);
	return TRUE;
}

BOOL QueryLongName(WCHAR * wszFullPath, WCHAR * wszLongName, ULONG size)
{
	BOOL		rtn				= FALSE;
	WCHAR *		pchStart		= wszFullPath;
	WCHAR *		pchEnd			= NULL;
	WCHAR *		wszShortName	= NULL;
	
	//c:\\Program\\Files1~1-->获得Files1~1的长名
	while(*pchStart)
	{
		if(IsDirectorySep(*pchStart))
			pchEnd = pchStart;
		
		pchStart++;
	}
	//wszFullPath=c:\\Program
	//pchEnd = Files~1
	
	if(pchEnd)
	{
		*pchEnd++ = L'\0';
		//c:\\Program\\Files1~1
		//wszFullPath:c:\\Program
		//pchEnd:Files1~1
		wszShortName = pchEnd;
		rtn = QueryDirectoryForLongName(wszFullPath, wszShortName, wszLongName, size);
		*(--pchEnd) = L'\\';
		//wszFullPath=c:\\Program\\Files1~1
	}
	return rtn;
}

//先把根目录拷贝到目标目录中,剩下的找到下一级目录是否含有~,如果有,则开始转化.
//如:c:\\Progam\\a~1\\b~1\hi~1.txt
//pchStart指向目录中前一个\\,pchEnd扫描并指向目录的下一个\\,其中如果发现了~,则是短名,需要转换.
//传c:\\Program\\a~1-->c:\\Progam\\ax
//传c:\\Program\\ax\\b~1-->c:\\Program\\ax\\by
//传c:\\Program\\ax\by\\hi~1.txt-->c:\\Program\\ax\by\\hiz.txt
BOOL ConverShortToLongName(WCHAR *wszLongName, WCHAR *wszShortName, ULONG size)
{
	WCHAR			*szResult		= NULL;
	WCHAR			*pchResult		= NULL;
	WCHAR			*pchStart		= wszShortName;
	INT				Offset			= 0;
  
	szResult = ExAllocatePoolWithTag(PagedPool,
						  sizeof(WCHAR) * (MAX_PATH * 2 + 1),
						  'L2S');

	if(szResult == NULL)
	{
		return FALSE;
	}
	
	RtlZeroMemory(szResult, sizeof(WCHAR) * (MAX_PATH * 2 + 1));
	pchResult = szResult;


	//C:\\x\\-->\\??\\c:
	if (pchStart[0] && pchStart[1] == L':') 
	{
		*pchResult++ = L'\\';
		*pchResult++ = L'?';
		*pchResult++ = L'?';
		*pchResult++ = L'\\';
		*pchResult++ = *pchStart++;
		*pchResult++ = *pchStart++;
		Offset = 4;
	}
	//\\DosDevices\\c:\\xx-->\\??\\c:
	else if (_wcsnicmp(pchStart, L"\\DosDevices\\", 12) == 0)
	{
		RtlStringCbCopyW(pchResult, sizeof(WCHAR) * (MAX_PATH * 2 + 1), L"\\??\\");
		pchResult += 4;
		pchStart += 12;
		while (*pchStart && !IsDirectorySep(*pchStart))
			*pchResult++ = *pchStart++;
		Offset = 4;
	}
	//\\Device\\HarddiskVolume1\\xx-->\\Device\\HarddiskVolume1
	else if (_wcsnicmp(pchStart, L"\\Device\\HardDiskVolume", 22) == 0)
	{
		RtlStringCbCopyW(pchResult, sizeof(WCHAR) * (MAX_PATH * 2 + 1),L"\\Device\\HardDiskVolume");
		pchResult += 22;
		pchStart += 22;
		while (*pchStart && !IsDirectorySep(*pchStart))
			*pchResult++ = *pchStart++;
	}
	//\\??\\c:\\xx-->\\??\\c:
	else if (_wcsnicmp(pchStart, L"\\??\\", 4) == 0)
	{
		RtlStringCbCopyW(pchResult, sizeof(WCHAR) * (MAX_PATH * 2 + 1), L"\\??\\");
		pchResult += 4;
		pchStart += 4;

		while (*pchStart && !IsDirectorySep(*pchStart))
			*pchResult++ = *pchStart++;
	}
	else
	{
		ExFreePool(szResult);
		return FALSE;
	}

	while (IsDirectorySep(*pchStart)) 
	{
		BOOL			bShortName			= FALSE;
		WCHAR			*pchEnd				= NULL;
		WCHAR			*pchReplacePos		= NULL;

		*pchResult++ = *pchStart++;

		pchEnd = pchStart;
		pchReplacePos = pchResult;

		while (*pchEnd && !IsDirectorySep(*pchEnd))
		{
			if(*pchEnd == L'~')
			{
				bShortName = TRUE;
			}

			*pchResult++ = *pchEnd++;
		}

		*pchResult = L'\0';
  
		if(bShortName)
		{
			WCHAR  * szLong = NULL;
			
			szLong = ExAllocatePoolWithTag(PagedPool,
						  sizeof(WCHAR) * MAX_PATH,
						  'L2S');
			if(szLong)
			{
				RtlZeroMemory(szLong,  sizeof(WCHAR) * MAX_PATH);

				if(QueryLongName(szResult, szLong, sizeof(WCHAR) * MAX_PATH))
				{
					RtlStringCbCopyW(pchReplacePos, sizeof(WCHAR) * (MAX_PATH * 2 + 1), szLong);
					pchResult = pchReplacePos + wcslen(pchReplacePos);
				}

				ExFreePool(szLong);
			}
		}

		pchStart = pchEnd;
	}

	wcsncpy(wszLongName, szResult + Offset, size/sizeof(WCHAR));
	ExFreePool(szResult);
	return TRUE;
}

BOOL IsShortNamePath(WCHAR * wszFileName)
{
	WCHAR *p = wszFileName;

	while(*p != L'\0')
	{
		if(*p == L'~')
		{
			return TRUE;
		}
		p++;
	}
	
	return FALSE;
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
	DbgPrint("Goodbye!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
	//dir /x看短名
	WCHAR	wszShortName[MAX_PATH] =L"\\??\\C:\\PROGRA~1\\COMMON~1\\MICROS~1\\VC\\1.txt";
	WCHAR	wszLongName[MAX_PATH] = {0};

	if(ConverShortToLongName(wszLongName, wszShortName, sizeof(wszLongName)))
	{
		DbgPrint("%ws\n", wszLongName);
	}

	pDriverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

设备名与符号链接名的转化
  • 设备名-> 符号链接名
    • 有指定的API可以调用
    • ZwOpenSymbolicLinkObject()得到符号链接名
    • ZwQuerySymbolicLinkObject()得到设备名
  • 设备名<-符号链接名
    • 没有指定的API可以调用
    • 但盘符是有限的,只有26个,A-Z
    • 可以通过枚举A-Z盘符对应的设备名是否与目标一致来找到符号链接名
实战
#include <ntddk.h>
#include <windef.h>
#include <ntstrsafe.h>

//输入\\??\\c:-->\\device\\\harddiskvolume1
//LinkTarget.Buffer注意要释放

NTSTATUS QuerySymbolicLink(
			IN PUNICODE_STRING SymbolicLinkName,
			OUT PUNICODE_STRING LinkTarget
			)                                  
{
	OBJECT_ATTRIBUTES	oa		= {0};
	NTSTATUS			status	= 0;
	HANDLE				handle	= NULL;

	InitializeObjectAttributes(
							&oa, 
							SymbolicLinkName,
							OBJ_CASE_INSENSITIVE,
							0, 
							0);

	status = ZwOpenSymbolicLinkObject(&handle, GENERIC_READ, &oa);
	if (!NT_SUCCESS(status))
	{
		return status;
	}

	LinkTarget->MaximumLength = MAX_PATH*sizeof(WCHAR);
	LinkTarget->Length = 0;
	LinkTarget->Buffer = ExAllocatePoolWithTag(PagedPool, LinkTarget->MaximumLength,'SOD');
	if (!LinkTarget->Buffer)
	{
		ZwClose(handle);
		return STATUS_INSUFFICIENT_RESOURCES;
	}

	RtlZeroMemory(LinkTarget->Buffer, LinkTarget->MaximumLength);

	status = ZwQuerySymbolicLinkObject(handle, LinkTarget, NULL);
	ZwClose(handle);

	if (!NT_SUCCESS(status))
	{
		ExFreePool(LinkTarget->Buffer);
	}

	return status;
}

//输入\\Device\\harddiskvolume1
//输出C:
//DosName.Buffer的内存记得释放

NTSTATUS
MyRtlVolumeDeviceToDosName(
						IN PUNICODE_STRING DeviceName,
						OUT PUNICODE_STRING DosName
						)

/*++

Routine Description:

This routine returns a valid DOS path for the given device object.
This caller of this routine must call ExFreePool on DosName->Buffer
when it is no longer needed.

Arguments:

VolumeDeviceObject - Supplies the volume device object.
DosName - Returns the DOS name for the volume
Return Value:

NTSTATUS

--*/

{
	NTSTATUS				status					= 0;
	UNICODE_STRING			driveLetterName			= {0};
	WCHAR					driveLetterNameBuf[128] = {0};
	WCHAR					c						= L'\0';
	WCHAR					DriLetter[3]			= {0};
	UNICODE_STRING			linkTarget				= {0};

	for (c = L'A'; c <= L'Z'; c++)
	{
		RtlInitEmptyUnicodeString(&driveLetterName,driveLetterNameBuf,sizeof(driveLetterNameBuf));
		RtlAppendUnicodeToString(&driveLetterName, L"\\??\\");
		DriLetter[0] = c;
		DriLetter[1] = L':';
		DriLetter[2] = 0;
		RtlAppendUnicodeToString(&driveLetterName,DriLetter);

		status = QuerySymbolicLink(&driveLetterName, &linkTarget);
		if (!NT_SUCCESS(status))
		{
			continue;
		}

		if (RtlEqualUnicodeString(&linkTarget, DeviceName, TRUE))
		{
			ExFreePool(linkTarget.Buffer);
			break;
		}

		ExFreePool(linkTarget.Buffer);
	}

	if (c <= L'Z')
	{
		DosName->Buffer = ExAllocatePoolWithTag(PagedPool, 3*sizeof(WCHAR), 'SOD');
		if (!DosName->Buffer)
		{
			return STATUS_INSUFFICIENT_RESOURCES;
		}

		DosName->MaximumLength = 6;
		DosName->Length   = 4;
		*DosName->Buffer  = c;
		*(DosName->Buffer+ 1) = ':';
		*(DosName->Buffer+ 2) = 0;

		return STATUS_SUCCESS;
	}

	return status;
} 

//c:\\windows\\hi.txt<--\\device\\harddiskvolume1\\windows\\hi.txt
BOOL NTAPI GetNTLinkName(IN WCHAR * wszNTName, OUT WCHAR * wszFileName)
{
	UNICODE_STRING		ustrFileName = {0};
	UNICODE_STRING		ustrDosName = {0};
	UNICODE_STRING		ustrDeviceName = {0};

	WCHAR				*pPath = NULL;
	ULONG				i = 0;
	ULONG				ulSepNum = 0;


	if (wszFileName == NULL ||
		wszNTName == NULL ||
		_wcsnicmp(wszNTName, L"\\device\\harddiskvolume", wcslen(L"\\device\\harddiskvolume"))!=0)
	{
		return FALSE;
	}

	ustrFileName.Buffer = wszFileName;
	ustrFileName.Length = 0;
	ustrFileName.MaximumLength = sizeof(WCHAR)*MAX_PATH;

	while(wszNTName[i]!=L'\0')
	{

		if (wszNTName[i] == L'\0')
		{
			break;
		}
		if (wszNTName[i] == L'\\')
		{
			ulSepNum++;
		}
		if (ulSepNum == 3)
		{
			wszNTName[i] = UNICODE_NULL;
			pPath = &wszNTName[i+1];
			break;
		}
		i++;
	}

	if (pPath == NULL)
	{
		return FALSE;
	}

	RtlInitUnicodeString(&ustrDeviceName, wszNTName);

	if (!NT_SUCCESS(MyRtlVolumeDeviceToDosName(&ustrDeviceName, &ustrDosName)))
	{
		return FALSE;
	}

	RtlCopyUnicodeString(&ustrFileName, &ustrDosName);
	RtlAppendUnicodeToString(&ustrFileName, L"\\");
	RtlAppendUnicodeToString(&ustrFileName, pPath);

	ExFreePool(ustrDosName.Buffer);

	return TRUE;
}

BOOL QueryVolumeName(WCHAR ch, WCHAR * name, USHORT size)
{
	WCHAR szVolume[7] = L"\\??\\C:";
	UNICODE_STRING LinkName;
	UNICODE_STRING VolName;
	UNICODE_STRING ustrTarget;
	NTSTATUS ntStatus = 0;
	
	RtlInitUnicodeString(&LinkName, szVolume);
	
	szVolume[4] = ch;

	ustrTarget.Buffer = name;
	ustrTarget.Length = 0;
	ustrTarget.MaximumLength = size;
	
	ntStatus = QuerySymbolicLink(&LinkName, &VolName);
	if (NT_SUCCESS(ntStatus))
	{
		RtlCopyUnicodeString(&ustrTarget, &VolName);
		ExFreePool(VolName.Buffer);
	}
	return NT_SUCCESS(ntStatus);
	
}

//\\??\\c:\\windows\\hi.txt-->\\device\\harddiskvolume1\\windows\\hi.txt

BOOL NTAPI GetNtDeviceName(IN WCHAR * filename, OUT WCHAR * ntname)
{
	UNICODE_STRING uVolName = {0,0,0};
	WCHAR volName[MAX_PATH] = L"";
	WCHAR tmpName[MAX_PATH] = L"";
	WCHAR chVol = L'\0';
	WCHAR * pPath = NULL;
	int i = 0;
	

	RtlStringCbCopyW(tmpName, MAX_PATH * sizeof(WCHAR), filename);
	
	for(i = 1; i < MAX_PATH - 1; i++)
	{
		if(tmpName[i] == L':')
		{
			pPath = &tmpName[(i + 1) % MAX_PATH];
			chVol = tmpName[i - 1];
			break;
		}
	}
	
	if(pPath == NULL)
	{
		return FALSE;
	}
	
	if(chVol == L'?')
	{
		uVolName.Length = 0;
		uVolName.MaximumLength = MAX_PATH * sizeof(WCHAR);
		uVolName.Buffer = ntname;
		RtlAppendUnicodeToString(&uVolName, L"\\Device\\HarddiskVolume?");
		RtlAppendUnicodeToString(&uVolName, pPath);
		return TRUE;
	}
	else if(QueryVolumeName(chVol, volName, MAX_PATH * sizeof(WCHAR)))
	{
		uVolName.Length = 0;
		uVolName.MaximumLength = MAX_PATH * sizeof(WCHAR);
		uVolName.Buffer = ntname;
		RtlAppendUnicodeToString(&uVolName, volName);
		RtlAppendUnicodeToString(&uVolName, pPath);
		return TRUE;
	}
	
	return FALSE;
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
	DbgPrint("Goodbye!\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
	UNICODE_STRING		ustrDeviceName			= {0};
	UNICODE_STRING		ustrLinkName			= {0};
	WCHAR				*wszDeviceName			= L"\\Device\\harddiskvolume1";
	NTSTATUS			ntStatus				= 0;
	WCHAR			DeviceName[MAX_PATH]		= L"\\Device\\harddiskvolume1\\windows\\hi.txt";
	WCHAR			FileName[MAX_PATH]		= {0};
	WCHAR			szDeviceName[MAX_PATH]  = {0};

	RtlInitUnicodeString(&ustrDeviceName, wszDeviceName);

	ntStatus = MyRtlVolumeDeviceToDosName(&ustrDeviceName, &ustrLinkName);
	if (NT_SUCCESS(ntStatus))
	{
		
		DbgPrint("linkname:%wZ\n", &ustrLinkName);
		if (ustrLinkName.Buffer)
		{
			ExFreePool(ustrLinkName.Buffer);
		}
	}
	if (GetNTLinkName(DeviceName, FileName))
	{
		DbgPrint("FileName:%ws\n", FileName);
		GetNtDeviceName(FileName, szDeviceName);
		DbgPrint("szDeviceName:%ws", szDeviceName);
	}

	pDriverObject->DriverUnload = DriverUnload;
	
	return STATUS_SUCCESS;

}


规则的下发方式
  • 用dat文件存放规则,dat文件做一些简单加密(防止木马和病毒利用,绕过监测)
  • 客户端把dat文件的规则读出,通过DeviceIoControl()发到内核,保存在内核中的链表或者树或者hash表里面,内核在拦截到攻击事件的时候,就会去遍历这个存放规则数据结构,匹配上就弹窗或者自行处理。

放行问题

  • 大数据机器学习,智能放行
  • 中国杀毒软件层参加国际比赛到很高分,是针对病毒库定制的版本(查杀率很高),和用户的版本不一样,取消比赛成绩
放行的条件
  • 内核态的操作不拦截
    • ExGetPreviousMode() == KernnelMode来判断是不是内核态
    • 或者用Irp->RequestorMode == kernelMode来判断是不是内核态
  • 自己进程的操作不拦截
    - 在DeviceIoctrlFilter中通过PsGetCurrentProcessId()拿到进程的PID
  • 系统进程的操作不拦截
    • DriverEntry里处于系统上下文,再通过PsGetCurrentProcessId()拿到的就是系统进程,以后凡是这些系统进程的操作都放行
    • 应用层的Irql一般不会超过APC_LEVEL,即KeGetCurrentIrql()>APC_LEVEL放行,只拦截应用层
  • 白名单中的进程(交了钱的)
    • 但有可能会被白利用,让白名单的程序把我做一些攻击操作,借刀杀人
    • 防范白利用,用调用链,不仅要看当前进程是好的还是坏的,还要看调用这个进程的父进程是好的还是坏的,只要有一个是坏的,就是不放行。
如何放行
  • 如果通过HOOK进行监控,放行就是直接调用原来被HOOK的API
  • IRP(如果是在过滤驱动进行监控的)
/// 把Irp往下发
IoSkipCurrentIrpStackLocation(IpIrp)IoCallDriver()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值