7 / 52 Windows、主控与网络

第 7 课:主控界面布局单独讲解

# 第 7 课:主控界面布局单独讲解

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

本课定位:专门讲 主控\Quick 的界面布局。目标不是教读者操作主控程序,而是让企业安全人员、安全工程学习者和授权红蓝测试人员能从界面结构反查源码入口,理解“按钮、菜单、列表、弹窗、资源 ID、消息映射、网络命令”之间的关系,并把这些线索转化为防御检测点和审计记录。

# 1. 先划清边界:界面分析不是使用教程

主控界面是这套源码最容易被新手误解的部分。界面上能看到大量功能入口,例如文件、屏幕、终端、注册表、音频、摄像头、代理、交谈、插件管理等。如果直接按功能名去尝试,很容易越过安全学习边界。

本课只做三件事:

本课做什么 本课不做什么
解释主控窗口由哪些 MFC/Codejock 组件组成 不讲如何连接真实主机
教读者从资源 ID 和消息映射反查处理函数 不提供任何远控功能使用步骤
标注高风险界面入口对应的审计点 不复现攻击链,不指导规避检测

正确的学习方式是:把界面当作“源码索引”。看到一个入口,先去找资源 ID,再去找消息映射,再去找处理函数,最后写出防御检测点。

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

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

目标 说明
看懂主控窗口布局 知道主框架、工具栏、连接列表、停靠面板、状态栏分别来自哪里
从界面反查源码 能用资源 ID 找到 ON_COMMANDON_MESSAGE 和处理函数
理解连接列表角色 知道 CQuickView 如何承载上线资料、分组和右键入口
理解功能弹窗创建 能看懂 CMainFrame 如何创建不同功能对话框
建立检测思路 能把界面入口、插件调度、网络行为和日志事件关联起来

适用人群包括企业安全人员、安全工程学习者、授权红蓝测试人员、安全研发人员和代码审计人员。授权测试人员可以用本课建立复盘清单、检测验证清单和审计证据链,不得在未授权环境中触发高风险功能。

# 3. 主控界面先看成五块

主控程序不是一个简单窗口,而是一个由多种界面组件拼出来的控制台。新手先把它分成五块:

  1. 主框架窗口:CMainFrame,负责承载整个主控界面。
  2. 工具栏和命令栏:按钮、菜单、托盘命令等入口。
  3. 分组页签和连接列表:CTabView + CQuickView,负责展示上线对象和分组。
  4. 停靠面板:日志、插件管理、客户生成、系统统计等面板。
  5. 状态栏:免责声明、监听状态、在线数量、视频墙参数等。

第 7 课图 1:主控界面布局总览

这张图的阅读顺序是从外到内:先看 CMainFrame,再看工具栏和状态栏,再看中间的连接列表和右侧/底部的停靠面板。后续追源码也按这个顺序走。

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

文件 本课用途 重点位置
主控\Quick\MainFrm.h 主框架成员变量、停靠面板、状态栏声明 m_paneManagerm_wndStatusBarCreatePane
主控\Quick\MainFrm.cpp 主窗口创建、停靠面板、状态栏、功能弹窗 OnCreateCreatePaneadddCXTPToolBarOnOpen*dialog
主控\Quick\TabView.h/.cpp 分组页签和分组视图 m_wndTabControlAddGroupOnAddFindGroup
主控\Quick\QuickView.h/.cpp 连接列表、列定义、右键菜单、工具栏命令 CQuickViewg_Column_DataHandlingRightClickMessages
主控\Quick\resource.h 资源 ID 定义 IDR_TOOLBAR_RIGHTMENUID_BUTTON_*IDD_*
主控\Quick\Quick.rc 对话框、图标、工具栏、字符串资源 IDD_*IDR_*STRINGTABLE

这里先讲主控界面布局。具体功能窗口内部逻辑会在后续主控架构课和插件专栏中展开。

# 5. 主框架类里有哪些界面成员

先看 MainFrm.h。这里能看到主窗口持有哪些界面对象。

// 源码位置:主控\Quick\MainFrm.h:122-131
CXTPDockingPaneManager          m_paneManager;

