阅读时间: 3-4 分钟
前置知识: 理解进程注入原理、Shellcode 绕过技巧、Windows API 基础
学习目标: 掌握 ETW 事件追踪,实现行为层检测


📺 配套视频教程

本文配套视频教程已发布在 B 站,建议结合视频学习效果更佳:

💡 提示: 点击视频右下角可全屏观看,建议配合文章食用!

视频链接: https://www.bilibili.com/video/BV1oU4gzCEUj/


承接上节

上节我们确认了一件事
只看起始地址会被 Shellcode 绕过

也留下了三个关键问题

  • 为什么突然出现新线程?
  • 谁创建了这个线程?
  • 正常程序会这样做吗?

本节给出直接答案
从”谁创建了线程”入手
用 ETW 还原”发起进程 → 目标进程”的创建关系
做一次可落地的行为检测


本节目标与策略

目标很明确:
不看线程从哪里开始
只看是谁创建了它

策略很简单:

  • 订阅线程创建事件
  • 拿到创建者 PID 与目标 PID
  • 不相等就是远程线程

不关心起始地址是不是 LoadLibrary
Shellcode 中转同样会被覆盖

这是一种行为检测
比地址特征更本质
也更难被简单绕过


什么是 ETW?

ETW,全称 Event Tracing for Windows
系统级事件追踪
性能开销小,覆盖面广

在安全产品与反作弊场景里
ETW 是常规的数据采集通道
用于还原进程间的行为链

常见监控维度包括:

  • 线程/进程事件(创建、退出)
  • 句柄打开与跨进程访问(OpenProcess/OpenThread)
  • 内存分配与权限变化(VirtualAllocEx/VirtualProtectEx)
  • 模块加载与映像校验(LoadLibrary/映像路径)

本节聚焦”线程创建”这一环
后续可叠加模块与内存画像,降低误报


实现步骤总览

  1. 启动内核会话(KERNEL_LOGGER_NAME)

    • 准备 PROPERTIES 结构
    • 填充会话名和缓冲区
    • 调用 StartTrace 启动
  2. 只启用线程事件(EVENT_TRACE_FLAG_THREAD)

    • 设置 EnableFlags
    • 减少无关数据
    • 提升监听效率
  3. 打开会话,注册事件回调

    • 配置 LOGFILEW 结构
    • 指定 EventRecordCallback
    • OpenTrace 获取句柄
    • ProcessTrace 阻塞读取
  4. 在回调里筛选”线程启动”事件

    • 检查 Opcode == START
    • 只处理创建那一刻
    • 跳过就绪和退出
  5. 读取关键字段,判断是否远程线程

    • 用 TDH 解析属性
    • 提取创建者和目标 PID
    • 判定:创建者 ≠ 目标
    • 打印远程线程信息

关键代码讲解

第1步:从 main 函数出发

先把主流程写出来
让程序能跑起来
再逐步补齐依赖

int main()
{
    std::cout << "=== 远程线程创建行为检测 ===nn";
    std::cout << "请输入需要监控的目标进程 PID (0 表示所有进程): ";
    DWORD pid = 0; std::cin >> pid;

    if (!StartMonitorSession(pid)) {
        std::cout << "初始化 ETW 监听失败(需要管理员权限)n";
        system("pause");
        return 1;
    }

    std::cout << "开始监听...n";
    ProcessTrace(&g_Context.traceHandle, 1, nullptr, nullptr);
    StopMonitorSession();
    std::cout << "监听结束n";
    system("pause");
    return 0;
}

主流程三步走:

  1. 启动 ETW 内核会话,只订阅线程事件,把事件回调绑定好
  2. 阻塞读取事件流;每条事件都会进入回调
  3. 先关读取,再停会话,资源收干净

第2步:补齐必要的包含与全局

#include <windows.h>
#include <evntrace.h>
#include <tdh.h>
#include <iostream>
#include <memory>

#pragma comment(lib, "tdh.lib")
#pragma comment(lib, "advapi32.lib")

static const GUID SystemTraceControlGuid = { 0x9e814aad, 0x3204, 0x11d2, { 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 } };

struct MonitorContext {
    TRACEHANDLE sessionHandle;
    TRACEHANDLE traceHandle;
    DWORD targetPid;
    EVENT_TRACE_PROPERTIES* properties;
    std::unique_ptr<BYTE[]> propertyBuffer;
};

