6 / 52 Windows、主控与网络

第 6 课:Windows C++/MFC 基础补课

# 第 6 课:Windows C++/MFC 基础补课

免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。

本课定位:给不熟悉 Windows C++、Visual Studio 和 MFC 的同学补一层基础。我们不把本课写成完整 C++ 教材,而是围绕这套源码中反复出现的概念展开:MFC 程序入口、消息映射、窗口类、资源文件、Win32 API、句柄、线程、注册表、进程和 DLL 加载。学习目标是能读懂源码结构、定位风险点、写出审计记录,而不是运行或复用高风险功能。

# 1. 为什么要先补 Windows C++/MFC

从第 2 课到第 5 课,我们已经知道这套源码由主控、主插件、第三方库和编译配置共同组成。接下来再往下读源码时,新手最容易卡在三个地方:

卡点 新手常见困惑 本课解决方式
MFC 框架 看不懂 CWinAppCFrameWndCDialog 是什么 先讲 Windows 桌面程序的基本骨架
消息映射 不知道 BEGIN_MESSAGE_MAPON_MESSAGE 怎么把界面操作连到函数 用主控界面源码解释事件流
Win32 API HANDLECreateThreadRegSetValueExLoadLibrary 没概念 按安全审计视角解释常见 API

安全分析不是只看“某一行代码危险不危险”。更可靠的办法是先看程序结构,再看事件入口,再看 API 调用,最后看数据流向。MFC 和 Win32 API 正好是这条阅读路径的基础。

# 2. 本课学习目标与适用人群

学完本课后,读者应当能做到:

目标 说明
读懂 MFC 程序入口 知道 InitInstance 为什么常被当作主控程序初始化入口
读懂消息映射 能从 ON_COMMANDON_MESSAGE 找到对应处理函数
识别常见 Win32 对象 理解 HANDLE、线程、进程、注册表、DLL 的基本含义
建立源码阅读顺序 从类、消息、处理函数、API、数据流逐步阅读
建立安全审计意识 看到线程创建、注册表写入、动态加载、进程操作时知道应该记录和验证什么

适用人群包括企业安全人员、安全工程学习者、授权红蓝测试人员、安全研发人员和做代码审计的工程师。红蓝测试人员应将本课用于授权环境下的检测设计、日志验证和风险复盘,不得用于未授权系统。

# 3. 先看一张 Windows C++ 基础地图

这套源码里,主控界面主要是 MFC 程序,底层动作大量依赖 Win32 API。新手可以先把它理解成四层:

  1. MFC 应用层:负责程序对象、窗口、菜单、对话框和消息分发。
  2. 业务处理层:负责具体功能的处理函数,例如打开某个管理窗口。
  3. Win32 API 层:负责线程、进程、注册表、网络、动态库等系统能力。
  4. 安全审计层:记录哪些入口会触发敏感 API,以及这些 API 读写了什么资源。

第 6 课图 1:Windows C++、MFC 与 Win32 API 基础地图

上图对后续阅读很重要:MFC 不是“另一种操作系统 API”,它更像是 Win32 桌面开发的一层封装。真正触碰系统资源的地方,通常还是落在 Win32 API。

# 4. MFC 程序里几个最常见的类

读这套源码时,先记住下面几个类就够用了:

类或概念 新手解释 在工程中的作用
CWinApp MFC 应用程序对象 管理程序启动、初始化和退出
CFrameWnd 主框架窗口 承载菜单、工具栏、状态栏和主界面
CDialog 对话框窗口 承载配置窗口、功能窗口和交互窗口
CDocument 文档对象 MFC 文档视图结构中的数据对象
CView 视图对象 展示数据和处理界面显示
消息映射 把消息编号映射到函数 决定用户操作最终进入哪个处理函数
资源文件 菜单、图标、对话框模板、字符串等 帮助还原界面入口和功能命名

本课不用展开 MFC 的全部历史和设计,只要抓住一句话:MFC 程序通常不是从一个线性的 main 函数一路执行到底,而是先初始化窗口,再等待 Windows 消息驱动不同处理函数。