// 防御分析:下面这些都是主控内嵌停靠面板。
// 面板名称可以帮助安全人员快速建立主控能力目录。
CXTPDockingPane*                m_pwndPanelist;             // 首页列表
CXTPDockingPane*                m_pwndPanemonitor;          // 屏幕监控
CXTPDockingPane*                m_pwndPaneLog;              // 日志信息
CXTPDockingPane*                m_pwndPanePlug;             // 插件管理
CXTPDockingPane*                m_pwndPaneBuild;            // 客户生成
CXTPDockingPane*                m_pwndPaneChart;            // 系统统计
CXTPDockingPane*                m_pwndPaneDDOS;             // 压力测试
CXTPDockingPane*                m_pwndPaneSystemSettings;   // 系统设置

// 防御分析:面板 ID 到窗口对象的映射表。
// 审计时可用它理解“面板壳”和“实际窗口类”的绑定关系。
CMap<UINT, UINT, CWnd*, CWnd*>  m_mapPanes;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这段代码说明:主控界面不是只由一个列表组成,它还把日志、插件、生成器、统计、屏幕监控等组件做成了停靠面板。安全分析时,面板名称本身就是能力线索。

# 6. OnCreate:主控窗口创建时搭起界面骨架

CMainFrame::OnCreate 是主框架窗口创建阶段的重要函数。第 6 课讲过 MFC 消息映射,本课要把它放到界面布局里看。

// 源码位置:主控\Quick\MainFrm.cpp:487-620
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // 防御分析:创建状态栏。
    // 状态栏会显示免责声明、监听状态、在线数量和视频墙参数。
    if (!m_wndStatusBar.Create(this) ||
        !m_wndStatusBar.SetIndicators(g_indicators, sizeof(g_indicators) / sizeof(UINT)))
    {
        TRACE0("Failed to create status bar\n");
        return -1;
    }

    // 防御分析:安装 Codejock 停靠面板管理器。
    // 后续每个 CreatePane 都会把一个界面类挂入主框架。
    m_paneManager.InstallDockingPanes(this);
    XTPPaintManager()->RefreshMetrics();
    m_paneManager.SetTheme(xtpPaneThemeVisualStudio2005Beta2);

    // 防御分析:创建主控内部面板。
    // 这里能直接读出主控功能面板的范围。
    m_pwndPanelist = CreatePane(250, 25, RUNTIME_CLASS(CPeneListView), _T("首页列表"), xtpPaneDockBottom);
    m_pwndPanemonitor = CreatePane(250, 25, RUNTIME_CLASS(CScreenMonitorDlg), _T("屏幕监控"), xtpPaneDockBottom, m_pwndPanelist);
    m_pwndPaneLog = CreatePane(250, 25, RUNTIME_CLASS(CLogView), _T("日志信息"), xtpPaneDockBottom, m_pwndPanemonitor);
    m_pwndPanePlug = CreatePane(250, 25, RUNTIME_CLASS(CPlugView), _T("插件管理"), xtpPaneDockBottom, m_pwndPaneLog);
    m_pwndPaneBuild = CreatePane(250, 25, RUNTIME_CLASS(CBuildDlg), _T("客户生成"), xtpPaneDockBottom, m_pwndPanePlug);
    m_pwndPaneDDOS = CreatePane(250, 25, RUNTIME_CLASS(CDDOSAttackDlg), _T("压力测试"), xtpPaneDockBottom, m_pwndPaneBuild);
    m_pwndPaneChart = CreatePane(250, 25, RUNTIME_CLASS(CChartView), _T("系统统计"), xtpPaneDockBottom, m_pwndPaneDDOS);

    // 防御分析:锁定面板拆分器和关闭能力。
    // 这属于界面行为控制,说明面板布局被设计成固定工作台。
    m_paneManager.LockSplitters();
    m_pwndPanelist->SetOptions(xtpPaneNoCloseable | xtpPaneNoFloatable | xtpPaneNoCaption | xtpPaneNoHideable);

    // 防御分析:状态栏加入视频墙宽度、高度、刷新周期等控件。
    // 这些参数会影响后续屏幕监控视图,应纳入界面到功能的追踪链。
    m_pPaneW = m_wndStatusBar.AddIndicator(ID_MONITOR_W, 2);
    m_pZoomPaneW = (CXTPStatusBarSliderPane*)m_wndStatusBar.AddIndicator(
        new CXTPStatusBarSliderPane(), ID_MONITOR_SLIDER_W, 3);

    showMonitorButtonsOnStatusBar(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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

这段代码可以拆成四层:

层次 代码线索 说明
主框架 CFrameWnd::OnCreate MFC 主窗口创建入口
状态栏 m_wndStatusBar.Create 底部状态、提示和参数控制
停靠面板 m_paneManagerCreatePane 日志、插件、生成器、监控等面板
参数控件 ID_MONITOR_WID_MONITOR_SLIDER_W 视频墙参数,不是普通文本

安全人员读到这里,应先建立“界面资产清单”:这个主控窗口包含哪些面板、哪些参数、哪些状态提示,这些都是后续静态分析和事件关联的入口。

# 7. CreatePane:面板壳和实际窗口类的绑定

CreatePane 把一个停靠面板和一个实际窗口类绑定起来。它是理解主控布局的关键小函数。

// 源码位置:主控\Quick\MainFrm.cpp:863-886
CXTPDockingPane* CMainFrame::CreatePane(
    int x,
    int y,
    CRuntimeClass* pNewViewClass,
    CString strFormat,
    XTPDockingPaneDirection direction,
    CXTPDockingPane* pNeighbour)
{
    static int nID = 1000;
    nID++;

    // 防御分析:创建停靠面板外壳,nID 是面板内部 ID。
    CXTPDockingPane* pwndPane = m_paneManager.CreatePane(
        nID, CRect(0, 0, x, y), direction, pNeighbour);

    // 防御分析:设置面板标题和图标。
    // 面板标题会暴露模块用途,是静态识别主控能力的线索。
    CString strTitle;
    strTitle.Format(strFormat, nID);
    pwndPane->SetTitle(strTitle);
    pwndPane->SetIconID(nID % 6 + 1);

    // 防御分析:真正的窗口类由 pNewViewClass 创建。
    // 例如 CLogView、CPlugView、CBuildDlg、CScreenMonitorDlg。
    CFrameWnd* pFrame = new CFrameWnd;
    CCreateContext context;
    context.m_pNewViewClass = pNewViewClass;
    context.m_pCurrentDoc = g_pTabView->GetDocument();

    pFrame->Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        CRect(0, 0, 0, 0), this, NULL, 0, &context);

    // 防御分析:保存面板 ID 到实际窗口对象的映射。
    m_mapPanes.SetAt(nID, pFrame);
    return pwndPane;
}
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

