微软轻量级系统监控工具sysmon原理与实现完全分析——ProcessGuid的生成

 

Sysmon的众多事件看起来都是独立存在的,但是它们确实都是由每个进程的产生的,而关联这些信息的东西正是ProcessGuid,这个对进程是唯一的。如下图

Event 23

都会有个ProcessGuid 字段,今天的这篇文章主要就是讲解Sysmo是如何生成事件的ProcessGuid的。
Sysmon有两个地方会调用ProcessGuid创建的函数,第一个是初始化的时候,在服务启动的时候会初始化一次当前机器里 已经打开进程的ProcessGuid

查看这个红色框的函数Sysmon_InitProcessCache

首先会枚举当前机器所有进程,然后下面就会调用Guid的创建的函数

传入ProcessPid,进程的pid

然后像Sysmon的驱动发送IoControl码去获取该进程的相关信息,获取哪些信息呢?接下来我们看内核驱动。
Sysmom内核驱动处理Io的函数SysmonDispatchIrpDeviceIO

找到对应的IoControl

这里就是处理的地方,可以发现会先检验传入参数的大小是不是正确的,然后会调用
SysmonInsertCreateThreadProcess(pUserBuffer->ProcessId, 1u, pUserBuffer->NewAdd, pIrp);去获取该进程pid的进程相关的信息。

首选会获取进程的Token相关的信息

在这里函数里使用了ZwOpenProcessToken(ProcessHandle, 0x20008u, &TokenHandle);去获取进程token句柄

然后Query句柄,使用ZwQueryInformationToken函数去获取,TokenInfomationClass分别是TokenUser、TokenGroups、TokenStatistics、TokenIntegrityLevel。最后会把这些信息输出到传输的缓冲区结构体里

获取了以上信息后,还会继续获取进程的全路径和参数

AttackProcess进程后去获取进程的参数的获取,是通过PEB—>ProcessParameter获取

接下来就是获取进程创建时间

ZwQueryInformationProcess(ProcessHandlev9, ProcessTimes, &KernelUserTime, 0x20u, 0);
if (!KernelUserTime.CreateTime.QuadPart) {
    ResultLength = 48;
    if (ZwQuerySystemInformation(SystemTimeOfDayInformation, &SystemTimeDelayInformation, 0x30u, &ResultLength) >= 0) KernelUserTime.CreateTime.QuadPart = SystemTimeDelayInformation.BootTime.QuadPart

    - SystemTimeDelayInformation.BootTimeBias;
}

继续还会计算改进程的hash

最后把这些信息输出到ring3输入的缓冲区里

结构体是:

struct _Report_Process {
    Report_Common_Header Header;
    CHAR data[16];
    ULONG ProcessPid;
    ULONG ParentPid;
    ULONG SessionId;
    ULONG UserSid;
    LARGE_INTEGER CreateTime;
    LUID AuthenticationId;
    ULONG TokenIsAppContainer;
    LUID TokenId;
    ULONG HashingalgorithmRule;
    DWORD DataChunkLength[6];
    CHAR Data[1];
};

后面的数据data 有

SID
TokenIntegrityLevel
ImageFileName
HASH
CommandLine
DirectoryPath

驱动过程结束后继续回到应用层调用的地方,应用层获取了以上数据后就是对数据进程组装来生成ProcessGuid。

可以看到函数v7 = (const __m128i *)SysmonCreateGuid(&OutBuffer.TokenId, (int)&OutBuffer.CreateTime, &Guid, 0x10000000);传入的参数是TokenId、CreateTime
继续分析该函数内部实现

OutGuid的参数是v6

V6的data1 赋值为g_Guid_Data1,这个从哪里来的呢,答案很简单。

在另外一个函数里有个给g_Guid_Data1赋值的地方

可以看到读取注册表键值为MachineGuid的键值获取这个键值Guid的第一个Data1给g_Guid_Data1,网上翻阅,会看到