# 5. 主控程序入口:CQuickApp 消息映射

先看主控工程里的应用类消息映射。

// 源码位置:主控\Quick\Quick.cpp:27-32
BEGIN_MESSAGE_MAP(CQuickApp, CWinApp)
    // 防御分析:关于菜单命令会进入 CQuickApp::OnAppAbout。
    // 审计时可从 ON_COMMAND 反查菜单项、资源 ID 和处理函数。
    ON_COMMAND(ID_APP_ABOUT, &CQuickApp::OnAppAbout)

    // 防御分析:这是 MFC 标准文件命令,不是本项目核心风险点。
    // 但仍可作为理解 ON_COMMAND 机制的入门样例。
    ON_COMMAND(ID_FILE_NEW, &CWinApp::OnFileNew)
    ON_COMMAND(ID_FILE_OPEN, &CWinApp::OnFileOpen)
END_MESSAGE_MAP()
1
2
3
4
5
6
7
8
9
10
11

新手读这段时可以按下面顺序看:

阅读点 说明
CQuickApp 当前应用程序类
CWinApp MFC 应用基类
ID_APP_ABOUT 资源 ID,通常可以在资源文件或头文件中继续追踪
OnAppAbout 实际处理函数

安全审计时,消息映射的价值是帮助我们建立“入口清单”。先知道有哪些按钮、菜单或自定义消息,再去看它们会不会触发敏感能力。

# 6. InitInstance:主控程序启动时做了什么

MFC 程序里,InitInstance 通常是应用初始化的核心入口。它不等同于 C 语言的 main,但对源码阅读来说,它就是主控程序启动阶段最值得先看的函数之一。