新手要注意:CXTPDockingPane 是面板壳,pNewViewClass 才是具体内容。比如“日志信息”这个面板,实际类是 CLogView;“插件管理”面板,实际类是 CPlugView

# 8. 工具栏资源与按钮 ID

主控工具栏由资源 ID 和命令映射共同决定。先看资源 ID。

// 源码位置:主控\Quick\resource.h:103,629-649,693

// 防御分析:工具栏资源本身的 ID。
#define IDR_TOOLBAR_RIGHTMENU           491

// 防御分析:下面这些按钮 ID 对应主控界面上的功能入口。
// 本课只用于源码定位和防御审计,不提供功能触发步骤。
#define ID_BUTTON_FILE                  32896
#define ID_BUTTON_DIFSCREEN             32898
#define ID_BUTTON_QUICKSCREEN           32899
#define ID_BUTTON_HIDESCREEN            32901
#define ID_BUTTON_AUDIO                 32903
#define ID_BUTTON_WEBCAM                32904
#define ID_BUTTON_CMD                   32907
#define ID_BUTTON_REGEDIT               32909
#define ID_BUTTON_PROXY                 32910
#define ID_BUTTON_CHAT                  32916
#define ID_BUTTON_ADDMONITOR            32961
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

再看主框架如何加载工具栏。

// 源码位置:主控\Quick\MainFrm.cpp:888-934
BOOL CMainFrame::adddCXTPToolBar()
{
    // 防御分析:初始化 Codejock 命令栏系统。
    if (!InitCommandBars())
    {
        return FALSE;
    }

    CXTPCommandBars* pCommandBars = GetCommandBars();
    if (pCommandBars == NULL)
    {
        TRACE0("Failed to create command bars object.\n");
        return FALSE;
    }

    // 防御分析:添加名为“功能”的工具栏,并加载 IDR_TOOLBAR_RIGHTMENU。
    // 这说明工具栏按钮和 resource.h / Quick.rc 中的资源 ID 绑定。
    CXTPToolBar* pToolBar = (CXTPToolBar*)pCommandBars->Add(_T("功能"), xtpBarTop);
    if (!pToolBar || !pToolBar->LoadToolBar(IDR_TOOLBAR_RIGHTMENU))
    {
        TRACE0("Failed to create toolbar\n");
        return FALSE;
    }

    pToolBar->ShowTextBelowIcons(TRUE);
    return TRUE;
}
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

