获取SYS加载方源头进程的方案-基于ETW技术"

admin 2026-10-21 18:09:16 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

cover_image

获取SYS加载方源头进程的方案-基于ETW技术

沧海浮萍\
沧海浮萍\

看雪学苑

2026年7月9日 17:59
上海

在小说阅读器读本章

去阅读

在dll/sys的加载中,常规手段上有两种方法(当然还有其他的方法)实现这个过程的感知,一个是通过IMG镜像回调,另一个是通过MF文件过滤器的IRP\_MJ\_ACQUIRE\_FOR\_SECTION\_SYNCHRONIZATION 这个IRP,这两个方法都能发现镜像加载的过程。

对于dll来说,加载宿主的PID就在参数中,但是对于SYS驱动来说,获取到的PID基本就是System(4)这个进程了。

但在EDR或者其他安全产品中,我们始终需要想办法找到“发起方”,这个时候通过参数里的信息就不能满足了,需要借助其他辅助信息旁路来获取对应的宿主信息(当然不能说绝对,借助InfinityHook也是可以一步到位的),这里介绍的就是ETW。

通过ETW是如何拿到这个SYS加载的发起方呢?这里就需要简单介绍一下SYS在应用层常规的加载技术:

常见的驱动加载工具(例如 sc.exe、InstDrv、自研 loader)并不是直接把 SYS 映射进内核。 它们通常调用 OpenSCManagerW、CreateServiceW/OpenServiceW、StartServiceW, 通过本地 RPC/ALPC 把请求发送给 services.exe。随后由 SCM 在 services.exe 中完成驱动服务启动逻辑, 并调用 NtLoadDriver 进入内核。因此,ImageLoad 事件里看到 System 并不代表真实发起者就是 System。

下面是应用层加载流程的简述:

  1. 应用层加载 SYS 的典型 API 链:应用层加载驱动最常见的方式是把驱动注册成一个“驱动服务”。

核心 API 链如下:

OpenSCManagerW()
    -> CreateServiceW(..., SERVICE_KERNEL_DRIVER, ..., ImagePath, ...)
       或 OpenServiceW()
    -> StartServiceW()
    -> CloseServiceHandle()
典型代码形态如下:

SC_HANDLE scm = OpenSCManagerW(
    NULL,
    NULL,
    SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);

SC_HANDLE svc = CreateServiceW(
    scm,
    L\"EDRSim\",
    L\"EDRSim\",
    SERVICE_START | DELETE | SERVICE_STOP,
    SERVICE_KERNEL_DRIVER,
    SERVICE_DEMAND_START,
    SERVICE_ERROR_NORMAL,
    L\"C:\\\\Users\\\\Administrator\\\\Desktop\\\\bin\\\\EDRSim.sys\",
    NULL,
    NULL,
    NULL,
    NULL,
    NULL);

StartServiceW(svc, 0, NULL);

API说明:

2.CreateServiceW 写入的服务注册表结构:CreateServiceW 会在 SCM 数据库中创建服务对象。对本机系统而言,服务配置最终落在注册表:

HKLM\\SYSTEM\\CurrentControlSet\\Services\\<ServiceName>

对于驱动服务,常见字段如下:

当后续调用StartServiceW时,用户态传递的是服务名,而不是直接把SYS 文件交给内核。 SCM 会把服务名解析到上述注册表路径,再构造 Native API 所需的驱动服务路径:

\\Registry\\Machine\\System\\CurrentControlSet\\Services\\<ServiceName>
  1. 从 Win32 API 到 services.exe:RPC 与 ALPC

OpenSCManagerW、CreateServiceW、OpenServiceW、StartServiceW这些Win32 API 暴露在 advapi32.dll中。应用程序调用它们时,实际并不会在当前进程内直接执行服务数据库修改和驱动加载。这些请求会被转换成发往SCM 服务进程 services.exe的RPC 调用。