// 源码位置:主控\Quick\Quick.cpp:107-173
BOOL CQuickApp::InitInstance()
{
#ifdef OPEN_LOGIN
    // 防御分析:条件编译控制登录对话框是否启用。
    // 审计时要记录宏开关,因为不同编译配置可能改变程序入口行为。
    Login.DoModal();
    if (Login.dLogin <= 10000)
    {
        return FALSE;
    }
#endif

#ifdef ONLINE_TIME
    // 防御分析:时间判断属于运行条件控制。
    // 企业审计时应关注硬编码时间、授权逻辑和绕过风险。
    CString strSysTime, strDeadline, strSWCreateTime;
    CTime sysTime;
    sysTime = CTime::GetCurrentTime();
    strSysTime = sysTime.Format(_T("%Y%m%d"));
    strSWCreateTime.Format(_T("20260126"));
    strDeadline.Format(_T("20260128"));
    if (strSysTime < strSWCreateTime)
    {
        return FALSE;
    }
    if (strSysTime > strDeadline)
    {
        // 原源码中这里保留了到期处理注释。
    }
#endif

    // 防御分析:初始化通用控件,属于 UI 基础初始化。
    INITCOMMONCONTROLSEX InitCtrls;
    InitCtrls.dwSize = sizeof(InitCtrls);
    InitCtrls.dwICC = ICC_WIN95_CLASSES;
    InitCommonControlsEx(&InitCtrls);

    CWinApp::InitInstance();

    // 防御分析:初始化 COM 多线程模型。
    // 后续如果出现 COM 对象、Shell 接口、自动化对象,可回到这里确认初始化模式。
    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    // 防御分析:初始化网络库。
    // 对安全人员来说,这是主控具备网络能力的早期信号,需要继续追踪 InitSocketLib。
    if (!InitSocketLib())
        return FALSE;

    // 防御分析:设置 MFC 配置存储位置。
    // 注册表或配置文件读写都应纳入企业基线审计。
    SetRegistryKey(_T("Local AppWizard-Generated Applications"));
    LoadStdProfileSettings(4);

    // 防御分析:创建文档、主框架窗口和视图之间的关系。
    // CMainFrame 是后续主控界面消息映射的关键入口。
    CSingleDocTemplate* pDocTemplate;
    pDocTemplate = new CSingleDocTemplate(
        IDR_MAINFRAME,
        RUNTIME_CLASS(CQuickDoc),
        RUNTIME_CLASS(CMainFrame),
        RUNTIME_CLASS(CTabView));
    if (!pDocTemplate)
        return FALSE;
    AddDocTemplate(pDocTemplate);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

这段代码可以拆成五件事:

阶段 作用 审计关注点
条件编译 根据宏决定是否启用登录、时间限制等逻辑 不同配置会产生不同行为
控件初始化 准备 Windows 通用控件 一般风险较低
COM 初始化 设置 COM 线程模型 后续 COM 调用要匹配线程模型
网络初始化 准备 socket 能力 是网络行为审计入口
主框架创建 绑定文档、窗口和视图 后续主界面事件从 CMainFrame 继续追踪

新手不要急着看每个业务函数。更稳的做法是先确认:程序启动时初始化了哪些系统能力,创建了哪个主窗口类,后续消息会进入哪里。

# 7. MFC 消息流:从用户操作到处理函数

Windows 桌面程序通常是事件驱动的。用户点击菜单、窗口收到通知、后台模块投递自定义消息,都会形成 Windows 消息。MFC 的消息映射负责把这些消息转给具体函数。

第 6 课图 2:MFC 消息流与处理函数关系

把图里的流程对应到源码,就是:

  1. 用户操作或程序内部事件产生消息。
  2. Windows 把消息投递给对应窗口。
  3. MFC 根据消息映射表找到处理函数。
  4. 处理函数继续调用业务代码。
  5. 业务代码可能调用 Win32 API 访问系统资源。

这条链路是后续做代码审计的主线。不要只看函数名,也要看消息从哪里来、参数带了什么、处理函数又调用了哪些 API。

# 8. 主框架窗口:CMainFrame 消息映射

主控界面的大量入口集中在 CMainFrame 的消息映射里。它把菜单、托盘、自定义消息和多个功能窗口关联起来。

// 源码位置:主控\Quick\MainFrm.cpp:88-125
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    // 防御分析:窗口创建、关闭和系统命令入口。
    ON_WM_CREATE()
    ON_WM_CLOSE()
    ON_WM_SYSCOMMAND()

    // 防御分析:自定义通知消息,应该继续追踪 WM_NOTIFYPROC 的定义和发送位置。
    ON_MESSAGE(WM_NOTIFYPROC, OnNotifyProc)

    // 防御分析:停靠窗格和弹窗通知,属于主控界面布局与交互入口。
    ON_MESSAGE(XTPWM_DOCKINGPANE_NOTIFY, OnDockingPaneNotify)
    ON_MESSAGE(WM_DESKTOPPOPUP, OnOpenDestopPopup)
    ON_MESSAGE(XTPWM_POPUPCONTROL_NOTIFY, OnPopUpNotify)

    // 防御分析:托盘菜单入口。
    // 审计时应记录这些命令是否会改变窗口可见性、锁定状态或截图策略。
    ON_COMMAND(ID_MENUITEM_SHOW, OnMenuitemShow)
    ON_COMMAND(ID_TRY_LOCK, OnLockButton)
    ON_COMMAND(ID_TRY_HIDDEN, OnHiddenButton)

    // 防御分析:下面这些自定义消息直接暴露了主控能力边界。
    // 本课只做结构性识别,不提供任何操作步骤。
    ON_MESSAGE(WM_OPENSHELLDIALOG, OnOpenshelldialog)           // 终端管理
    ON_MESSAGE(WM_OPENKEYBOARDDIALOG, OnOpenkeyboarddialog)     // 键盘相关界面
    ON_MESSAGE(WM_OPENREGEDITDIALOG, OnOpenregeditdialog)       // 注册表界面
    ON_MESSAGE(WM_OPENPROXYDIALOG, OnOpenproxydialog)           // 代理界面
    ON_MESSAGE(WM_OPENCHATDIALOG, OnOpenchatdialog)             // 交谈界面
    ON_MESSAGE(WM_OPENSTARTUPMGRDIALOG, OnOpenStartupMgrDialog) // 启动项界面
    ON_MESSAGE(WM_OPENAUDIODIALOG, OnOpenaudiodialog)           // 音频界面
    ON_MESSAGE(WM_OPENMANAGERDIALOG, OnOpenmanagerdialog)       // 文件管理界面
    ON_MESSAGE(WM_OPENWEBCAMDIALOG, OnOpenwebcamdialog)         // 摄像头界面
    ON_MESSAGE(WM_OPENSPEAKERDIALOG, OnOpenspeakerdialog)       // 扬声器界面
    ON_MESSAGE(WM_OPENSYSINFODIALOG, OnOpensysinfodialog)       // 系统信息界面
    ON_MESSAGE(WM_OPENPKERNELDIALOG, OnOpenkerneldialog)        // 驱动插件界面
    ON_MESSAGE(WM_OPENPEXPANDDIALOG, OnOpenexpanddialog)        // 扩展插件界面

    // 防御分析:屏幕相关入口较多,后续专栏会单独按模块展开。
    ON_MESSAGE(WM_OPENSCREENSPYDIALOG_DIF, OnOpencreenspydialog_dif)
    ON_MESSAGE(WM_OPENSCREENSPYDIALOG_QUICK, OnOpencreenspydialog_quick)
    ON_MESSAGE(WM_OPENSCREENSPYDIALOG_PLAY, OnOpencreenspydialog_play)
    ON_MESSAGE(WM_OPENSCREENSPYDIALOG_HIDE, OnOpencreenspydialog_hide)

    // 防御分析:界面参数调整入口,关注参数是否影响后续采集、传输或渲染行为。
    ON_COMMAND(ID_MONITOR_W, OnButton_Monitor_W)
    ON_NOTIFY(XTP_SBN_SCROLL, ID_MONITOR_SLIDER_W, OnZoomSliderScroll_W)
END_MESSAGE_MAP()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

这段代码对企业安全分析非常有价值。它相当于一张主控能力目录:

能力线索 从哪里看出来 防御侧应该怎么用
终端管理 WM_OPENSHELLDIALOG 标记为高风险功能入口,后续追踪通信和命令执行边界
注册表界面 WM_OPENREGEDITDIALOG 关注注册表枚举、修改、删除行为
文件管理 WM_OPENMANAGERDIALOG 关注文件读写、上传下载、路径校验
音视频界面 WM_OPENAUDIODIALOGWM_OPENWEBCAMDIALOG 关注隐私数据访问、采集和传输
启动项界面 WM_OPENSTARTUPMGRDIALOG 关注持久化、服务、计划任务和自启动位置

这里不要把消息名直接等同于实际行为。消息映射只是入口,真正的风险要继续看处理函数、网络协议和被调用的 Win32 API。

# 9. Win32 API 基础:句柄、线程、进程和资源

MFC 负责把界面和事件组织起来,但很多真实动作最终会落到 Win32 API。新手可以先理解四个概念:

概念 新手解释 审计关注点
HANDLE Windows 内核对象的引用,例如线程、进程、文件、注册表键 是否关闭、是否泄漏、权限是否过大
线程 同一进程里的执行路径 是否在敏感位置创建线程,线程入口做什么
进程 一个正在运行的程序实例 是否打开其他进程,权限是什么
注册表 Windows 的配置数据库 是否写入持久化位置、敏感配置或二进制数据
DLL 动态链接库 是否动态加载、从哪里加载、导出函数如何调用

下面这张图把常见资源和安全风险放在一起:

第 6 课图 3:Win32 资源与安全风险对应关系

后续专栏每进入一个插件,都可以按这张图检查:它创建了什么对象、访问了什么资源、有没有跨进程或跨网络边界、有没有留下可检测的日志或行为特征。

# 10. 线程与 DLL 生命周期示例

下面这段来自文件管理插件,用来说明 DLL 入口、线程创建和句柄释放。这里不讲如何运行插件,只讲审计时如何读代码。

// 源码位置:主插件\文件管理\文件管理\文件管理.cpp:120-155

// 防御分析:隐藏控制台窗口属于界面行为调整。
// 审计时应记录是否存在隐藏窗口、隐藏控制台或降低可见性的逻辑。
ShowWindow(GetConsoleWindow(), SW_HIDE);

// 防御分析:CreateThread 创建新线程,MainThread 是后续重点追踪入口。
// 不要只记录 CreateThread 本身,还要继续看线程函数读取了什么、写入了什么、发出了什么请求。
hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0);

