第 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 框架 | 看不懂 CWinApp、CFrameWnd、CDialog 是什么 | 先讲 Windows 桌面程序的基本骨架 |
| 消息映射 | 不知道 BEGIN_MESSAGE_MAP 和 ON_MESSAGE 怎么把界面操作连到函数 | 用主控界面源码解释事件流 |
| Win32 API | 对 HANDLE、CreateThread、RegSetValueEx、LoadLibrary 没概念 | 按安全审计视角解释常见 API |
安全分析不是只看“某一行代码危险不危险”。更可靠的办法是先看程序结构,再看事件入口,再看 API 调用,最后看数据流向。MFC 和 Win32 API 正好是这条阅读路径的基础。
# 2. 本课学习目标与适用人群
学完本课后,读者应当能做到:
| 目标 | 说明 |
|---|---|
| 读懂 MFC 程序入口 | 知道 InitInstance 为什么常被当作主控程序初始化入口 |
| 读懂消息映射 | 能从 ON_COMMAND、ON_MESSAGE 找到对应处理函数 |
| 识别常见 Win32 对象 | 理解 HANDLE、线程、进程、注册表、DLL 的基本含义 |
| 建立源码阅读顺序 | 从类、消息、处理函数、API、数据流逐步阅读 |
| 建立安全审计意识 | 看到线程创建、注册表写入、动态加载、进程操作时知道应该记录和验证什么 |
适用人群包括企业安全人员、安全工程学习者、授权红蓝测试人员、安全研发人员和做代码审计的工程师。红蓝测试人员应将本课用于授权环境下的检测设计、日志验证和风险复盘,不得用于未授权系统。
# 3. 先看一张 Windows C++ 基础地图
这套源码里,主控界面主要是 MFC 程序,底层动作大量依赖 Win32 API。新手可以先把它理解成四层:
- MFC 应用层:负责程序对象、窗口、菜单、对话框和消息分发。
- 业务处理层:负责具体功能的处理函数,例如打开某个管理窗口。
- Win32 API 层:负责线程、进程、注册表、网络、动态库等系统能力。
- 安全审计层:记录哪些入口会触发敏感 API,以及这些 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()
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);
}
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 的消息映射负责把这些消息转给具体函数。

把图里的流程对应到源码,就是:
- 用户操作或程序内部事件产生消息。
- Windows 把消息投递给对应窗口。
- MFC 根据消息映射表找到处理函数。
- 处理函数继续调用业务代码。
- 业务代码可能调用 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()
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_OPENAUDIODIALOG、WM_OPENWEBCAMDIALOG | 关注隐私数据访问、采集和传输 |
| 启动项界面 | WM_OPENSTARTUPMGRDIALOG | 关注持久化、服务、计划任务和自启动位置 |
这里不要把消息名直接等同于实际行为。消息映射只是入口,真正的风险要继续看处理函数、网络协议和被调用的 Win32 API。
# 9. Win32 API 基础:句柄、线程、进程和资源
MFC 负责把界面和事件组织起来,但很多真实动作最终会落到 Win32 API。新手可以先理解四个概念:
| 概念 | 新手解释 | 审计关注点 |
|---|---|---|
HANDLE | Windows 内核对象的引用,例如线程、进程、文件、注册表键 | 是否关闭、是否泄漏、权限是否过大 |
| 线程 | 同一进程里的执行路径 | 是否在敏感位置创建线程,线程入口做什么 |
| 进程 | 一个正在运行的程序实例 | 是否打开其他进程,权限是什么 |
| 注册表 | Windows 的配置数据库 | 是否写入持久化位置、敏感配置或二进制数据 |
| DLL | 动态链接库 | 是否动态加载、从哪里加载、导出函数如何调用 |
下面这张图把常见资源和安全风险放在一起:

后续专栏每进入一个插件,都可以按这张图检查:它创建了什么对象、访问了什么资源、有没有跨进程或跨网络边界、有没有留下可检测的日志或行为特征。
# 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;
}
}
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);
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
看到 LoadLibrary 和 GetProcAddress 时,不要只写“存在动态加载”。更完整的审计记录应包含:
| 字段 | 应记录内容 |
|---|---|
| 调用位置 | 文件路径、函数名、大致行号 |
| 加载对象 | DLL 文件名、路径来源、是否可被外部影响 |
| 导出函数 | 例如这里的 "run" |
| 调用时机 | 启动时、消息触发时、网络数据到达时还是调试分支 |
| 防御检测 | Sysmon ImageLoad、EDR DLL 加载事件、文件创建事件、签名校验 |
如果动态加载路径来自外部输入或可写目录,还要额外评估 DLL 劫持和供应链风险。
# 13. 进程句柄示例:打开进程后要关注权限和关闭路径
下面这段代码用于判断某个进程 ID 是否仍在运行。它可以帮助新手理解 OpenProcess、HANDLE 和返回路径审计。
// 源码位置:主插件\上线模块\上线模块\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;
}
}
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 源码的固定路线
建议以后读主控界面相关源码时,都按这个顺序走:
- 找类:先确认当前文件里的类继承自谁,例如
CWinApp、CFrameWnd、CDialog。 - 找消息映射:搜索
BEGIN_MESSAGE_MAP。 - 找入口:记录
ON_COMMAND、ON_MESSAGE、ON_NOTIFY。 - 找处理函数:跳转到
OnXxx函数定义。 - 找系统 API:关注
CreateThread、OpenProcess、RegSetValueEx、LoadLibrary、VirtualAlloc、send、recv等。 - 找数据流:看参数来自界面、配置、网络、文件还是注册表。
- 写审计结论:不要只贴代码,要说明入口、资源、权限、检测点和风险边界。
这条路线可以降低跳读带来的误判。很多看起来危险的函数,只有放回触发条件和数据来源里,才能判断真实风险;很多看起来普通的界面入口,继续往下追踪后可能会连到高风险 API。
# 15. 本课涉及源码目录和文件
| 目录或文件 | 本课用途 | 关键位置 |
|---|---|---|
主控\Quick\Quick.cpp | 主控应用对象和初始化入口 | BEGIN_MESSAGE_MAP、CQuickApp::InitInstance |
主控\Quick\MainFrm.cpp | 主控主框架窗口和消息映射 | BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) |
主插件\文件管理\文件管理\文件管理.cpp | DLL 入口、线程创建和句柄释放示例 | DllMain、CreateThread、WaitForSingleObject |
主插件\上线模块\上线模块\KernelManager.cpp | 注册表、动态加载、进程句柄示例 | RegSetValueEx、LoadLibrary、OpenProcess |
这些文件不是本课的全部源码范围,而是用来建立基础概念的代表性样例。后续进入具体插件专栏时,会再按模块展开。
# 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 是否覆盖所有路径 | 静态审计、运行时句柄统计 |
| 编译差异 | _DEBUG、OPEN_LOGIN、ONLINE_TIME 等宏 | 工程配置、构建日志、预处理输出 |
检测时不要孤立看一个 API。更有效的方式是把“消息入口、系统 API、资源对象、网络或文件行为”串成事件链。
# 17. 新手常见误区
| 误区 | 正确做法 |
|---|---|
看到 ON_MESSAGE 就认为已经执行了某功能 | 继续找消息发送位置和处理函数 |
看到 LoadLibrary 就直接下恶意结论 | 检查加载路径、签名、调用时机和上下文 |
| 只看 Debug 行为 | 同时对比 Release 分支和宏开关 |
| 只看 API 名,不看参数 | 参数里的权限、路径、值名、大小更关键 |
| 忽略句柄释放 | 所有 Open*、Create* 成功后都要找释放路径 |
| 把主控界面当成全部能力 | 主插件、被加载 DLL、网络协议同样重要 |
# 18. 本课小结
本课补了三层基础:
- MFC 层:
CWinApp管启动,CFrameWnd管主窗口,消息映射把事件转给处理函数。 - Win32 层:线程、进程、注册表、DLL、句柄是 Windows 安全审计的常见对象。
- 审计层:不要孤立看单行代码,要把入口、参数、资源、权限和检测点连起来。
从下一课开始,再进入主控界面布局、资源文件和具体功能窗口时,就可以用本课的方法读代码:先找入口,再找处理函数,再找系统 API,最后写出检测点和审计结论。
# 19. 合法练习题
- 在
主控\Quick\MainFrm.cpp中搜索BEGIN_MESSAGE_MAP,列出 5 个自定义消息和对应处理函数,只写功能判断,不运行程序。 - 在
主插件目录中搜索CreateThread,任选 3 处记录线程入口函数、所在文件和大致行号。 - 在
主插件目录中搜索RegSetValueEx,记录根键、值名、数据类型和数据来源。 - 在
主插件目录中搜索LoadLibrary,记录加载对象名称来源和后续GetProcAddress的导出函数名。 - 审查
pid_is_running的句柄释放路径,写出一段不超过 200 字的代码质量风险说明。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言