这里的阅读方法是:

  1. resource.h 找按钮 ID。
  2. Quick.rc 找工具栏资源和字符串资源。
  3. QuickView.cppMainFrm.cppON_COMMAND(ID_BUTTON_..., 函数名)
  4. 再看处理函数做了什么。

# 9. 从界面入口反查源码的固定路线

新手最容易犯的错,是直接在源码里全文搜索某个中文功能名,然后跳到很多不相关位置。更稳定的路线如下:

第 7 课图 2:从界面入口反查源码路径

以“文件管理”这个入口为例,反查路线是:

步骤 要找什么 典型文件
1 按钮或菜单资源 ID resource.hQuick.rc
2 命令映射 QuickView.cppBEGIN_MESSAGE_MAP
3 按钮处理函数 OnMenuitemFILE
4 统一入口处理 HandlingRightClickMessages
5 主框架弹窗消息 WM_OPENMANAGERDIALOG
6 创建功能窗口 CMainFrame::OnOpenmanagerdialog
7 后续风险分析 网络通信、插件加载、文件操作、日志

这条路线同样适用于终端、注册表、音频、视频、屏幕等入口。差别只是资源 ID、菜单项、窗口类和后续协议不同。

# 10. CTabView:分组页签怎么承载连接列表

主控中间的分组页签由 CTabView 管理,每个分组页签里通常挂一个 CQuickView 连接列表。

// 源码位置:主控\Quick\TabView.cpp:26-36
BEGIN_MESSAGE_MAP(CTabView, CView)
    // 防御分析:页签视图的基本窗口消息。
    ON_WM_CREATE()
    ON_WM_ERASEBKGND()
    ON_WM_CONTEXTMENU()
    ON_WM_SIZE()

    // 防御分析:页签切换事件。
    ON_NOTIFY(TCN_SELCHANGE, IDC_TABCONTROL, OnSelectedChanged)

    // 防御分析:收到上线分组消息后,进入 OnAddFindGroup。
    ON_MESSAGE(WM_ADDFINDGROUP, OnAddFindGroup)
END_MESSAGE_MAP()
1
2
3
4
5
6
7
8
9
10
11
12
13
14

OnCreate 负责创建底部页签控件。

// 源码位置:主控\Quick\TabView.cpp:94-109
int CTabView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    // 防御分析:创建分组页签控件。
    // IDC_TABCONTROL 是资源 ID,后续页签变化会触发 OnSelectedChanged。
    m_wndTabControl.Create(WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        CRect(0, 0, 0, 0), this, IDC_TABCONTROL);
    m_wndTabControl.GetPaintManager()->SetAppearance(xtpTabAppearanceExcel);
    m_wndTabControl.SetPosition(xtpTabPositionBottom);
    m_wndTabControl.SetColor(xtpTabColorOffice2003);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

AddGroupAddView 说明每个分组页签里创建的是 CQuickView

// 源码位置:主控\Quick\TabView.cpp:112-158
BOOL CTabView::AddGroup(LPCTSTR lpszTitle)
{
    // 防御分析:每个分组页签默认添加 CQuickView。
    // 这说明连接列表是按分组拆分展示的。
    return AddView(RUNTIME_CLASS(CQuickView), lpszTitle);
}

BOOL CTabView::AddView(CRuntimeClass* pViewClass, LPCTSTR lpszTitle)
{
    CCreateContext contextT;
    contextT.m_pCurrentDoc = GetDocument();
    contextT.m_pNewViewClass = pViewClass;

    // 防御分析:通过运行时类创建具体视图对象。
    CWnd* pWnd = (CWnd*)pViewClass->CreateObject();

    int nTab = m_wndTabControl.GetItemCount();
    CRect rect(0, 0, 0, 0);
    if (!pWnd->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
        rect, &m_wndTabControl, (AFX_IDW_PANE_FIRST + nTab), &contextT))
    {
        return FALSE;
    }

    // 防御分析:把视图窗口句柄插入页签。
    m_wndTabControl.InsertItem(nTab, lpszTitle, pWnd->GetSafeHwnd());
    pWnd->SetOwner(this);
    return TRUE;
}
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

