实战指南:FRP 流量特征深度伪装与隐蔽通信改造

0. 背景 : 流量特征分析与合规预警

在企业级网络策略中,标准版 frp 流量因具备明显的协议指纹,极易被防火墙或 DPI(深度包检测)设备识别并阻断。主要特征包括:

  1. TLS 指纹:固定的 ClientHello 首字节(0x17)及特定加密套件顺序。
  2. 明文特征:控制信道中包含 privilege_key 等明文 JSON 字段。
  3. 行为特征:固定频率(默认 30s)且包大小恒定的心跳包。

本文基于 frp v0.65.0 源码,通过协议头注入、TLS 指纹模拟、载荷混淆及心跳随机化四个维度,将 frp 流量伪装为普通 HTTPS 浏览器访问行为。

⚠️ 核心警告

  • 不兼容性:本文涉及的修改会导致客户端(frpc)与原版服务端(frps)无法互通,必须两端同时编译部署。
  • 法律风险:此类修改属于网络防御规避(Defense Evasion)。在未经授权的网络环境中使用可能违反法律或公司合规政策,请仅用于安全测试或科研环境。

1. 协议头注入 : 伪装 HTTP 握手

原理:在 TCP 连接建立后、TLS 握手前,插入 HTTP 请求交互。防火墙会先识别到合法的 HTTP 流量(模拟访问百度),从而放行后续流量。

1.1 客户端修改

文件路径client/connector.go
逻辑:在 realConnect 方法中,TCP 连接建立后立即发送 HTTP GET 请求,并校验服务端返回的 200 OK。

if protocol == "tcp" {
    // 1. 发送伪造 HTTP 请求头 (模拟访问白名单域名)
    fakeHeader := "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: Mozilla/5.0\r\nAccept: */*\r\n\r\n"
    if _, err := conn.Write([]byte(fakeHeader)); err != nil {
        conn.Close()
        return nil, fmt.Errorf("write fake header error: %v", err)
    }

    // 2. 读取并校验服务端响应
    buf := make([]byte, 1024)
    conn.SetReadDeadline(time.Now().Add(5 * time.Second))
    n, err := conn.Read(buf)
    conn.SetReadDeadline(time.Time{}) // 重置超时

    if err != nil {
        conn.Close()
        return nil, fmt.Errorf("read fake response error: %v", err)
    }

    // 必须包含 200 OK 才能继续
    if !strings.Contains(string(buf[:n]), "200 OK") {
        conn.Close()
        return nil, fmt.Errorf("invalid fake response")
    }
    // ... 后续标准 TLS 握手逻辑 ...
}

1.2 服务端修改

文件路径server/service.go
逻辑:引入中间层 FakeHandshakeListener,拦截并“吞掉”伪造的 HTTP 流量,仅将后续真实流量透传给 frp 逻辑。

// 自定义 Listener 包装器
type FakeHandshakeListener struct {
    net.Listener
}

func (l *FakeHandshakeListener) Accept() (net.Conn, error) {
    c, err := l.Listener.Accept()
    if err != nil { return nil, err }
    return &FakeHandshakeConn{Conn: c}, nil
}

// 自定义 Conn 包装器
type FakeHandshakeConn struct {
    net.Conn
    handshakeDone bool
}

func (c *FakeHandshakeConn) Read(b []byte) (n int, err error) {
    // 握手完成后直接透传
    if c.handshakeDone {
        return c.Conn.Read(b)
    }

    // 1. 读取客户端伪造头
    buf := make([]byte, 1024)
    n, err = c.Conn.Read(buf)
    if err != nil { return 0, err }
    data := string(buf[:n])

    // 2. 校验特征 (Host: www.baidu.com)
    if strings.Contains(data, "Host: www.baidu.com") {
        // 3. 回复伪造 200 OK
        _, _ = c.Conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"))
        c.handshakeDone = true
        return 0, nil // 返回 0 字节,向上层隐藏此次交互
    }

    c.Conn.Close()
    return 0, fmt.Errorf("invalid handshake")
}

2. TLS 指纹伪装 : 模拟 Chrome

原理:Go 标准库 crypto/tls 的指纹特征固定。使用 utls 库完全模拟 Chrome 浏览器的 JA3 指纹,并移除 frp 特有的魔数。

2.1 引入 utls 依赖

go get github.com/refraction-networking/utls

2.2 客户端集成 utls

文件路径client/connector.go

import utls "github.com/refraction-networking/utls"

// ... 在 realConnect 中替换原有 TLS 逻辑 ...

tlsEnable := true // 强制开启 TLS