static MonitorContext g_Context = {};

第3步:准备会话属性并启动会话

准备缓冲区:

g_Context.targetPid = targetPid;
std::wstring name = KERNEL_LOGGER_NAME;

size_t bytes = sizeof(EVENT_TRACE_PROPERTIES) + (name.size() + 1) * sizeof(wchar_t);
g_Context.propertyBuffer = std::make_unique<BYTE[]>(bytes);
ZeroMemory(g_Context.propertyBuffer.get(), bytes);
g_Context.properties = reinterpret_cast<EVENT_TRACE_PROPERTIES*>(g_Context.propertyBuffer.get());

填充属性:

g_Context.properties->Wnode.BufferSize = static_cast<ULONG>(bytes);
g_Context.properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
g_Context.properties->Wnode.Guid = SystemTraceControlGuid;
g_Context.properties->Wnode.ClientContext = 1;
g_Context.properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
g_Context.properties->EnableFlags = EVENT_TRACE_FLAG_THREAD;
g_Context.properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);

LPWSTR loggerName = reinterpret_cast<LPWSTR>(g_Context.propertyBuffer.get() + sizeof(EVENT_TRACE_PROPERTIES));
wcscpy_s(loggerName, name.size() + 1, name.c_str());

启动会话:

StopTrace(0, name.c_str(), g_Context.properties);

ULONG s = StartTrace(&g_Context.sessionHandle, name.c_str(), g_Context.properties);
if (s != ERROR_SUCCESS) {
    std::cout << "StartTrace 失败 错误码: " << s << "n";
    return false;
}
return true;

第4步:打开追踪并注册回调

EVENT_TRACE_LOGFILEW log = {};
log.LoggerName = const_cast<LPWSTR>(name.c_str());
log.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
log.EventRecordCallback = ThreadEventCallback;

g_Context.traceHandle = OpenTrace(&log);
if (g_Context.traceHandle == reinterpret_cast<TRACEHANDLE>(INVALID_HANDLE_VALUE)) {
    std::cout << "OpenTrace 失败n";
    StopTrace(g_Context.sessionHandle, name.c_str(), g_Context.properties);
    return false;
}
return true;

第5步:实现回调,判定远程线程

第一步:过滤事件,只处理 START

void WINAPI ThreadEventCallback(PEVENT_RECORD eventRecord)
{
    if (!eventRecord) return;
    if (eventRecord->EventHeader.EventDescriptor.Opcode != EVENT_TRACE_TYPE_START) return;

第二步:提取字段,获取关键数据

    DWORD creatorPid = eventRecord->EventHeader.ProcessId;
    DWORD targetPid = 0;
    DWORD threadId  = 0;
    ULONGLONG startAddr = 0;

    GetEventProperty(eventRecord, L"ProcessId", targetPid) ||
    GetEventProperty(eventRecord, L"TargetProcessId", targetPid);

    GetEventProperty(eventRecord, L"ThreadId", threadId) ||
    GetEventProperty(eventRecord, L"NewThreadId", threadId);

    GetEventProperty(eventRecord, L"StartAddress", startAddr) ||
    GetEventProperty(eventRecord, L"Win32StartAddr", startAddr);

第三步:判定输出,识别远程线程

    if (creatorPid != 0 && targetPid != 0 && creatorPid != targetPid) {
        if (g_Context.targetPid != 0 && targetPid != g_Context.targetPid) return;
        std::cout << "[!] 检测到远程线程创建n"
                  << "    发起进程 PID: " << creatorPid << "n"
                  << "    目标进程 PID: " << targetPid << "n"
                  << "    线程 ID: " << threadId << "n"
                  << std::hex << "    起始地址: 0x" << startAddr << std::dec << "n"
                  << "========================n";
    }
}

第6步:补齐 TDH 属性读取函数

template<typename T>
bool GetEventProperty(PEVENT_RECORD eventRecord, LPCWSTR propertyName, T& value)
{
    PROPERTY_DATA_DESCRIPTOR property = {};
    property.PropertyName = reinterpret_cast<ULONGLONG>(propertyName);
    property.ArrayIndex = ULONG_MAX;
    ULONG size = 0;
    if (TdhGetPropertySize(eventRecord, 0, nullptr, 1, &property, &size) != ERROR_SUCCESS) return false;
    std::vector<BYTE> buffer(size);
    if (TdhGetProperty(eventRecord, 0, nullptr, 1, &property, size, buffer.data()) != ERROR_SUCCESS) return false;
    value = *reinterpret_cast<T*>(buffer.data());
    return true;
}