防御侧理解这一点很重要:主控不是只有一个“在线列表”,而是按分组维护多个 CQuickView。如果要审计上线对象展示、分组变化、备注变化,就要看 CTabViewCQuickView 的配合。

# 11. CQuickView:连接列表的列和右键入口

CQuickView 继承自 CXTPReportView,可以把它理解为一个增强版表格视图。先看列定义。

// 源码位置:主控\Quick\QuickView.cpp:159-184
typedef struct
{
    TCHAR* title;        // 防御分析:列标题,暴露列表展示的信息类型。
    int    nWidth;       // 防御分析:列宽。
    BOOL   bIsShow;      // 防御分析:默认是否显示。
    BOOL   boutosize;    // 防御分析:是否自动调整列宽。
    int    nMaxWidth;    // 防御分析:最大列宽。
} LISTHEAD;

LISTHEAD g_Column_Data[] =
{
    {_T("定位/监控"),             65, 1, 1, 300},
    {_T("离开/刷新"),             60, 1, 0, 70},
    {_T("窗口/☝/截图"),           80, 1, 1, 200},
    {_T("系统/管理"),             130, 1, 0, 160},
    {_T("exe os/注册"),           100, 1, 0, 60},
    {_T("视频/查看"),             60, 1, 0, 30},
    {_T("聊天/交谈"),             55, 1, 1, 200},
    {_T("备注/修改"),             40, 1, 1, 100},
    {_T("通信/文件"),             60, 1, 0, 30},
    {_T("端口/终端"),             60, 1, 0, 40},
    {_T("PID/权限/代理"),         80, 1, 0, 120},
    {_T("反馈/切换加载方式"),      80, 1, 1, 200},
};
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

列标题说明连接列表不仅显示在线状态,还把监控、系统、注册、视频、文件、终端、权限、代理等信息组织到同一行里。防御侧可以把这些列看成主控关注的信息维度。

再看 CQuickView 的消息映射。

// 源码位置:主控\Quick\QuickView.cpp:41-88
BEGIN_MESSAGE_MAP(CQuickView, CXTPReportView)
    ON_WM_CREATE()
    ON_WM_SETFOCUS()
    ON_WM_DESTROY()

    // 防御分析:报表头、列表行、双击、右键等交互入口。
    ON_NOTIFY(XTP_NM_REPORT_HEADER_RCLICK, XTP_ID_REPORT_CONTROL, OnShowFilterEditandOnShowFieldChooser)
    ON_NOTIFY(NM_CLICK, XTP_ID_REPORT_CONTROL, OnShowGroup)
    ON_NOTIFY(XTP_NM_REPORT_LBUTTONDOWN, XTP_ID_REPORT_CONTROL, OnReportLButtonDown)
    ON_NOTIFY(NM_DBLCLK, XTP_ID_REPORT_CONTROL, OnReportDBLCLK)
    ON_NOTIFY(NM_RCLICK, XTP_ID_REPORT_CONTROL, OnReportItemRClick)

    // 防御分析:连接对象新增和下线删除入口。
    ON_MESSAGE(WM_ADDTOMAINLIST, OnAddtomainlist)
    ON_MESSAGE(WM_REMOVEFROMLIST, OnRemoveFromList)

    // 防御分析:工具栏按钮入口。
    // 每个 ID_BUTTON_* 都应继续追踪到对应处理函数。
    ON_COMMAND(ID_BUTTON_FILE, OnMenuitemFILE)
    ON_COMMAND(ID_BUTTON_DIFSCREEN, OnMenuitemDIFSCREEN)
    ON_COMMAND(ID_BUTTON_QUICKSCREEN, OnMenuitemQUICKSCREEN)
    ON_COMMAND(ID_BUTTON_HIDESCREEN, OnMenuitemHIDESCREEN)
    ON_COMMAND(ID_BUTTON_AUDIO, OnMenuitemAUDIO)
    ON_COMMAND(ID_BUTTON_WEBCAM, OnMenuitemWEBCAM)
    ON_COMMAND(ID_BUTTON_CMD, OnMenuitemCMD)
    ON_COMMAND(ID_BUTTON_REGEDIT, OnMenuitemREGEDIT)
    ON_COMMAND(ID_BUTTON_PROXY, OnMenuitemPROXY)
    ON_COMMAND(ID_BUTTON_CHAT, OnMenuitemCHAT)
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

