阅读时间: 8-10 分钟
前置知识: 理解进程注入技术、Windows API 基础、数字签名概念
学习目标: 掌握模块归属检测技术,实现基于数字签名的进程防御系统


📺 配套视频教程

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

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

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


06-防御篇-检测模块归属(博客版)

承接上节:攻击者的致命疏忽

上节课,攻击者通过 APC 注入成功绕过了我们的 ETW 监控:

  • 绕过 ETW 监控:不创建新线程,复用现有线程的 APC 队列
  • 利用 Alertable 状态:等待线程进入可警告状态后执行 Shellcode
  • 隐蔽执行:ETW 事件中看不到远程线程创建的痕迹

但是,攻击者忽略了一个关键问题:无论注入方式多么隐蔽,恶意代码最终要以某种形式存在于目标进程的内存空间中

作为防御者,我们的思路是:不监控注入行为,而是检查进程内存中已经存在的模块,通过模块归属来判断是否存在恶意注入

防御者的思考:为什么需要模块归属检测

传统检测方法的局限性:

  1. 线程起始地址检测:只能检测直接使用 LoadLibrary 的注入
  2. ETW 事件监控:只能检测创建新线程的注入行为
  3. 行为监控:攻击者可以通过各种方式绕过行为记录

模块归属检测的优势:

  • 技术无关性:不管注入方式如何(远程线程、APC、线程劫持等),恶意代码模块都会出现在进程空间
  • 事后检测能力:即使注入过程被绕过,仍能发现已加载的恶意模块
  • 简单可靠:基于文件路径的黑白名单判断,逻辑清晰,误报率低

核心原理讲解

模块归属检测的基本概念

每个 Windows 进程都有一个模块列表,记录了所有加载到该进程地址空间的 DLL 和 EXE 文件。我们可以通过枚举这些模块,检查它们的可信度来判断是否存在恶意注入。

路径检测的局限性:
通常系统模块都在 C:WindowsSystem32 目录下,但只检测路径是很不可靠的:

  • 路径伪装:攻击者可以将恶意DLL放到系统目录下伪装成系统模块
  • 无法识别可信第三方软件:其他厂商的合法软件不在系统目录
  • 准确性有限:单纯路径检查无法提供可靠的可信度判断

更好的解决方案:数字签名验证
数字签名是现代软件可信度判断的标准:

  • Microsoft 官方签名:系统模块的可信度保证
  • 第三方厂商签名:合法商业软件的身份认证
  • 无签名模块:极有可能是恶意代码或未经验证的软件

基于数字签名的模块分类:

进程模块分类:
├── 有数字签名的模块 (Trusted Modules)
│   ├── Microsoft 签名的系统模块
│   ├── 其他可信厂商签名的模块
│   └── 有效数字签名的任意模块
├── 白名单模块 (Whitelisted Modules)
│   └── 我们自己的程序文件
└── 可疑模块 (Suspicious Modules)
    └── 无数字签名或签名无效的模块

检测逻辑的核心思想

核心假设:

  • 可信模块:具有有效数字签名的模块,包括系统模块和可信第三方模块
  • 白名单模块:我们自己的程序,明确可信(即使没有签名)
  • 可疑模块:没有数字签名或签名验证失败的模块,需要重点关注

判断流程:

枚举进程模块 → 验证数字签名 → 模块分类 → 输出可疑模块

数字签名验证的实际应用

在实际应用中,数字签名也常常被用于验证软件的可信度:

1. 游戏反外挂系统

外挂程序通常没有有效的数字签名,游戏客户端通过验证所有加载模块的签名来检测和阻止外挂注入。

2. 杀毒软件的自我保护

杀毒软件监控自身进程,阻止未签名的恶意模块注入,保护核心功能不被破坏。

动手实现:构建实时模块监控器

从 main 函数开始

首先创建程序的入口点,建立实时监控的基本框架:

#include <windows.h>
#include <psapi.h>
#include <iostream>
#include <string>
#include <algorithm>
#include <wintrust.h>

#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "wintrust.lib")

// 函数声明(我们先声明函数,稍后实现具体内容)
void DetectSuspiciousModules(DWORD processId);

/**
 * @brief 程序入口
 * @return 程序退出码
 */
int main()
{
    // 获取并打印当前进程ID(只需要获取一次)
    DWORD currentPid = GetCurrentProcessId();

    // 无限循环检测
    while (true)
    {
        // 执行检测逻辑
        DetectSuspiciousModules(currentPid);

        // 等待1秒后自动重新检测
        Sleep(1000);

        // 清屏并重新开始
        system("cls");
    }  // 无限循环

    return 0;
}

知识点讲解:

  • GetCurrentProcessId():获取当前进程的唯一标识符
  • while(true):建立无限循环,实现持续监控
  • Sleep(1000):暂停1秒,控制检测频率
  • system("cls"):清空控制台屏幕
  • #pragma comment(lib, ...):告诉链接器自动链接指定的库
  • 函数声明:在实际编程中,当我们需要使用还未实现的函数时,通常先声明函数名,稍后再实现

第一步:获取进程句柄

// ========== 第一步:获取进程句柄 ==========
HANDLE hProcess = GetCurrentProcess();

为什么使用 GetCurrentProcess()?

  • 返回当前进程的伪句柄(特殊值 -1)
  • 伪句柄不需要调用 CloseHandle() 关闭
  • 如果要检测其他进程,需要使用 OpenProcess() 并处理权限问题

第二步:枚举进程模块

// ========== 第二步:枚举进程模块 ==========
HMODULE hModules[1024];
DWORD cbNeeded;

if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
{
    std::cout << "获取模块列表失败rn";
    return;
}

DWORD moduleCount = cbNeeded / sizeof(HMODULE);
std::cout << "当前进程共有 " << moduleCount << " 个模块rnrn";

EnumProcessModules API 的使用:

  • hProcess:进程句柄
  • hModules:模块句柄数组(输出)
  • sizeof(hModules):数组大小
  • &cbNeeded:实际使用的字节数(输出)
  • 1024 是经验值,一般进程不会加载超过这个数量的模块

第三步:初始化统计变量

// ========== 第三步:初始化统计变量 ==========
int suspiciousCount = 0;
int systemCount = 0;
int whitelistCount = 0;

为什么设计三类统计变量?

  • systemCount:有数字签名的可信模块
  • whitelistCount:我们自己的程序文件
  • suspiciousCount:无签名且不在白名单中的可疑模块
  • “可疑”比”恶意”更准确,体现了安全检测的谨慎态度

第四步:遍历检测每个模块