哦,答案一目了然了,是
v2 = RegOpenKeyW(HKEY_LOCAL_MACHINE, L”SOFTWARE\Microsoft\Cryptography”, &phkResult);

我们看下自己机器的注册表里

确实存在一个Guid,sysmon只去了第一个data1.

接着看(_DWORD )&v6->Data2 = v8;

V8 就是 进程的CreateTime,只是经过了

RtlTimeToSecondsSince1970(Time, &v8);的时间格式化

最后就是8个字节的数据

(_DWORD )v6->Data4 = GudingValue | TokenIdv5->HighPart;
(_DWORD )&v6->Data4[4] = TokenIdv5->LowPart;

GudingValue是哥固定值0x10000000

填充Tokenid的高位与低位

现在我们大概清楚ProcessGuid的算法就是

MachineIdData1-进程创建时间-TokenId组合而成的

以上是初始化的时候ProcessGuid过程,第二个部分是实时从内核获取进程线程事件后,Sleep(500u);每500毫秒从驱动内获得事件缓冲数据

然后调用SysmonDisplayEvent(lpOutBuffer);去解析事件

事件类型为7的时候,同样会调用以上函数Sysmon_DeviceIo_Process_Pid_Guid((_DWORD )v22, (GUID *)&v67, 4, SizeInWords);去生成ProcessGuid,事件类型7 就是我们上一篇文章讲的进程线程事件。

最后就是实验我们的结果,写代码

if (SUCCEEDED(StringCchPrintf(szDriverKey, MAX_PATH, _T(“\\.\ % s”), _T(“SysmonDrv”)))) {
    HANDLE hObjectDrv = CreateFile(szDriverKey, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    if (hObjectDrv != INVALID_HANDLE_VALUE) {
        LARGE_INTEGER Request;
        Request.LowPart = GetCurrentProcessId();
        Request.HighPart = FALSE;
        BYTE OutBuffer[4002] = {
            0
        };
        ULONG BytesReturned = 0;
        if (SUCCEEDED(DeviceIoControl(hObjectDrv, SYSMON_REQUEST_PROCESS_INFO, &Request, 8, OutBuffer, sizeof(OutBuffer), &BytesReturned, 0))) {

            if (BytesReturned) {
                Sysmon_Report_Process * pSysmon_Report_Process = (Sysmon_Report_Process * ) & OutBuffer[0];

                if (pSysmon_Report_Process - >Header.ReportSize) {

                    typedef void(__stdcall * pRtlTimeToSecondsSince1970)(LARGE_INTEGER * , LUID * );
                    LUID CreateTime;
                    GUID ProcessGuid = {
                        0
                    };
                    ProcessGuid.Data1 = 0x118a010c;
                    pRtlTimeToSecondsSince1970 RtlTimeToSecondsSince1970 = (pRtlTimeToSecondsSince1970) GetProcAddress(GetModuleHandle(_T("ntdll.dll")), "RtlTimeToSecondsSince1970");
                    if (RtlTimeToSecondsSince1970) {

                        RtlTimeToSecondsSince1970( & pSysmon_Report_Process - >CreateTime, &CreateTime); * (DWORD * ) & ProcessGuid.Data2 = CreateTime.LowPart; * (DWORD * ) ProcessGuid.Data4 = pSysmon_Report_Process - >TokenId.HighPart; * (DWORD * ) & ProcessGuid.Data4[4] = pSysmon_Report_Process - >TokenId.LowPart;
                    }
                    CheckServiceOk = TRUE;
                }
            }
        }
        CloseHandle(hObjectDrv);
        hObjectDrv = NULL;
    }
}

我们计算自己进程的结果是

ProcessGuid = {118A010C-7A53-5FAB-0000-0010B1AED716}

再看看sysmon里记录的记过是

是一样的,算法结果没问题

这篇文章就到此结束,希望能对读者有些帮助,当然我这个是v8的sysmon的版本对应的算法,最新版v11有点小的变动,但变化是在内核里的数据,其他变化不大,读者可以自己去研究。

(完)