这个消息映射就是连接列表上的功能入口表。读者不需要运行界面,也能从这里知道:连接列表支持哪些交互,以及这些交互会进入哪些处理函数。

# 12. 连接列表列配置和注册表

CQuickView::OnCreate 不只创建列,还会读取列显示配置。

// 源码位置:主控\Quick\QuickView.cpp:186-320
int CQuickView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CXTPReportView::OnCreate(lpCreateStruct) == -1)
        return -1;

    wndReport = &GetReportCtrl();
    R_g_Column_Data = new BYTE[g_Column_Count];

    // 防御分析:添加固定序号列。
    CXTPReportColumn* pColumnFixed = wndReport->AddColumn(
        new CXTPReportColumn(0, _T("序号/屏幕"), 60));

    // 防御分析:下面是报表显示、排序、网格、多选、列调整等界面行为。
    // 这些设置影响审计人员看到的数据形态。
    wndReport->FocusSubItems(TRUE);
    wndReport->EnableDoubleBuffering(TRUE);
    GetReportCtrl().AllowSort(TRUE);
    GetReportCtrl().SetMultipleSelection(TRUE);
    GetReportCtrl().GetReportHeader()->AllowColumnResize(TRUE);
    GetReportCtrl().GetReportHeader()->AllowColumnReorder(TRUE);

    // 防御分析:读取当前用户注册表中的列显示配置。
    // ColumnData 是可审计的本地配置项。
    HKEY hKEY;
    BOOL b_RegConfi = FALSE;
    if (ERROR_SUCCESS == ::RegOpenKeyEx(HKEY_CURRENT_USER,
        ((CQuickApp*)AfxGetApp())->m_strExeName.GetBuffer(),
        0, KEY_READ, &hKEY))
    {
        DWORD dwSize = g_Column_Count;
        DWORD dwType = REG_BINARY;
        if (::RegQueryValueEx(hKEY, _T("ColumnData"), 0, &dwType,
            (LPBYTE)R_g_Column_Data, &dwSize) == ERROR_SUCCESS)
        {
            b_RegConfi = TRUE;
        }
    }

    // 防御分析:根据配置创建并显示列。
    for (int i = 0; i < g_Column_Count; i++)
    {
        CXTPReportColumn* Coll = new CXTPReportColumn(
            wndReport->GetColumns()->GetCount(),
            g_Column_Data[i].title,
            g_Column_Data[i].nWidth);
        wndReport->AddColumn(Coll);
    }
}
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

这里有一个很实用的检测点:主控端本地配置也会留下痕迹。比如列显示配置 ColumnData 可以帮助取证人员判断程序是否被打开、界面是否被配置过、列表展示习惯是否被修改过。

# 13. 工具栏按钮如何进入统一处理函数

工具栏按钮并不是每个都直接打开窗口。很多按钮先进入 OnMenuitemXxx,再统一进入 HandlingRightClickMessages

// 源码位置:主控\Quick\QuickView.cpp:1922-1983
void CQuickView::OnMenuitemFILE()        // 文件管理
{
    // 防御分析:文件管理按钮不在这里直接创建窗口,
    // 而是把菜单枚举交给统一处理函数。
    HandlingRightClickMessages(MENU_文件管理);
}

void CQuickView::OnMenuitemCMD()         // 远程终端
{
    // 防御分析:终端类入口属于高风险能力。
    // 本教程只标注入口链路,不提供使用步骤。
    HandlingRightClickMessages(MENU_远程终端);
}

void CQuickView::OnMenuitemREGEDIT()     // 查注册表
{
    // 防御分析:注册表入口要继续关联后续注册表读写 API。
    HandlingRightClickMessages(MENU_查注册表);
}

void CQuickView::OnMenuitemPROXY()       // 代理映射
{
    // 防御分析:代理类入口应重点关联网络连接、端口和转发行为。
    HandlingRightClickMessages(MENU_代理映射);
}
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

统一处理函数里可以看到一批功能入口最后会走到插件或功能调度逻辑。