第7步:完善清理函数

void StopMonitorSession()
{
    if (g_Context.traceHandle != 0) CloseTrace(g_Context.traceHandle);
    if (g_Context.sessionHandle != 0) StopTrace(g_Context.sessionHandle, KERNEL_LOGGER_NAME, g_Context.properties);
}

完整检测代码

/**
 * @file 03-防御篇-检测远程线程创建行为.cpp
 * @brief 基于 ETW 的远程线程创建行为检测工具
 * @details 使用 Windows ETW (Event Tracing for Windows) 技术监控系统级别的线程创建事件
 *          可以检测到包括 Shellcode 注入在内的所有远程线程创建行为
 * @author 教学示例
 * @date 2025
 * @note 需要管理员权限运行
 */

#include <windows.h>
#include <evntrace.h>
#include <tdh.h>
#include <iostream>
#include <vector>
#include <memory>

#pragma comment(lib, "tdh.lib")
#pragma comment(lib, "advapi32.lib")

/**
 * @brief Windows 系统跟踪控制 GUID
 * @details 用于启动内核级别的事件跟踪会话
 */
static const GUID SystemTraceControlGuid = { 0x9e814aad, 0x3204, 0x11d2, { 0x9a, 0x82, 0x00, 0x60, 0x08, 0xa8, 0x69, 0x39 } };

/**
 * @struct MonitorContext
 * @brief ETW 监控上下文结构体
 * @details 保存 ETW 会话的所有必要信息
 */
struct MonitorContext {
    TRACEHANDLE sessionHandle;              ///< ETW 会话句柄
    TRACEHANDLE traceHandle;                ///< ETW 跟踪句柄
    DWORD targetPid;                        ///< 目标进程 PID(0 表示监控所有进程)
    EVENT_TRACE_PROPERTIES* properties;     ///< ETW 会话属性指针
    std::unique_ptr<BYTE[]> propertyBuffer; ///< 属性缓冲区(包含会话名称)
};

/**
 * @brief 全局监控上下文
 */
static MonitorContext g_Context = {};

void StopMonitorSession(); // 前向声明,供控制台回调调用

/**
 * @brief 处理控制台信号,确保 Ctrl+C 等方式退出时清理 ETW 会话
 */
BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType) {
    switch (ctrlType) {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
    case CTRL_CLOSE_EVENT:
    case CTRL_LOGOFF_EVENT:
    case CTRL_SHUTDOWN_EVENT:
        std::cout << "n[提示] 收到退出信号,正在停止监听..." << std::endl;
        StopMonitorSession();
        return TRUE;
    default:
        return FALSE;
    }
}

// ==================== 辅助函数:解析事件属性 ====================

/**
 * @brief 从 ETW 事件中读取指定类型的属性(模板函数)
 * @tparam T 属性值类型(如 DWORD、ULONGLONG)
 * @param eventRecord ETW 事件记录指针
 * @param propertyName 属性名称(宽字符串)
 * @param[out] value 输出参数,存储读取到的值
 * @return 成功返回 true,失败返回 false
 * @details 使用 TDH (Trace Data Helper) API 解析事件属性
 * @see TdhGetPropertySize, TdhGetProperty
 */
template<typename T>
bool GetEventProperty(PEVENT_RECORD eventRecord, LPCWSTR propertyName, T& value) {
    // 准备属性描述符
    PROPERTY_DATA_DESCRIPTOR property = {};
    property.PropertyName = reinterpret_cast<ULONGLONG>(propertyName);
    property.ArrayIndex = ULONG_MAX;

    // 获取属性大小
    ULONG size = 0;
    if (TdhGetPropertySize(eventRecord, 0, nullptr, 1, &property, &size) != ERROR_SUCCESS) {
        return false;
    }

    // 读取属性值
    std::vector<BYTE> buffer(size);
    if (TdhGetProperty(eventRecord, 0, nullptr, 1, &property, size, buffer.data()) != ERROR_SUCCESS) {
        return false;
    }

    value = *reinterpret_cast<T*>(buffer.data());
    return true;
}