// ========== 第四步:遍历检测每个模块 ==========
for (DWORD i = 0; i < moduleCount; i++)
{
    // 获取模块完整路径
    char modulePath[MAX_PATH];
    if (GetModuleFileNameExA(hProcess, hModules[i], modulePath, MAX_PATH))
    {
        std::string fullPath = modulePath;

        // 从完整路径中提取文件名(用于白名单检查)
        size_t lastSlash = fullPath.find_last_of("\");
        std::string fileName = (lastSlash != std::string::npos) ?
            fullPath.substr(lastSlash + 1) : fullPath;

        // 模块分类判断:数字签名 > 白名单 > 可疑
        if (VerifyDigitalSignature(fullPath))
        {
            // 有数字签名的模块视为可信系统模块
            systemCount++;
        }
        else if (IsWhitelistDLL(fileName))
        {
            // 我们自己的程序文件,即使没有签名也是可信的
            whitelistCount++;
        }
        else
        {
            // 无签名且不在白名单中的模块,标记为可疑
            suspiciousCount++;
            std::cout << "🚨 [可疑模块] " << fileName << "rn";
            std::cout << "    路径: " << fullPath << "rn";
            std::cout << "    基址: 0x" << std::hex << (uintptr_t)hModules[i] << std::dec << "rn";

            // 获取模块的详细信息(大小)
            MODULEINFO moduleInfo;
            if (GetModuleInformation(hProcess, hModules[i], &moduleInfo, sizeof(moduleInfo)))
            {
                std::cout << "    大小: " << moduleInfo.SizeOfImage << " 字节rn";
            }
            std::cout << "rn";
        }
    }
}

为什么需要两种路径形式?

  • 完整路径:数字签名验证需要检查文件的完整位置
  • 文件名:白名单检查只需要文件名,更灵活

三层分类逻辑的优先级设计:

  • VerifyDigitalSignature():第一层,最客观的可信标准
  • IsWhitelistDLL():第二层,我们的程序,主观可信
  • else:第三层,其他所有情况,需要人工判断

为什么用 else if 链?

  • 提高效率:满足第一个条件后不再检查后续条件
  • 逻辑清晰:明确的优先级关系
  • 避免重复:一个模块只属于一个类别

第五步:输出统计结果

// ========== 第五步:输出统计结果 ==========
std::cout << "=== 检测结果 ===rn";
std::cout << "系统模块: " << systemCount << " 个(已过滤)rn";
std::cout << "白名单模块: " << whitelistCount << " 个rn";
std::cout << "可疑模块: " << suspiciousCount << " 个rnrn";

if (suspiciousCount > 0)
{
    std::cout << "⚠️  检测到可疑模块!可能遭受注入攻击!rn";
    std::cout << "建议检查这些模块是否为您手动加载的DLL。rn";
}
else
{
    std::cout << "✓ 未检测到可疑模块,进程安全。rn";
}

std::cout << "rn1秒后自动重新检测,Ctrl+C 退出程序...rn";

第六步:实现数字签名验证功能

在主检测函数中,我们需要VerifyDigitalSignature函数来判断模块是否有数字签名。这是模块可信度判断的核心功能:

/**
 * @brief 验证文件数字签名
 * @param filePath 文件路径
 * @return 签名验证成功返回TRUE,失败返回FALSE
 */
BOOL VerifyDigitalSignature(const std::string& filePath)
{
    // 检查文件是否存在
    DWORD fileAttr = GetFileAttributesA(filePath.c_str());
    if (fileAttr == INVALID_FILE_ATTRIBUTES)
    {
        return FALSE;
    }

    // 转换为宽字符(Windows API通常需要Unicode)
    std::wstring widePath(filePath.begin(), filePath.end());

    // 设置文件信息结构
    WINTRUST_FILE_INFO fileInfo = {0};
    fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
    fileInfo.pcwszFilePath = widePath.c_str();

    // 设置WinTrust验证数据结构
    WINTRUST_DATA winTrustData = {0};
    winTrustData.cbStruct = sizeof(WINTRUST_DATA);
    winTrustData.pPolicyCallbackData = NULL;
    winTrustData.pSIPClientData = NULL;
    winTrustData.dwUIChoice = 2;  // WTD_UI_NONE - 禁用UI提示
    winTrustData.fdwRevocationChecks = 0;  // WTD_REVOKE_NONE - 不检查吊销
    winTrustData.dwUnionChoice = 1;  // WTD_CHOICE_FILE - 验证文件
    winTrustData.dwStateAction = 1;  // WTD_STATEACTION_VERIFY - 执行验证
    winTrustData.hWVTStateData = NULL;
    winTrustData.pwszURLReference = NULL;
    winTrustData.dwProvFlags = 0x1000;  // WTD_SAFER_FLAG - 安全模式
    winTrustData.pSignatureSettings = NULL;
    winTrustData.pFile = &fileInfo;

    // 定义Windows验证策略GUID
    GUID policyGUID = {0xaac56b, 0xcd44, 0x11d0, {0x8c, 0xc2, 0x00, 0xc0, 0x4f, 0xc2, 0x95, 0xee}};

    // 执行数字签名验证
    LONG result = WinVerifyTrust(NULL, &policyGUID, &winTrustData);

    // 返回验证结果:ERROR_SUCCESS 表示验证成功
    return (result == ERROR_SUCCESS);
}

知识点讲解:

  • GetFileAttributesA:检查文件是否存在,避免验证不存在的文件
  • WinVerifyTrust:Windows核心的数字签名验证API
  • WINTRUST_FILE_INFO:描述要验证的文件信息
  • WINTRUST_DATA:控制验证行为的详细参数
  • 关键配置参数说明:
    • dwUIChoice = 2:禁用所有用户界面提示
    • fdwRevocationChecks = 0:不检查证书吊销状态(提高速度)
    • dwProvFlags = 0x1000:启用更安全的验证模式

第七步:实现白名单检查功能

为了减少误报,我们需要实现IsWhitelistDLL函数来识别我们自己的程序文件:

/**
 * @brief 检查是否为白名单DLL(我们的程序)
 * @param dllName DLL名称
 * @return 如果是白名单DLL返回TRUE
 */
BOOL IsWhitelistDLL(const std::string& dllName)
{
    // 转换为小写进行比较,确保大小写不敏感
    std::string lowerDllName = dllName;
    std::transform(lowerDllName.begin(), lowerDllName.end(), lowerDllName.begin(), ::tolower);

    // 定义白名单数组(我们的程序文件)
    const std::string whitelistDLLs[] = {
        "uxtheme.dll",
        "06-防御篇-检测模块归属.exe"
    };

    // 遍历白名单数组进行检查
    for (const auto& whitelistDLL : whitelistDLLs)
    {
        if (lowerDllName == whitelistDLL)
        {
            return TRUE;
        }
    }

    return FALSE;
}

知识点讲解:

  • std::transform:C++标准库算法,用于字符大小写转换
  • ::tolower:C标准库函数,将字符转换为小写
  • 白名单机制:预先定义的可信文件列表,避免误报
  • 大小写不敏感比较:确保匹配的准确性

核心步骤的深度解析

获取进程句柄的考虑

在实际编程中,要访问进程信息首先需要进程句柄,这就像要进入房间需要钥匙一样。

  • GetCurrentProcess() 返回当前进程的伪句柄(特殊值 -1)
  • 伪句柄不需要调用 CloseHandle() 关闭
  • 如果要检测其他进程,需要使用 OpenProcess() 并处理权限问题

枚举模块的完整流程

“获取模块列表”是一个完整的业务目标,包含:

  • 准备存储空间:HMODULE hModules[1024]
  • 调用系统API:EnumProcessModules
  • 处理返回结果:检查返回值和错误处理
  • 计算有效数据:DWORD moduleCount = cbNeeded / sizeof(HMODULE)

关键技术理解:

  • 1024 是经验值,一般进程不会加载超过这个数量的模块
  • 如果不够大,cbNeeded 会告诉我们实际需要多少空间
  • 错误处理要检查返回值而不是依赖异常

分类统计的设计思路

我们为什么设计三类而不是简单的两类(可信/不可信)?

  1. 数字签名模块:客观可信,系统标准
  2. 白名单模块:主观可信,我们自己的程序
  3. 可疑模块:需要人工判断的其他模块

变量命名的含义:

  • suspiciousCount:为什么不是 badCount
    • 体现了安全检测的谨慎态度
    • 我们的检测方法可能有误报
    • “可疑”比”恶意”更准确

遍历检测的核心逻辑

这是最复杂的步骤,包含了完整的检测逻辑:

路径获取与处理:

std::string fullPath = modulePath;  // 完整路径,用于数字签名验证
std::string fileName = ...;         // 文件名,用于白名单检查
  • 完整路径:数字签名验证需要检查文件的完整位置
  • 文件名:白名单检查只需要文件名,更灵活

三层分类逻辑的优先级设计:

if (VerifyDigitalSignature(fullPath)) {
    // 第一层:最客观的可信标准
}
else if (IsWhitelistDLL(fileName)) {
    // 第二层:我们的程序,主观可信
}
else {
    // 第三层:其他所有情况,需要人工判断
}

为什么用 else if 链而不是独立 if

  • 提高效率:满足第一个条件后不再检查后续条件
  • 逻辑清晰:明确的优先级关系
  • 避免重复:一个模块只属于一个类别

完整代码

#include <windows.h>
#include <psapi.h>
#include <iostream>
#include <string>
#include <algorithm>
#include <wintrust.h>

#pragma comment(lib, "psapi.lib")
#pragma comment(lib, "wintrust.lib")

/**
 * @brief 验证文件数字签名
 * @param filePath 文件路径
 * @return 签名验证成功返回TRUE,失败返回FALSE
 */
BOOL VerifyDigitalSignature(const std::string& filePath)
{
    // 检查文件是否存在
    DWORD fileAttr = GetFileAttributesA(filePath.c_str());
    if (fileAttr == INVALID_FILE_ATTRIBUTES)
    {
        return FALSE;
    }

    // 转换为宽字符
    std::wstring widePath(filePath.begin(), filePath.end());

    // 设置文件信息
    WINTRUST_FILE_INFO fileInfo = {0};
    fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
    fileInfo.pcwszFilePath = widePath.c_str();

    // 设置WinTrust数据
    WINTRUST_DATA winTrustData = {0};
    winTrustData.cbStruct = sizeof(WINTRUST_DATA);
    winTrustData.pPolicyCallbackData = NULL;
    winTrustData.pSIPClientData = NULL;
    winTrustData.dwUIChoice = 2;  // WTD_UI_NONE - 禁用UI提示
    winTrustData.fdwRevocationChecks = 0;  // WTD_REVOKE_NONE - 不检查吊销
    winTrustData.dwUnionChoice = 1;  // WTD_CHOICE_FILE - 验证文件
    winTrustData.dwStateAction = 1;  // WTD_STATEACTION_VERIFY - 执行验证
    winTrustData.hWVTStateData = NULL;
    winTrustData.pwszURLReference = NULL;
    winTrustData.dwProvFlags = 0x1000;  // WTD_SAFER_FLAG - 安全模式
    winTrustData.pSignatureSettings = NULL;
    winTrustData.pFile = &fileInfo;

    // 定义策略GUID
    GUID policyGUID = {0xaac56b, 0xcd44, 0x11d0, {0x8c, 0xc2, 0x00, 0xc0, 0x4f, 0xc2, 0x95, 0xee}};

    // 执行验证
    LONG result = WinVerifyTrust(NULL, &policyGUID, &winTrustData);

    // 返回验证结果
    return (result == ERROR_SUCCESS);
}

/**
 * @brief 检查是否为白名单DLL(我们的程序)
 * @param dllName DLL名称
 * @return 如果是白名单DLL返回TRUE
 */
BOOL IsWhitelistDLL(const std::string& dllName)
{
    // 转换为小写进行比较
    std::string lowerDllName = dllName;
    std::transform(lowerDllName.begin(), lowerDllName.end(), lowerDllName.begin(), ::tolower);

    // 我们的程序白名单
    const std::string whitelistDLLs[] = {
        "uxtheme.dll",
        "06-防御篇-检测模块归属.exe"
    };

    for (const auto& whitelistDLL : whitelistDLLs)
    {
        if (lowerDllName == whitelistDLL)
        {
            return TRUE;
        }
    }

    return FALSE;
}

/**
 * @brief 检测进程中的可疑模块
 * @param processId 进程ID(用于显示)
 */
void DetectSuspiciousModules(DWORD processId)
{
    std::cout << "=== 简单模块检测器(教学版) ===rn";
    std::cout << "当前进程ID: " << processId << "rn";
    std::cout << "正在检测进程中的可疑模块...rnrn";

    // === 第一步:获取进程句柄 ===
    HANDLE hProcess = GetCurrentProcess();

    // === 第二步:枚举所有模块 ===
    HMODULE hModules[1024];
    DWORD cbNeeded;

    if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
    {
        std::cout << "获取模块列表失败rn";
        return;
    }

    DWORD moduleCount = cbNeeded / sizeof(HMODULE);
    std::cout << "当前进程共有 " << moduleCount << " 个模块rnrn";

    // === 第三步:初始化统计变量 ===
    int suspiciousCount = 0;
    int systemCount = 0;
    int whitelistCount = 0;

    // === 第四步:遍历检测每个模块 ===
    for (DWORD i = 0; i < moduleCount; i++)
    {
        // 获取模块完整路径
        char modulePath[MAX_PATH];
        if (GetModuleFileNameExA(hProcess, hModules[i], modulePath, MAX_PATH))
        {
            std::string fullPath = modulePath;

            // 从完整路径中提取文件名(用于白名单检查)
            size_t lastSlash = fullPath.find_last_of("\");
            std::string fileName = (lastSlash != std::string::npos) ?
                fullPath.substr(lastSlash + 1) : fullPath;

            // 模块分类判断:数字签名 > 白名单 > 可疑
            if (VerifyDigitalSignature(fullPath))
            {
                // 有数字签名的模块视为可信系统模块
                systemCount++;
            }
            else if (IsWhitelistDLL(fileName))
            {
                // 我们自己的程序文件,即使没有签名也是可信的
                whitelistCount++;
            }
            else
            {
                // 无签名且不在白名单中的模块,标记为可疑
                suspiciousCount++;
                std::cout << "🚨 [可疑模块] " << fileName << "rn";
                std::cout << "    路径: " << fullPath << "rn";
                std::cout << "    基址: 0x" << std::hex << (uintptr_t)hModules[i] << std::dec << "rn";

                // 获取模块的详细信息(大小)
                MODULEINFO moduleInfo;
                if (GetModuleInformation(hProcess, hModules[i], &moduleInfo, sizeof(moduleInfo)))
                {
                    std::cout << "    大小: " << moduleInfo.SizeOfImage << " 字节rn";
                }
                std::cout << "rn";
            }
        }
    }

    // 输出统计结果
    std::cout << "=== 检测结果 ===rn";
    std::cout << "系统模块: " << systemCount << " 个(已过滤)rn";
    std::cout << "白名单模块: " << whitelistCount << " 个rn";
    std::cout << "可疑模块: " << suspiciousCount << " 个rnrn";

    if (suspiciousCount > 0)
    {
        std::cout << "⚠️  检测到可疑模块!可能遭受注入攻击!rn";
        std::cout << "建议检查这些模块是否为您手动加载的DLL。rn";
    }
    else
    {
        std::cout << "✓ 未检测到可疑模块,进程安全。rn";
    }

    std::cout << "rn1秒后自动重新检测,Ctrl+C 退出程序...rn";
}

/**
 * @brief 程序入口
 * @return 程序退出码
 */
int main()
{
    // 获取并打印当前进程ID(只需要获取一次)
    DWORD currentPid = GetCurrentProcessId();

    // 无限循环检测
    while (true)
    {
        // 执行检测逻辑
        DetectSuspiciousModules(currentPid);

        // 等待1秒后自动重新检测
        Sleep(1000);

        // 清屏并重新开始
        system("cls");
    }  // 无限循环

    return 0;
}

检测效果演示

模块检测器运行效果:

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

  • ✅ 正常进程显示所有加载的模块分类统计
  • ✅ 系统模块(已过滤)和白名单模块数量清晰
  • ✅ 无可疑模块时显示安全状态提示
  • ✅ 每秒自动刷新,实时监控进程模块变化

优势与局限

优势

  1. 技术无关性:不依赖特定的注入技术检测,任何形式的模块加载都能发现
  2. 实时监控能力:每秒自动检测,能快速发现模块变化
  3. 签名验证可靠:基于数字签名的可信度判断,有效防止路径伪装攻击
  4. 输出清晰直观:分类明确,重点突出可疑模块
  5. 教学友好:代码结构清晰,便于理解和扩展

局限

  1. 签名依赖性:完全依赖数字签名,未签名的合法模块可能被误报
  2. 静态白名单:白名单需要预先配置,不够灵活
  3. 无法识别注入时机:只能检测已加载的模块,无法知道何时注入的
  4. 内存模块检测有限:对于不通过文件加载的内存模块检测能力有限
  5. 签名吊销检查:为提高验证速度,未进行实时的签名吊销状态检查

教学版简化说明

当前实现为教学简化版,实际生产环境中还应考虑:

  • 数字签名验证:验证模块的数字签名和发布者信息
  • 动态白名单管理:支持动态添加和修改白名单
  • 更复杂的路径分析:识别伪装的系统路径
  • 模块完整性检查:检查模块是否被修改
  • 网络行为监控:结合网络行为进行综合判断

防御建议

基础防御措施

  1. 定期模块检查:在关键业务流程中定期检查进程模块列表
  2. 白名单维护:建立完善的白名单策略,明确允许加载的模块
  3. 路径规范:将应用程序放置在规范的目录中,避免路径混淆

进阶防御策略

  1. 多层检测:结合线程检测、ETW监控、模块检测形成多层次防御体系
  2. 行为分析:不仅检查模块存在,还要分析模块的行为特征
  3. 基线建立:为正常应用建立模块基线,发现偏差时告警

实战应用场景

  1. 游戏反外挂:实时监控游戏进程,检测第三方注入模块
  2. 安全软件:作为进程保护的一部分,防止恶意代码注入
  3. 企业安全:监控关键业务进程,发现异常模块加载

下节课预告:攻击者的新思路 – Module Stomping

虽然我们的模块归属检测很有效,但攻击者总能找到新的绕过方法。下节课,作为攻击者,我们将学习一种更加隐蔽的注入技术:Module Stomping(模块践踏)

Module Stomping 的核心思想:

  • 不创建新的模块,而是”践踏”现有的合法模块
  • 将恶意代码加载到已存在的系统模块内存空间中
  • 从模块列表角度看,一切正常,没有新增模块

这种技术能够完美绕过我们的模块归属检测,因为它根本不会在进程模块列表中留下痕迹。我们将学习如何:

  1. 选择目标系统模块
  2. 在合法模块的内存空间中注入恶意代码
  3. 修改模块内存保护属性
  4. 实现代码执行

Module Stomping 代表了注入技术的一个重要发展方向:从”添加”到”伪装”的转变。这要求防御者必须从模块检查转向内存完整性检查。

下节课关键词: Module Stomping、内存伪装、代码注入、绕过检测

互动时间

💬 评论区讨论:

  1. 思维扩展:除了路径检查,还有哪些方法可以判断模块的可信度?

  2. 实战思考:如果恶意DLL将自身伪装成系统DLL(比如放在System32目录下),我们的检测器还能有效工作吗?应该如何加强检测?

  3. 技术探讨:实时监控虽然能快速发现问题,但也会消耗系统资源。你能在性能和检测效果之间找到平衡点吗?比如设计一个智能的检测频率调节机制?

  4. 扩展应用:这个检测原理能否应用到其他领域?比如检测注册表项、服务、计划任务等系统组件的异常?

🔥 下期预告关键词: Module Stomping、内存伪装、现有模块利用

By UD2

发表回复

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