// 防御分析:等待线程结束。长时间等待可能影响宿主进程行为。
WaitForSingleObject(hThread, INFINITE);

// 防御分析:线程句柄在这里被关闭,这是句柄生命周期审计点。
CloseHandle(hThread);

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
#ifdef _DEBUG
        // 防御分析:调试分支通常和发布分支行为不同。
        // 审计报告中应注明当前分析的是 Debug 还是 Release。
#else
        if (MyInfo.RunDllEntryProc && (hThread == NULL))
        {
            // 防御分析:DLL 被加载到进程后创建工作线程。
            // DllMain 中做复杂工作存在稳定性和可审计性风险。
            hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)MainThread, 0, 0, 0);
            WaitForSingleObject(hThread, INFINITE);
        }
#endif
    }
    break;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

这段代码可以按生命周期读:

阶段 代码线索 防御问题
DLL 被加载 DLL_PROCESS_ATTACH 谁加载了这个 DLL,加载路径是什么
创建工作线程 CreateThread(... MainThread ...) MainThread 做了哪些文件、注册表、网络操作
等待线程 WaitForSingleObject(... INFINITE) 是否可能造成宿主卡死或长时间占用
关闭句柄 CloseHandle(hThread) 所有成功创建的句柄是否都有关闭路径

企业检测上,DllMain 内创建线程、隐藏窗口、长时间等待,都是值得记录的行为。它们不一定单独构成恶意结论,但可以作为关联分析的节点。