// ==================== 步骤一:处理线程创建事件 ====================

/**
 * @brief ETW 线程事件回调函数
 * @param eventRecord ETW 事件记录指针
 * @details 当系统中有线程创建时,ETW 会调用此回调函数
 *          本函数负责过滤出远程线程创建事件并打印信息
 * @note 此函数由 ProcessTrace 在内部调用,不应手动调用
 */
void  ThreadEventCallback(PEVENT_RECORD eventRecord) {
    if (!eventRecord) return;

    // 1.1 过滤:只处理线程启动事件
    if (eventRecord->EventHeader.EventDescriptor.Opcode != EVENT_TRACE_TYPE_START) {
        return;
    }

    // 1.2 提取关键信息
    DWORD creatorPid = eventRecord->EventHeader.ProcessId;  ///< 创建线程的进程 PID
    DWORD targetPid = 0;     ///< 线程所属进程 PID
    DWORD threadId = 0;      ///< 线程 ID
    ULONGLONG startAddr = 0; ///< 线程起始地址

    // 尝试获取目标进程 PID(不同 Windows 版本字段名可能不同)
    if (!GetEventProperty(eventRecord, L"ProcessId", targetPid)) {
        GetEventProperty(eventRecord, L"TargetProcessId", targetPid);
    }

    // 1.3 判断是否为远程线程(创建者 != 目标进程)
    if (creatorPid == 0 || targetPid == 0 || creatorPid == targetPid) {
        return; // 普通线程,忽略
    }

    // 1.3.1 过滤:忽略系统进程(PID 4)发起的远程线程
    if (creatorPid == 4) {
        return;
    }

    // 1.4 过滤:如果指定了监控目标,只显示目标进程的事件
    if (g_Context.targetPid != 0 && targetPid != g_Context.targetPid) {
        return;
    }

    // 1.5 获取线程 ID 和起始地址
    if (!GetEventProperty(eventRecord, L"ThreadId", threadId)) {
        GetEventProperty(eventRecord, L"NewThreadId", threadId);
    }

    if (!GetEventProperty(eventRecord, L"StartAddress", startAddr)) {
        GetEventProperty(eventRecord, L"Win32StartAddr", startAddr);
    }

    // 1.6 打印检测结果(红色高亮显示)
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_INTENSITY);
    std::cout << "n[!] 检测到远程线程创建" << std::endl;
    SetConsoleTextAttribute(hConsole, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    std::cout << "    发起进程 PID: " << creatorPid << std::endl;
    std::cout << "    目标进程 PID: " << targetPid << std::endl;
    std::cout << "    线程 ID: " << threadId << std::endl;
    std::cout << "    起始地址: 0x" << std::hex << startAddr << std::dec << std::endl;
    std::cout << "========================" << std::endl;
}

// ==================== 步骤二:启动 ETW 会话 ====================

/**
 * @brief 启动 ETW 监听会话
 * @param targetPid 目标进程 PID,传入 0 表示监听所有进程
 * @return 成功返回 true,失败返回 false
 * @details 完整的启动流程:
 *          1. 准备会话属性缓冲区
 *          2. 初始化 EVENT_TRACE_PROPERTIES 结构
 *          3. 停止可能存在的旧会话(清理残留)
 *          4. 调用 StartTrace 启动内核跟踪
 *          5. 调用 OpenTrace 打开跟踪会话并注册回调
 * @note 需要管理员权限,否则 StartTrace 会返回错误码 5 (ACCESS_DENIED)
 * @see StartTrace, OpenTrace, EVENT_TRACE_PROPERTIES
 */