// 源码位置:主控\Quick\QuickView.cpp:1394-1435
case MENU_文件管理:
case MENU_差异屏幕:
case MENU_高速屏幕:
case MENU_娱乐屏幕:
case MENU_后台屏幕:
case MENU_播放监听:
case MENU_语音监听:
case MENU_视频查看:
case MENU_系统管理:
case MENU_远程终端:
case MENU_键盘记录:
case MENU_查注册表:
case MENU_代理映射:
case MENU_远程交谈:
case MENU_启动管理:
case MENU_驱动插件:
{
    // 防御分析:根据菜单 ID 取出菜单文本,再进入 SendDll。
    // SendDll 是界面入口和插件/网络调度之间的重要边界。
    // 高风险能力只做防御说明,不给复用步骤。
    CString menuStr;
    mListmeau.GetMenuString(nMenuResult, menuStr, MF_BYCOMMAND);
    int nfind = menuStr.ReverseFind('(');
    if (nfind > 0)
    {
        menuStr = menuStr.Mid(0, nfind);
    }

    // 防御分析:这里是审计重点,应继续追踪 SendDll 的参数、目标对象、
    // 插件名称、网络发送路径和日志记录。
    SendDll(menuStr.GetBuffer());
    log_信息(menuStr);
}
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

这段代码说明“界面功能”和“插件调度”之间存在衔接。审计时不能只写“有工具栏按钮”,还要继续追踪:

审计问题 为什么重要
当前选中了哪些连接对象 功能入口通常依赖 ClientContext
menuStr 最终是什么 可能决定插件或功能名称
SendDll 做了什么 可能进入插件发送、加载或网络命令链路
日志是否记录 影响企业审计和取证

# 14. CMainFrame 如何创建功能弹窗

当功能入口进入主框架后,CMainFrame 会创建对应对话框。下面只选三类代表性窗口:终端、文件、注册表。

// 源码位置:主控\Quick\MainFrm.cpp:2487-2580

LRESULT CMainFrame::OnOpenshelldialog(WPARAM wParam, LPARAM lParam)
{
    // 防御分析:lParam 被解释为 ClientContext。
    // 说明弹窗和某个连接上下文绑定。
    ClientContext* pContext = (ClientContext*)lParam;

    // 防御分析:创建终端相关对话框。
    // 终端能力属于高风险入口,只做防御识别。
    CShellDlg* dlg = new CShellDlg(this, g_pSocketBase, pContext);
    pContext->m_Dialog[0] = SHELL_DLG;
    pContext->m_Dialog[1] = (int)dlg;
    dlg->Create(IDD_SHELL, GetDesktopWindow());
    dlg->ShowWindow(SW_SHOW);
    return 0;
}

LRESULT CMainFrame::OnOpenmanagerdialog(WPARAM wParam, LPARAM lParam)
{
    // 防御分析:文件管理对话框同样绑定 g_pSocketBase 和 ClientContext。
    ClientContext* pContext = (ClientContext*)lParam;
    CFileManagerDlg* dlg = new CFileManagerDlg(this, g_pSocketBase, pContext);
    pContext->m_Dialog[0] = FILEMANAGER_DLG;
    pContext->m_Dialog[1] = (int)dlg;
    dlg->Create(IDD_FILE, GetDesktopWindow());
    dlg->ShowWindow(SW_SHOW);
    return 0;
}

LRESULT CMainFrame::OnOpenregeditdialog(WPARAM wParam, LPARAM lParam)
{
    // 防御分析:注册表对话框是后续注册表读写行为的界面入口。
    ClientContext* pContext = (ClientContext*)lParam;
    CRegeditDlg* dlg = new CRegeditDlg(this, g_pSocketBase, pContext);
    pContext->m_Dialog[0] = REGEDIT_DLG;
    pContext->m_Dialog[1] = (int)dlg;
    dlg->Create(IDD_REGEDIT, GetDesktopWindow());
    dlg->ShowWindow(SW_SHOW);
    return 0;
}
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

这里有两个审计细节:

细节 审计意义
对话框构造函数带 g_pSocketBase 功能窗口和网络通信层存在直接联系
对话框构造函数带 ClientContext 功能窗口和某个连接对象绑定
pContext->m_Dialog[0] 记录窗口类型 后续收包分发会根据窗口类型转给对应对话框
pContext->m_Dialog[1] = (int)dlg 在 64 位场景中要关注指针截断风险,审计时应记录平台假设

最后一点尤其适合安全工程学习者训练代码质量审计。虽然主控工程主要按 Win32 编译,但把指针保存到 int 在跨平台或 x64 移植时会产生风险。

# 15. 连接列表到功能弹窗的完整链路

把上面几段源码合起来,可以得到下面的链路:

第 7 课图 3:连接列表到功能弹窗的调用链