# 11. 注册表写入示例

注册表是 Windows 程序常见配置位置,也是安全审计中必须重点关注的对象。下面这段来自上线模块,展示了向注册表写入二进制配置的逻辑。

// 源码位置:主插件\上线模块\上线模块\KernelManager.cpp:25-34

// 防御分析:打开 HKLM\SOFTWARE,并请求 64 位注册表视图的写权限。
// HKLM 通常需要较高权限,企业环境中应结合权限、进程身份和事件日志一起分析。
HKEY hKey;
::RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE"), 0,
    KEY_WOW64_64KEY | KEY_SET_VALUE, &hKey);

// 防御分析:删除旧值再写入新值。
// 值名 IpDates_info 和 REG_BINARY 数据类型都应写入审计记录。
::RegDeleteValue(hKey, _T("IpDates_info"));
::RegSetValueEx(hKey, _T("IpDates_info"), 0, REG_BINARY,
    (unsigned char*)&MyInfo, sizeof(Info));

// 防御分析:关闭注册表键句柄,避免句柄泄漏。
::RegCloseKey(hKey);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这段代码里有几个审计要点:

审计项 说明
根键 HKEY_LOCAL_MACHINE,影响范围通常大于当前用户
子键 SOFTWARE,需要继续确认实际值名和权限
视图 KEY_WOW64_64KEY 表示访问 64 位注册表视图
值名 IpDates_info 是可检索特征
类型 REG_BINARY 不直观,需要结合结构体解释
数据来源 MyInfo 结构体,后续要看字段定义和填充来源

防御侧可以把注册表写入看成“配置落点”。如果同一进程还存在网络通信、动态加载、内存执行等行为,注册表值就可能成为事件关联中的关键证据。

# 12. 动态库加载示例

动态加载 DLL 是 Windows 程序的常见技术。正常软件会用它加载插件或可选组件,高风险程序也可能用它绕开静态导入表或延迟暴露能力。审计时要看加载路径、文件名、导出函数名和错误处理。