bool StartMonitorSession(DWORD targetPid) {
    g_Context.targetPid = targetPid;

    // 2.1 准备会话属性缓冲区
    // 缓冲区包含:EVENT_TRACE_PROPERTIES + 会话名称字符串
    std::wstring sessionName = KERNEL_LOGGER_NAME;
    size_t bufferSize = sizeof(EVENT_TRACE_PROPERTIES) + (sessionName.size() + 1) * sizeof(wchar_t);
    g_Context.propertyBuffer = std::make_unique<BYTE[]>(bufferSize);

    // 2.2 初始化会话属性
    auto ResetProperties = [&]() {
        ZeroMemory(g_Context.propertyBuffer.get(), bufferSize);
        g_Context.properties = reinterpret_cast<EVENT_TRACE_PROPERTIES*>(g_Context.propertyBuffer.get());
        g_Context.properties->Wnode.BufferSize = static_cast<ULONG>(bufferSize);
        g_Context.properties->Wnode.Flags = WNODE_FLAG_TRACED_GUID;
        g_Context.properties->Wnode.Guid = SystemTraceControlGuid;
        g_Context.properties->Wnode.ClientContext = 1; // 使用 QueryPerformanceCounter 作为时间戳
        g_Context.properties->LogFileMode = EVENT_TRACE_REAL_TIME_MODE; // 实时模式,不写入日志文件
        g_Context.properties->EnableFlags = EVENT_TRACE_FLAG_THREAD;    // 监听线程事件
        g_Context.properties->LoggerNameOffset = sizeof(EVENT_TRACE_PROPERTIES);
        LPWSTR loggerName = reinterpret_cast<LPWSTR>(g_Context.propertyBuffer.get() + sizeof(EVENT_TRACE_PROPERTIES));
        wcscpy_s(loggerName, sessionName.size() + 1, sessionName.c_str());
    };

    ResetProperties();

    // 2.3 停止可能存在的旧会话(避免冲突)
    ULONG stopStatus = StopTrace(0, sessionName.c_str(), g_Context.properties);
    if (stopStatus == ERROR_SUCCESS) {
        std::cout << "[提示] 清理了残留的 ETW 会话,等待资源释放..." << std::endl;
        Sleep(500); // 增加等待时间,让系统有足够时间释放资源
    } else if (stopStatus != ERROR_WMI_INSTANCE_NOT_FOUND) {
        std::cout << "[提示] StopTrace 返回: " << stopStatus << std::endl;
    }

    ResetProperties(); // StopTrace 会覆写属性结构,重新写回关键字段

    // 2.4 启动新的跟踪会话(带重试机制)
    ULONG status = ERROR_ALREADY_EXISTS;
    for (int retry = 0; retry < 3 && status == ERROR_ALREADY_EXISTS; retry++) {
        if (retry > 0) {
            std::cout << "[提示] 会话仍在释放中,等待后重试 (" << retry << "/3)..." << std::endl;
            Sleep(500);
        }
        status = StartTrace(&g_Context.sessionHandle, sessionName.c_str(), g_Context.properties);
    }

    if (status != ERROR_SUCCESS) {
        std::cout << "StartTrace 失败,错误码: " << status << std::endl;
        if (status == ERROR_ALREADY_EXISTS) {
            std::cout << "[建议] 会话仍被占用,请稍后再试或重启系统" << std::endl;
        }
        return false;
    }

    // 2.5 打开跟踪会话并注册事件回调
    EVENT_TRACE_LOGFILEW logFile = {};
    logFile.LoggerName = const_cast<LPWSTR>(sessionName.c_str());
    logFile.ProcessTraceMode = PROCESS_TRACE_MODE_EVENT_RECORD | PROCESS_TRACE_MODE_REAL_TIME;
    logFile.EventRecordCallback = ThreadEventCallback; // 注册回调函数

    g_Context.traceHandle = OpenTrace(&logFile);
    if (g_Context.traceHandle == reinterpret_cast<TRACEHANDLE>(INVALID_HANDLE_VALUE)) {
        std::cout << "OpenTrace 失败" << std::endl;
        StopTrace(g_Context.sessionHandle, sessionName.c_str(), g_Context.properties);
        return false;
    }

    return true;
}

// ==================== 步骤三:停止监听 ====================

/**
 * @brief 停止 ETW 监听会话并清理资源
 * @details 清理流程:
 *          1. 关闭跟踪句柄 (CloseTrace)
 *          2. 停止跟踪会话 (StopTrace)
 * @note 程序退出前必须调用此函数,否则 ETW 会话可能残留在系统中
 * @see CloseTrace, StopTrace
 */