if tlsConfig != nil {
    uConfig := &utls.Config{
        ServerName:         tlsConfig.ServerName,
        InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
        RootCAs:            tlsConfig.RootCAs,
        NextProtos:         tlsConfig.NextProtos,
    }

    // 关键:使用 HelloChrome_Auto 模拟 Chrome 指纹
    uConn := utls.UClient(conn, uConfig, utls.HelloChrome_Auto)
    if err := uConn.Handshake(); err != nil {
        conn.Close()
        return nil, err
    }
    conn = uConn // 替换连接对象
}

2.3 移除特征字节

文件路径pkg/util/net/tls.go
逻辑:FRP 默认在 TLS 前发送 0x17,这是极强特征,需修改为标准 TLS ClientHello 首字节 0x16

// var FRPTLSHeadByte = 0x17 // 原版
var FRPTLSHeadByte = 0x16    // 修改后

注:需同步注释掉 pkg/util/net/dial.go 中的 DialHookCustomTLSHeadByte 写入逻辑。

3. 载荷特征混淆 : JSON 字段简化

原理:缩短并混淆 JSON 字段名,规避针对 privilege_key 等关键词的正则匹配。

文件路径pkg/msg/msg.go

type Login struct {
    Version      string            `json:"v,omitempty"`  // version -> v
    Hostname     string            `json:"h,omitempty"`  // hostname -> h
    PrivilegeKey string            `json:"pk,omitempty"` // privilege_key -> pk
    Timestamp    int64             `json:"ts,omitempty"` // timestamp -> ts
    RunID        string            `json:"ri,omitempty"` // run_id -> ri
}

type Ping struct {
    PrivilegeKey string `json:"pk,omitempty"`
    Timestamp    int64  `json:"ts,omitempty"`
    Padding      string `json:"p,omitempty"` // 新增填充字段
}

4. 心跳逻辑重构 : 随机化与抗分析

原理:打破固定时间间隔和包大小的统计特征。

文件路径client/control.go
修改点

  1. 立即启动:连接建立后立即发送首个心跳。
  2. 随机抖动:心跳间隔增加 ±20% 随机值。
  3. 垃圾填充:在 Padding 字段填充随机长度字符。
go func() {
    // 1. 立即发送首个心跳
    if _, err := sendHeartBeat(); err != nil {
        xl.Warnf("send first heartbeat error: %v", err)
    }

    for {
        // 2. 计算基准时间 + 随机抖动 (30s ± 20%)
        baseInterval := time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatInterval) * time.Second
        jitter := time.Duration(rand.Intn(int(baseInterval)/5*2) - int(baseInterval)/5)

        select {
        case <-time.After(baseInterval + jitter):
            // 3. 发送带 Padding 的心跳
            if _, err := sendHeartBeat(); err != nil {
                xl.Warnf("send heartbeat error: %v", err)
            }
        case <-ctl.doneCh:
            return
        }
    }
}()

5. 构建与部署 : 编译配置

5.1 编译命令 (PowerShell)

必须禁用 CGO 以确保静态链接。

# Linux 服务端/客户端
$env:CGO_ENABLED="0"; $env:GOOS="linux"; $env:GOARCH="amd64"
go build -ldflags "-s -w" -o frps ./cmd/frps
go build -ldflags "-s -w" -o frpc_linux ./cmd/frpc

# Windows 客户端
$env:CGO_ENABLED="0"; $env:GOOS="windows"; $env:GOARCH="amd64"
go build -ldflags "-s -w" -o frpc.exe ./cmd/frpc

5.2 配置文件关键项

配置文件参数项建议值说明
frps.initls_onlytrue强制走修改后的 TLS 逻辑
frps.iniheartbeat_timeout90放宽超时时间以适应随机心跳
frpc.initls_enabletrue客户端显式开启 TLS
frpc.iniheartbeat_interval30设置基准值触发随机逻辑

6. 验证 : 流量表现与局限性

完成修改并部署后,预期效果如下:

  • Wireshark 抓包
    • 连接初期显示为标准的 HTTP GET Host: www.baidu.com200 OK
    • 后续 TLS ClientHello 与 Chrome 浏览器完全一致(无 0x17 前缀)。
    • 载荷全加密,无明文 JSON 关键字。
  • 流量统计:心跳包大小动态变化,时间间隔无明显周期性。

局限性说明
尽管上述手段消除了协议指纹,但在高安全等级环境中(如 H800 计算集群),高级的 UEBA(用户实体行为分析) 仍可能通过以下特征发现异常:

  1. 非工作时间的持续长连接。
  2. 单 IP 出现异常的大流量吞吐。
  3. 目标服务器 IP 信誉度低(如家用宽带 IP)。

已发布

分类

来自

标签:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注