// 源码位置:主插件\上线模块\上线模块\KernelManager.cpp:61-83

// 防御分析:调试分支会把传入的插件名转换成本地 DLL 名。
// 这说明 Debug 和 Release 行为可能不一致,复盘时必须记录编译配置。
wchar_t szLocalDllName[MAX_PATH] = { 0 };
wcscpy_s(szLocalDllName, ARRAYSIZE(szLocalDllName), g_dllSendData.DllName);
::PathRemoveExtension(szLocalDllName);
wcscat_s(szLocalDllName, ARRAYSIZE(szLocalDllName), L".dll");

// 防御分析:LoadLibrary 会从指定路径或搜索路径加载 DLL。
// 企业检测应关注 DLL 搜索路径、签名、哈希、落盘位置和加载进程。
HMODULE hDllModule = ::LoadLibrary(szLocalDllName);
if (hDllModule != NULL)
{
    // 防御分析:GetProcAddress 通过导出名解析函数地址。
    // 导出名 "run" 是一个关键审计特征。
    FARPROC runFunc = ::GetProcAddress(hDllModule, "run");
    if (runFunc != NULL)
    {
        // 防御分析:这里进行了动态函数调用。
        // 本教程只用于理解调用链和检测点,不提供复用方式。
        runFunc();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

看到 LoadLibraryGetProcAddress 时,不要只写“存在动态加载”。更完整的审计记录应包含:

字段 应记录内容
调用位置 文件路径、函数名、大致行号
加载对象 DLL 文件名、路径来源、是否可被外部影响
导出函数 例如这里的 "run"
调用时机 启动时、消息触发时、网络数据到达时还是调试分支
防御检测 Sysmon ImageLoad、EDR DLL 加载事件、文件创建事件、签名校验

如果动态加载路径来自外部输入或可写目录,还要额外评估 DLL 劫持和供应链风险。

# 13. 进程句柄示例:打开进程后要关注权限和关闭路径

下面这段代码用于判断某个进程 ID 是否仍在运行。它可以帮助新手理解 OpenProcessHANDLE 和返回路径审计。

// 源码位置:主插件\上线模块\上线模块\KernelManager.cpp:283-300
bool pid_is_running(DWORD pid)
{
    // 防御分析:以 PROCESS_QUERY_INFORMATION 权限打开进程。
    // 审计时应记录目标 pid 来源,以及是否存在更高权限请求。
    HANDLE h_process = OpenProcess(PROCESS_QUERY_INFORMATION, false, pid);
    if (h_process == NULL) {
        return false;
    }

    DWORD exit_code;
    if (!GetExitCodeProcess(h_process, &exit_code)) {
        // 防御分析:这里直接返回,没有关闭 h_process。
        // 这是代码质量和资源管理审计点,可能导致句柄泄漏。
        return false;
    }

    if (exit_code == STILL_ACTIVE) {
        // 防御分析:这里也直接返回,没有关闭 h_process。
        // 审计建议:成功 OpenProcess 后应保证所有路径释放句柄。
        return true;
    }
    else {
        return false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

这段代码的安全价值不在于“判断进程是否存在”,而在于提醒我们:句柄是有生命周期的。只要 OpenProcess 成功,后续所有返回路径都应该考虑 CloseHandle。这类问题在长期运行的程序里会放大成稳定性风险,也会影响取证时对资源占用的判断。

# 14. 新手读 MFC 源码的固定路线

建议以后读主控界面相关源码时,都按这个顺序走:

  1. 找类:先确认当前文件里的类继承自谁,例如 CWinAppCFrameWndCDialog
  2. 找消息映射:搜索 BEGIN_MESSAGE_MAP
  3. 找入口:记录 ON_COMMANDON_MESSAGEON_NOTIFY
  4. 找处理函数:跳转到 OnXxx 函数定义。
  5. 找系统 API:关注 CreateThreadOpenProcessRegSetValueExLoadLibraryVirtualAllocsendrecv 等。
  6. 找数据流:看参数来自界面、配置、网络、文件还是注册表。
  7. 写审计结论:不要只贴代码,要说明入口、资源、权限、检测点和风险边界。

这条路线可以降低跳读带来的误判。很多看起来危险的函数,只有放回触发条件和数据来源里,才能判断真实风险;很多看起来普通的界面入口,继续往下追踪后可能会连到高风险 API。

# 15. 本课涉及源码目录和文件

目录或文件 本课用途 关键位置
主控\Quick\Quick.cpp 主控应用对象和初始化入口 BEGIN_MESSAGE_MAPCQuickApp::InitInstance
主控\Quick\MainFrm.cpp 主控主框架窗口和消息映射 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
主插件\文件管理\文件管理\文件管理.cpp DLL 入口、线程创建和句柄释放示例 DllMainCreateThreadWaitForSingleObject
主插件\上线模块\上线模块\KernelManager.cpp 注册表、动态加载、进程句柄示例 RegSetValueExLoadLibraryOpenProcess

这些文件不是本课的全部源码范围,而是用来建立基础概念的代表性样例。后续进入具体插件专栏时,会再按模块展开。

# 16. 企业检测点

检测方向 具体检测点 可能的数据源
UI 能力枚举 消息映射暴露的功能入口和命名 源码审计、二进制字符串、资源表
线程行为 CreateThread、线程入口函数、长时间等待 EDR、调试日志、代码审计
进程访问 OpenProcess 权限、目标 PID 来源 Sysmon Event ID 10、EDR 进程事件
注册表写入 RegSetValueEx、根键、值名、数据类型 Sysmon Event ID 12/13/14、Windows 日志
DLL 加载 LoadLibrary、加载路径、导出函数调用 Sysmon Event ID 7、EDR ImageLoad
资源管理 CloseHandle 是否覆盖所有路径 静态审计、运行时句柄统计
编译差异 _DEBUGOPEN_LOGINONLINE_TIME 等宏 工程配置、构建日志、预处理输出

检测时不要孤立看一个 API。更有效的方式是把“消息入口、系统 API、资源对象、网络或文件行为”串成事件链。

# 17. 新手常见误区

误区 正确做法
看到 ON_MESSAGE 就认为已经执行了某功能 继续找消息发送位置和处理函数
看到 LoadLibrary 就直接下恶意结论 检查加载路径、签名、调用时机和上下文
只看 Debug 行为 同时对比 Release 分支和宏开关
只看 API 名,不看参数 参数里的权限、路径、值名、大小更关键
忽略句柄释放 所有 Open*Create* 成功后都要找释放路径
把主控界面当成全部能力 主插件、被加载 DLL、网络协议同样重要

# 18. 本课小结

本课补了三层基础:

  1. MFC 层:CWinApp 管启动,CFrameWnd 管主窗口,消息映射把事件转给处理函数。
  2. Win32 层:线程、进程、注册表、DLL、句柄是 Windows 安全审计的常见对象。
  3. 审计层:不要孤立看单行代码,要把入口、参数、资源、权限和检测点连起来。

从下一课开始,再进入主控界面布局、资源文件和具体功能窗口时,就可以用本课的方法读代码:先找入口,再找处理函数,再找系统 API,最后写出检测点和审计结论。

# 19. 合法练习题

  1. 主控\Quick\MainFrm.cpp 中搜索 BEGIN_MESSAGE_MAP,列出 5 个自定义消息和对应处理函数,只写功能判断,不运行程序。
  2. 主插件 目录中搜索 CreateThread,任选 3 处记录线程入口函数、所在文件和大致行号。
  3. 主插件 目录中搜索 RegSetValueEx,记录根键、值名、数据类型和数据来源。
  4. 主插件 目录中搜索 LoadLibrary,记录加载对象名称来源和后续 GetProcAddress 的导出函数名。
  5. 审查 pid_is_running 的句柄释放路径,写出一段不超过 200 字的代码质量风险说明。

安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。

上次更新: 2026/07/14, 19:37:40

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>