用文字描述就是:

  1. 网络或上线数据形成 ClientContext
  2. CTabView 根据分组创建或选择 CQuickView
  3. CQuickView 在连接列表中展示上线对象。
  4. 工具栏、右键或双击入口进入 OnMenuitemXxx 或列表事件函数。
  5. HandlingRightClickMessages 统一做菜单处理和功能调度。
  6. CMainFrame::OnOpen*dialog 创建功能窗口。
  7. 功能窗口持有 g_pSocketBaseClientContext,后续进入网络通信和插件链路。

防御分析时,最重要的是第 5 到第 7 步。因为这几步把“界面入口”连接到了“网络命令、插件加载或系统 API”。如果只看界面文件,会漏掉真正的风险;如果只看插件文件,又会漏掉用户入口和上下文来源。

# 16. 主控界面资源如何帮助静态识别

资源文件在安全分析中经常被忽视。实际上,resource.hQuick.rc 对静态识别很有帮助:

资源线索 可以帮助判断什么
ID_BUTTON_* 工具栏和菜单入口
IDD_* 功能窗口和对话框类型
STRINGTABLE 按钮提示、菜单名称、产品名、状态提示
ICON 图标资源和窗口标识
DIALOGEX 窗口控件、输入框、按钮、列表和树控件

企业侧做样本分析时,即使没有运行程序,也可以从资源表中提取功能名称、按钮 ID、对话框名称和产品字符串,再和源码或二进制行为关联。

# 17. 主控界面布局的企业检测点

检测方向 具体检测点 数据来源
静态能力识别 工具栏 ID、菜单字符串、对话框 ID、窗口类名 源码、资源表、二进制字符串
UI 到网络关联 功能窗口是否持有 socket 对象和连接上下文 源码审计、调试日志、EDR 行为图
插件调度 SendDll 参数、插件名称、加载路径、版本信息 源码审计、网络日志、文件/模块事件
连接上下文 ClientContext 生命周期、分组、备注、在线状态 源码、内存取证、日志
本地配置 ColumnData、MFC 配置键、工具栏布局 注册表、用户配置目录
高风险入口 终端、文件、注册表、屏幕、音频、摄像头、代理 资源表、消息映射、网络协议
代码质量 指针转 int、窗口对象释放、异常路径 静态审计、代码扫描

检测时建议把界面入口当作“行为意图”信号,把网络、插件和系统 API 当作“实际执行”信号。两类信号关联起来,误报会更少。

# 18. 新手常见误区

误区 正确做法
看到界面按钮就认为已经执行了功能 继续追消息映射、处理函数和上下文
只看 Quick.rc,不看 resource.h 资源名称和数值 ID 要结合看
只看 OnMenuitemXxx,不看统一处理函数 很多入口会进入 HandlingRightClickMessages
忽略 ClientContext 主控功能通常和具体连接对象绑定
忽略状态栏和注册表配置 本地 UI 配置也是取证线索
把界面分析当成操作手册 只做授权环境下的源码审计和检测设计

# 19. 本课小结

本课把主控界面拆成了五块:主框架、工具栏、分组页签、连接列表、停靠面板和状态栏。对新手来说,最重要的不是记住每个类名,而是掌握反查路线:

  1. 从界面入口找资源 ID。
  2. 从资源 ID 找消息映射。
  3. 从消息映射找处理函数。
  4. 从处理函数找统一调度和弹窗创建。
  5. 从弹窗对象继续追网络通信、插件加载和系统 API。

这样读源码会比随意搜索函数名更连续,也更适合企业安全检测和代码审计。

# 20. 合法练习题

  1. 主控\Quick\resource.h 中找出 5 个 ID_BUTTON_*,记录它们的大致行号和可能对应的功能。
  2. 主控\Quick\QuickView.cpp 中搜索这 5 个按钮 ID,写出对应 ON_COMMAND 和处理函数。
  3. 任选一个入口,继续追到 HandlingRightClickMessages,只写调用链,不运行程序。
  4. 主控\Quick\MainFrm.cpp 中找出 3 个 OnOpen*dialog 函数,记录对话框类、IDD_*m_Dialog[0] 标记。
  5. 设计一张企业审计表:字段至少包含资源 ID、入口函数、上下文对象、网络边界、风险等级和检测数据源。

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

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

社区交流

讨论与留言

前往 GitHub Issues →

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

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