在一次排查系统的驱动的目录system32/driver目录文件时,看到一个名字为ProcLaunchMon.sys的驱动
数字签名微软官方的
该驱动带入的时间是2019年12月7日,17:08:33,文件描述是Time Travel Debugging Process Launch Monitor大致明白了就是一个调试工具实事记录进程活动的驱动。微软的这个官方页面有讲述了什么是Time Travel Debugging,
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/time-travel-debugging-overview
本篇的文章的目的是主要研究这个驱动到底做了什么?
接下来就祭出逆向工具ida
微软对这个模块是有部分符号文件的,左侧的Function windows可以看到他是用c++写的驱动有一些类名显示
主要的类名就是ProcessLaunchMonitorClient 、ProcessLaunchMonitorDevice、LegacyDevice
然后再看看导入表有哪些函数,函数并不多主要就两类: 进程函数和事件函数
PsSetCreateProcessNotifyRoutine
ZwCreateEvent
PsSuspendProcess
PsResumeProcess
ZwDuplicateObject
等等
分析完表面这些明显的地方,下面就从驱动的入口点分析
最开始的时候会构建一个device 类 LegacyDevice::LegacyDevice
LegacyDevice::LegacyDevice类的大小是0x38,因为驱动,所以一般用c++写驱动的时候都会用动态内存分配全局结构,ProcessLaunchMonitorDevice::`vftable 是该类的基类,在驱动的虚表rdata节里可以看到该基类的定义
前面第一个是构造与析构函数,后面四个是IO 例程函数DispatchRoutine、DispatchCreate、DispatchClose、DispatchBufferedIoctl
内存构造结束了,就直接调用LegacyDevice::LegacyDevice的构造函数
首先会构造设备名字的字符串
然后创建设备和设备连接
注意该驱动使用的是
WdmlibIoCreateDeviceSecure来创建设备,该函数创建的设备是管理权限才能打开设备。
最后构造函数里会填充MajorFunction结构体,用StaticDispatchRoutine函数覆盖。
memset64(a2->MajorFunction, (unsigned __int64)LegacyDevice::StaticDispatchRoutine, 0x1Cui64);
自此这个构造类结束。
构造了这个全局类LegacyDevice::LegacyDevice,后接下来就是调用注册进程回调通知
回调函数是ProcessCreationNotifyRoutine
到这里入口函数就结束,其实过程很简单,但是精华却在例程函数里。
要控制利用驱动,首先我们必须CreateFile一个驱动,这是会进入驱动的IRP_MJ_CREATE例程,接下来看上面的提到的StaticDispatchRoutine函数
这个函数很简单,可能很多人一下子看不懂,从设备信息中获得DeviceExtension结构然后call v4 + 8的函数,这到底是什么呢,
秘密在刚才那个构造函数里。
在之前的LegacyDevice::LegacyDevice的构造函数里有这么句代码
(_QWORD )(*DeviceObject)->DeviceExtension = this;
就是把LegacyDevice::LegacyDevice这个全局类的this指针赋值给DeviceObject)->DeviceExtension的结构中,现在明白了这个DeviceExtension里的值是什么了把,对他就是LegacyDevice::LegacyDevice的地址值,那么
((void (fastcall **)(int64, _QWORD ))((_QWORD )v4 + 8i64))(v4, v7);
的代码的意思就是调用LegacyDevice::LegacyDevice的基类的第二个函数DispatchRoutine
继续分析进入DispatchRoutine 函数
IRP_MJ_CREATE 会进入第一个函数
ProcessLaunchMonitorDevice::DispatchCreate(this, ((struct _FILE_OBJECT *)iocode + 6));
ProcessLaunchMonitorDevice::DispatchCreate函数里会为每一个Client创建该设备的对象生成一个ProcessLaunchMonitorClient::ProcessLaunchMonitorClient(v6, v7, &v15),该类的内存大小为0x70.
这个类的构造函数一个最主要的功能就是生成一个进程间通讯的Event,赋值给了
v3 = (PVOID )((char )this + 56);的便宜的位置。
最后会把这个Process
赋值给了
ProcessLaunchMonitorDevice这个全局类的v10 = ((_QWORD )this + 6);的位置的LIST_ENTRY的链表里。以及当前设备的文件对象的FsContext的上下文里。
打开了设备之后,我们就要通过IRP_MJ_DEVICE_CONTROL IO控制码是14的之类的去给驱动发IO命令,那么就会经过驱动的
然后再进入ProcessLaunchMonitorDevice::DispatchBufferedIoctl函数
首先该函数会先从之前的我们讲的文件对象的FsContext结构中取出之前创建的ProcessLaunchMonitorClient的指针。
接下来会看到里面有一个IO code
Case : 0x224040 恢复进程
Case 0x224044 关闭 ProcessLaunchMonitorClient 并且清楚恢复所有被悬挂的进程
Case 0x2240048 获取之前ProcessLaunchMonitorClient里创建的进程间通讯的事件。
等等
这些主要的IRP例程的函数大致的逻辑就分析完毕,那该驱动的进程回调函数有什么用呢?这个问题很好,下面就来分析回调通知里的逻辑
当一个进程启动后就会进入该驱动回调通知
然后就会进入
ProcessLaunchMonitorDevice::HandleProcessCreatedOrDestroyed(gLegacyDevice::gLegacyDevice, Create != 0, v4, v6);
这个函数是主要的处理逻辑
然后就会进入
进入这个函数SuspendResumeProcessById(a4, v13)后就会把进程悬挂起来
可以看到
If(a2)
{
v5 = PsSuspendProcess(Object);
}
如果a2这个参数是1的话,直接就悬挂了,然后外面给的参数就是1,那就是只要驱动功能起来了,就直接悬挂了新起来的进程(这个驱动太霸道了), 如果你不发之前我们看到那个IO: 0x224040的控制码的话,它就一直被悬挂,起不来了,或者这个驱动被关闭也能自动恢复所有被悬挂的进程。
其他一些小的附加的结构体的处理不在具体分析,以上就是该驱动的最主要的功能,当然分析完毕就是验证我们的分析结果。
主要代码如下:(具体的iocode 我模糊处理了,避免被恶意乱用)
int main()
{
TCHAR szDriverKey[MAX_PATH] = { 0 };
GetSystemDirectory(szDriverKey,MAX_PATH);
StringCchCat(szDriverKey, MAX_PATH, _T("\\dirver\\ProcLaunchMon.sys"));
if (TRUE)
{
//install driver
LoadDriver(szDriverKey);
}
BOOL CheckServiceOk = FALSE;
if (SUCCEEDED(StringCchPrintf(
szDriverKey,
MAX_PATH,
_T("\\\\.\\%s"),
_T("com_microsoft_xxxxx_ProcLaunchMon")))) //
{
HANDLE hObjectDrv = CreateFile(
szDriverKey,
GENERIC_READ |
GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (hObjectDrv != INVALID_HANDLE_VALUE)
{
DWORD dwProcessId = -1;
BYTE OutBuffer[4002] = { 0 };
ULONG BytesReturned = 0;
DWORD sendrequest = 0xxabsdd;
if (DeviceIoControl(
hObjectDrv,
sendrequest,//,
&dwProcessId,
4,
&dwProcessId,
4,
&BytesReturned, 0))
{
ULONG64 Request = 0;
sendrequest = 0xa2XXXX;
ULONG64 KeyHandle = 0;
if (DeviceIoControl(
hObjectDrv,
sendrequest,//
&Request,
8,
&KeyHandle,
8,
&BytesReturned, 0))
{
//Get the kernel process event and wait for the kernel setting event
if (WaitForSingleObject((HANDLE)KeyHandle, INFINITE) == WAIT_OBJECT_0)
{
// int nI = 0;
// nI++;
}
while (TRUE)
{
sendrequest = 0x125a3X;
typedef struct _GetPidBuffer
{
LARGE_INTEGER Pid;
ULONG32 Other;
}GetPidBuffer;
GetPidBuffer ProcessPid = {0};
if (DeviceIoControl(
hObjectDrv,
sendrequest,
&Request,
8,
&ProcessPid,
12,
&BytesReturned,
0))
{
if (BytesReturned && ProcessPid.Pid.LowPart > 0)
{
printf("curent run pid:%d", ProcessPid.Pid.HighPart);
//If the process is not recovered, it will cause denial of service attack. For example,
//if it is security software, it will cause security software failure
sendrequest = 0x224040;
if (DeviceIoControl(
hObjectDrv,
sendrequest,
&ProcessPid.Pid.HighPart,
8,
&ProcessPid.Pid.HighPart,
8,
&BytesReturned, 0))
{
}
Sleep(100);
BytesReturned = 0;
}
else
{
if (WaitForSingleObject((HANDLE)KeyHandle, INFINITE) == WAIT_OBJECT_0)
{
}
}
}
}
}
}
CloseHandle(hObjectDrv);
hObjectDrv = NULL;
}
}
return 0;
}
1. 加载驱动成功
2. 打开设备成功
3. 开启功能
后记: 这个曾经上报给微软的msrc,对方承认是个有待改进的问题的驱动,但是并非是个漏洞,至今已经过去了四个月了,可以到了公布的时间了,如果一个恶意进程已经攻击进入一个系统后,,并且已经有了管理员权限,他就可以利用这个驱动去控制安全软件的启动,甚至失效,这也是很危险的驱动。