本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如有错漏,欢迎留言交流指正
拦截,弹窗
- 应用场景:假设系统中了勒索病毒,开始加密整个磁盘,这时候内核监控到并拦截(Hook,驱动过滤,回调)了这些操作,进行数据收集弹窗给用户等待处理。
弹窗
整体思路
- 驱动截获攻击操作,进行数据收集
- 驱动为了拿到应用处理完弹窗的返回的结果,建立一个
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
拿到结果做出相应操作(阻止/运行某个进程的操作)
- 如果此时
- 驱动拿到用户操作结果做出相应的动作。
- 应用层开一个独立线程去异步读出(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,太长的文件名中间部分会被
~
替换)
- 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
放行,只拦截应用层
- DriverEntry里处于
- 白名单中的进程(交了钱的)
- 但有可能会被
白利用
,让白名单的程序把我做一些攻击操作,借刀杀人
- 防范白利用,用
调用链
,不仅要看当前进程是好的还是坏的,还要看调用这个进程的父进程是好的还是坏的,只要有一个是坏的,就是不放行。
- 但有可能会被
如何放行
- 如果通过HOOK进行监控,放行就是直接调用原来被HOOK的API
- IRP(如果是在过滤驱动进行监控的)
/// 把Irp往下发
IoSkipCurrentIrpStackLocation(IpIrp);
IoCallDriver();