void StopMonitorSession() {
    if (g_Context.traceHandle != 0) {
        CloseTrace(g_Context.traceHandle);
        g_Context.traceHandle = 0;  // 重置句柄
    }
    if (g_Context.sessionHandle != 0) {
        EVENT_TRACE_PROPERTIES stopProps = {};
        stopProps.Wnode.BufferSize = sizeof(stopProps);
        StopTrace(g_Context.sessionHandle, KERNEL_LOGGER_NAME, &stopProps);
        g_Context.sessionHandle = 0;  // 重置句柄
    }
    // 清空缓冲区,释放内存
    g_Context.propertyBuffer.reset();
    g_Context.properties = nullptr;
    g_Context.targetPid = 0;
}

// ==================== 主程序 ====================

/**
 * @brief 主函数
 * @return 成功返回 0,失败返回 1
 * @details 程序流程:
 *          1. 输入目标进程 PID
 *          2. 启动 ETW 监听会话
 *          3. 处理事件(阻塞等待)
 *          4. 清理资源
 */
int main() {
    std::cout << "=== 远程线程创建行为检测 ===" << std::endl;
    std::cout << "当前进程 PID: " << GetCurrentProcessId() << std::endl;
    std::cout << std::endl;

    std::cout << "请输入需要监控的目标进程 PID (0 表示所有进程): ";
    DWORD targetPid = 0;
    std::cin >> targetPid;

    // 注册控制台信号处理,确保异常退出也能清理会话
    SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);

    // 步骤 1:启动 ETW 监听
    if (!StartMonitorSession(targetPid)) {
        std::cout << "初始化 ETW 监听失败(需要管理员权限)" << std::endl;
        system("pause");
        return 1;
    }

    std::cout << "开始监听..." << std::endl;

    // 步骤 2:处理事件(阻塞调用,Ctrl+C 会自动中断)
    ProcessTrace(&g_Context.traceHandle, 1, nullptr, nullptr);

    // 步骤 3:清理资源
    StopMonitorSession();

    std::cout << "监听结束" << std::endl;
    system("pause");
    return 0;
}

运行与验证

环境与权限

  • Windows 10/11 x64
  • Visual Studio 2022,x64 Debug
  • 以管理员权限运行(ETW 内核会话需要)

操作步骤

  1. 启动本节检测程序,输入目标进程 PID(0 表示所有进程)
  2. 运行上一节的 Shellcode 注入 PoC
  3. 返回本程序窗口,观察是否打印”远程线程创建”

检测效果验证

如图所示,程序成功检测到:

  • ✅ 远程线程创建事件
  • ✅ 发起进程和目标进程的 PID
  • ✅ 新创建线程的 ID 和起始地址
  • ✅ 即使使用 Shellcode 中转,行为层依然能捕获

优势、边界与白名单

优势

  • ✅ 与起始地址无关,覆盖 Shellcode 中转
  • ✅ 行为层检测,更难被简单改造绕过
  • ✅ 开销低,适合常驻监控

边界与可能的误报

  • ⚠️ 调试器、自动化工具可能合法创建远程线程
  • ⚠️ 某些安全/运维产品也会触发同类事件

建议:

  • 建立进程白名单(调试器、已知工具)
  • 结合时间窗口与频次阈值(短时间内大量远程线程更可疑)
  • 结合目标模块/内存区域属性进一步评分

常见故障排查

  • StartTrace 返回 5(ACCESS_DENIED)

    • 用管理员权限运行
  • OpenTrace 失败

    • 先 StopTrace 清理残留会话
  • 不打印任何事件

    • 确认 EVENT_TRACE_FLAG_THREAD 已启用
    • 目标 PID 过滤是否设置为 0(监听全局以便对照)

实战延伸

  • 用户态: 继续叠加模块归属检测
  • 内核态: 基于 NtCreateThreadEx 的回调或回溯调用栈
  • 关联: 把”谁创建了线程”与”线程执行了什么”结合成事件链

下节课预告

我们已经能抓到远程线程创建
攻击者下一步会怎么做?

不创建新线程
改为 APC 或劫持现有线程

下节课见:攻击篇 – 不使用远程线程的注入(APC/线程劫持)


互动讨论

欢迎在评论区讨论以下问题:

  1. ETW 除了监听线程创建,还可以监听哪些系统事件?
  2. 如果攻击者关闭 ETW 会话,防御者如何应对?
  3. 在实际应用中如何降低 ETW 监听的性能开销?

⚠️ 本课程内容仅供防御性安全研究与教学使用,请勿用于非法用途。

By UD2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注