本机场景通常使用本地 RPC 协议序列 ncalrpc。从 Win32 API 视角看,这是 RPC runtime 的内部细节; 从 ETW 视角看,本地 RPC 在现代 Windows 中会落到 LPC/ALPC 通信上,因此可以通过 Kernel ALPC 相关事件观察到.

4.链路拓扑

5.services.exe 内部如何触发 NtLoadDriver

services.exe 收到 StartServiceW 对应的 SCM RPC 请求后,会检查服务配置。 如果服务类型是 SERVICE\_KERNEL\_DRIVER 或 SERVICE\_FILE\_SYSTEM\_DRIVER,它不会像普通 Win32 服务那样创建用户态服务进程, 而是进入驱动服务启动路径。

核心动作可以概括为:

1. 根据服务名定位 HKLM\\SYSTEM\\CurrentControlSet\\Services\\<ServiceName>
2. 检查 Type/Start/ErrorControl/ImagePath 等配置;
3. 构造 Native 注册表路径:
   \\Registry\\Machine\\System\\CurrentControlSet\\Services\\<ServiceName>
4. 调用 NtLoadDriver(RegistryPath),到达内核;
5. 内核读取服务项,解析 ImagePath;
6. 内核映射 .sys 镜像,创建 DRIVER_OBJECT;
7. 调用 DriverEntry(DriverObject, RegistryPath);

从内核角度看,ZwLoadDriver的参数不是普通DOS 文件路径,而是驱动服务注册表路径。 这也是为什么很多驱动加载流程必须先写入 Services注册表项,随后才能启动。

接下来就是ETW   的事件关联了,由于应用程序加载sys与ETW事件的获取严格意义上是“异步”的,所以需要借助事件与时间的关联来进行识别:

当捕获到 .sys Kernel ImageLoad事件时:
    1. 取 image path 和时间戳 T2
    2. 在 T2 前的短时间窗口内查找 services.exe 相关 SCM RPC/ALPC 请求
    3. 优先匹配 StartService/OpenService/CreateService 语义
    4. 若能拿到服务名,则解析 Services\\Name\\ImagePath 与 image path 归一化比较
    5. 输出 RPC/ALPC 客户端进程作为 loader source
    6. 若找不到 SCM 链路,再降级输出 System/Unknown,并标记为“未完成源头关联”

根据前面一大堆啰嗦的流程描述,下面开始看代码:

【为了提高开发效率,这里的ETW解析库直接使用了开源项目“krabsetw”编译出来的静态库“etwtracelib.lib”】

我们在“etwtracelib”项目中增加几个关心的ETW回调,并创建一个lib的导出函数“EtwCopyStartTrace”供exe使用,核心代码如下:

// 导出函数入口。函数内部同时启动 Kernel Trace 和 User
// Trace,前者采集 ImageLoad/Process/ALPC,后者采集 RPC。该函数不会主动
// 返回,调用方应放到工作线程里运行。
extern \"C\" void EtwCopyStartTrace()
{
    try
    {
        krabs::kernel_trace kernelTrace(L\"ETWMonitor-Kernel\");
        krabs::user_trace rpcTrace(L\"ETWMonitor-RPC\");
        krabs::kernel::image_load_provider imageLoadProvider;
        krabs::kernel::process_provider processProvider;
        krabs::kernel::alpc_provider alpcProvider;
        krabs::provider<> rpcProvider(L\"Microsoft-Windows-RPC\");

        //******** 下面是几个关键的ETW追踪函数,实现几个事件的联合跟踪,这几个函数的细节代码篇幅过大,就不贴了,大家可以下载代码自己看 *******
        EtwCopySetupImageLoadProvider(imageLoadProvider);
        EtwCopySetupProcessProvider(processProvider);
        EtwCopySetupAlpcProvider(alpcProvider);
        EtwCopySetupRpcProvider(rpcProvider);

        // Kernel providers can share one kernel trace. The RPC provider is a
        // normal user-mode provider and must be enabled on a user trace.
        kernelTrace.enable(imageLoadProvider);
        kernelTrace.enable(processProvider);
        kernelTrace.enable(alpcProvider);
        rpcTrace.enable(rpcProvider);
        // krabs::trace::start is blocking, so each trace runs on its own
        // thread. The outer loop intentionally keeps the library alive.
        std::thread kernelThread([&kernelTrace]()
            {
                try
                {
                    kernelTrace.start();
                }
                catch (const std::runtime_error& error)
                {
                    std::cerr << error.what() << std::endl;
                    kernelTrace.stop();
                }
            });
        std::thread rpcThread([&rpcTrace]()
            {
                try
                {
                    rpcTrace.start();
                }
                catch (const std::runtime_error& error)
                {
                    std::cerr << error.what() << std::endl;
                    rpcTrace.stop();
                }
            });
        while (true)
        {
            Sleep(1000);
        }
        kernelTrace.stop();
        rpcTrace.stop();
        kernelThread.join();
        rpcThread.join();
    }
    catch (const std::runtime_error& error)
    {
        std::cerr << error.what() << std::endl;
    }
}

下面是调用lib的exe测试程序的代码:

/*
 * ETWMonitor 测试程序。
 *
 * 设计说明:
 *   该程序只负责注册回调、启动 etwtracelib 中的 ETW 采集逻辑,并打印
 *   SYS 加载结果。RPC/ALPC 溯源、路径归一化、ImageLoad 解析都放在
 *   etwtracelib 副本库中实现。
 *
 */
#include <Windows.h>
#include <cstdio>
#include <thread>
#if defined(_M_X64) && defined(NDEBUG)
#pragma comment(lib, \"etwtracelib.lib\")
#elif defined(_M_X64)
#pragma comment(lib, \"etwtracelib.lib\")
#else
#pragma comment(lib, \"etwtracelib.lib\")
#endif
// 必须和 etwtracelib 副本里的 IMAGE_LOAD_OPERATE 保持二进制兼容。
// iTypeId == 4 时,cBuf 指向该结构;如果库侧字段布局变化,这里也要同步更新。
typedef struct _IMAGE_LOAD_OPERATE
{
    ULONG ulLoaderPid;              // 修正后的加载发起方 PID;SYS 场景下通常来自 RPC/ALPC 溯源。
    ULONG ulTargetPid;              // 镜像加载目标进程 PID;SYS 通常没有普通用户态目标进程。
    ULONG ulImageType;              // 1 表示 DLL,2 表示 SYS。
    char cLoaderPath[1024];         // 修正后的加载发起方进程路径。
    char cTargetProcessPath[1024];  // 镜像加载目标进程路径。
    char cImagePath[1024];          // 被加载的 DLL/SYS 镜像路径。
}
IMAGE_LOAD_OPERATE, * PIMAGE_LOAD_OPERATE;
typedef void(*FUNC_DoCmd)(int iTypeId, char* cBuf, int ilen);

// 静态库对外暴露的新入口名。副本故意与原始工程不同,便于区分符号。
extern \"C\" void EtwCopyStartTrace();
extern \"C\" void EtwCopySetCallback(FUNC_DoCmd pFunc);

// 库回调函数。库侧会同时上报 DLL/SYS,但当前 exe 只验证 SYS 加载溯源,
// 因此这里再过滤一次,只打印 ulImageType == 2 的记录。
staticvoid EtwCopyOnEtwImageEvent(int iTypeId, char* cBuf, int ilen){
    // iTypeId 4 对应 IMAGE_LOAD_OPERATE。长度检查用于防止库/测试程序版本
    // 不一致时 memcpy 越界或解析错位。
    if (iTypeId != 4 || ilen != sizeof(IMAGE_LOAD_OPERATE))
    {
        return;
    }
    IMAGE_LOAD_OPERATE imageInfo = { 0 };
    memcpy(&imageInfo, cBuf, sizeof(imageInfo));
    if (imageInfo.ulImageType != 2)
    {
        // 控制台暂时只输出 SYS;DLL 事件仍然由库侧采集,只是在测试程序中过滤。
        return;
    }
    printf(\"IMAGE[SYS] loaderPid:%lu loader:%s targetPid:%lu target:%s image:%s\
\",
        imageInfo.ulLoaderPid,
        imageInfo.cLoaderPath,
        imageInfo.ulTargetPid,
        imageInfo.cTargetProcessPath,
        imageInfo.cImagePath);
}
int main(){
    // 先注册回调,再启动 ETW trace;否则早期 ImageLoad 事件可能已经被采集,
    // 但由于没有回调而无法交给测试程序打印。
    EtwCopySetCallback(EtwCopyOnEtwImageEvent);
    // EtwCopyStartTrace 是阻塞式监控循环,所以放到工作线程中运行。
    // 主线程 join 等待,使该程序表现为一个持续运行的控制台监控器。
    std::thread traceThread([]()
        {
            EtwCopyStartTrace();
        });
    traceThread.join();
    return 0;
}

验证结果如图:

看雪ID:沧海浮萍\_

https://bbs.kanxue.com/user-home-804146.htm

\*本文为看雪论坛优秀文章,由 沧海浮萍\_ 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

往期推荐

libmsaoaidsec.so 检测监测——绕过 Hook 脚本

Android 逆向 Multi-Agent 系统设计

整理Windows 全架构 Hook 技术图谱:从 Ring3 到固件层 34 种实现

libmsaoaidsec.so 检测体系分析

从0到1构建一个Hook工具之Frida-like风格的Hook

球分享

球点赞

球在看

点击阅读原文查看更多


获取SYS加载方源头进程的方案-基于ETW技术" 网络安全文章

获取SYS加载方源头进程的方案-基于ETW技术"

本文介绍基于ETW技术获取SYS驱动加载发起方进程的方案。常规ImageLoad回调只能获取SystemPID,无法识别真实发起方。文章详细分析了应用层通过SCM和RPC/ALPC加载驱动的流程,并提出通过关联KernelImageLoad、ALPC和RPC事件,在时间窗口内溯源到services.exe的RPC客户端进程,从而确定加载源。提供了基于krabsetw库的代码实现,包括KernelTrace和UserTrace的联合采集与事件关联逻辑。
【高危漏洞预警】GhostLock(CVE-2026-43499):Linux内核15年栈UAF漏洞,本地提权与容器逃逸风险通告" 网络安全文章

【高危漏洞预警】GhostLock(CVE-2026-43499):Linux内核15年栈UAF漏洞,本地提权与容器逃逸风险通告"

2026年7月7日公开的Linux内核高危漏洞CVE-2026-43499(GhostLock)存在于rtmutex子系统的remove\\_waiter()函数中,为栈释放后使用漏洞,影响2.6.39至7.1版本内核。攻击者利用本地普通权限通过构造futex系统调用序列可触发内存损坏,实现内核提权至root并完成容器逃逸,攻击稳定性达97%。官方补丁已发布,建议立即升级内核或采取限制本地访问、启用内核加固等临时缓解措施。
慢雾招募令|AML产品GTM市场负责人" 网络安全文章

慢雾招募令|AML产品GTM市场负责人"

慢雾科技正在招聘AML产品GTM市场负责人,负责MistTrack/SlowMistKYT产品的市场进入策略、产品定位、内容营销、销售赋能及增长转化。岗位要求5年以上B2BSaaS或相关领域经验,熟悉CryptoAML和区块链分析。工作地点可选厦门、深圳或香港,薪资面议。符合条件的候选人可投递简历至[email protected]。
评论:0   参与:  0