Sliver 植入体 mTLS 传输客户端源码深度解析:证书选择、会话复用与安全拨号
2026/9/23 12:30:38 网站建设 项目流程
  • 网络安全

【免费下载链接】sliver

Adversary Emulation Framework

项目地址:https://gitcode.com/gh_mirrors/sl/sliver
点击查看免费下载

导读

本文以 implant/sliver/transports/mtls/README.md 为核心线索,深入剖析 Sliver Adversary Emulation Framework 中植入体(implant)侧的 Mutual TLS(mTLS)传输客户端实现。该模块负责植入体与 C2 服务器之间建立双向 TLS 加密链路,涵盖证书加载与校验、长度前缀帧协议(length-prefix framing)、minisign 原始签名认证、心跳保活以及基于 yamux 的多路复用会话。读完本文,你将掌握 mtls.go 中每个关键函数的职责与调用链、帧格式的字节级细节、内置证书的构建期注入机制,以及对应的服务端与测试验证逻辑,可直接用于二次开发或排障。

模块定位:植入体外联传输层的核心组件

在 Sliver 中,植入体通过多种 C2 传输方式与服务器通信,包括 mTLS、HTTP(S)、DNS、WireGuard 与 Pivot 链。根据 implant/sliver/transports/README.md 的说明,mtls/子包是“植入体使用的 Mutual TLS 传输客户端,负责证书选择、会话复用与安全拨号”。其全部实现位于 mtls.go,由 mtls_test.go 与 read_envelope_test.go 提供单元测试保障。

该模块的调用方是传输层调度器:

  • beacon.go:Beacon 模式下调用mtls.MtlsConnect()建立连接,随后在每个 yamux 流上通过mtls.ReadEnvelope()/mtls.WriteEnvelope()收发帧。
  • session.go:交互式 Session 模式下同样先MtlsConnect,并周期性调用mtls.WritePing()发送心跳。

构建期条件编译

mtls.go是一个 Gotext/template渲染的源文件({{if .Config.IncludeMTLS}}包裹整个文件主体),意味着 mTLS 传输只有在植入体配置开启IncludeMTLS时才会被编译进二进制。这一点在 server/generate/external.go 中得到印证:

config.IncludeMTLS = config.IncludeMTLS || models.IsC2Enabled([]string{"mtls"}, config.C2)

只要植入体的 C2 配置中启用了mtls监听器,该传输就会被打包。

全局配置:心跳间隔、yamux 前言与内置证书

mtls.go 顶部定义了几个决定传输行为的全局变量,理解它们是阅读后续代码的前提:

变量默认值作用
PingInterval2 * time.Minute带内(in-band)心跳间隔,用于维持连接活性、探测断线
YamuxPreface"MUX/1"建立 yamux 会话前写入的魔数前缀,用于握手识别
caCertPEM{{.Build.MtlsCACert}}内嵌的 mTLS CA 证书(PEM)
keyPEM{{.Build.MtlsKey}}内嵌的植入体私钥(PEM)
certPEM{{.Build.MtlsCert}}内嵌的植入体证书(PEM)

后三个模板占位符在服务端构建植入体时被真实证书填充,见 server/generate/binaries.go:

build.MtlsCACert = string(serverCACert) build.MtlsCert = string(sliverCert) build.MtlsKey = string(sliverKey)

这意味着植入体二进制中自带完整的 mTLS 身份材料,无需在运行时加载外部文件——这是“证书选择”能力的核心实现方式。此外还有一个不可配置的常量maxEnvelopeLength = 512 * 1024 * 1024(512MB),用于限制单条信封的最大长度,防止恶意或损坏的帧导致内存失控。

证书选择与安全拨号:MtlsConnect / getTLSConfig

MtlsConnect(address string, port uint16) (*tls.Conn, error)是植入体的拨号入口,逻辑直白:组装address:port后调用tls.Dial("tcp", ...)。真正的复杂度集中在getTLSConfig(),它完成了三件关键工作:

  1. 加载本端证书tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))将构建期注入的 PEM 解析为tls.Certificate。若失败会记录日志并直接os.Exit(5)——这是致命错误,因为没有身份就无法完成双向认证。
  2. 加载 CA 池x509.NewCertPool()+AppendCertsFromPEM([]byte(caCertPEM))将内嵌的 mTLS CA 加入信任根。
  3. 自定义证书校验:设置InsecureSkipVerify: true的同时注册VerifyPeerCertificate回调,回调内部调用cryptography.RootOnlyVerifyCertificate(caCertPEM, rawCerts, verifiedChains)

为什么关闭标准校验?

由于植入体通过 IP 而非域名连接服务器,Go 标准 TLS 校验会因主机名(hostname)不匹配而失败。因此代码注释中有一句著名的自嘲:InsecureSkipVerify: true, // Don't worry I sorta know what I'm doing(别担心,我大概知道自己在干什么)。校验逻辑被重写为“只验证证书是否由我们的 CA 签发”,不再检查主机名。

这种“仅根证书校验”模式不是 mTLS 客户端独有——Sliver 的操作员控制端在 client/transport/mtls.go 中以完全相同的模式连接服务器(RootOnlyVerifyCertificate),且该文件注释明确引用了 Go 官方 issue #21971(Go 不提供跳过主机名校验的原生选项)。可见这是整个项目统一的 TLS 策略。

tlsConfig := &tls.Config{ Certificates: []tls.Certificate{certPEM}, RootCAs: caCertPool, InsecureSkipVerify: true, // 仅跳过主机名校验,证书链仍由回调验证 VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return cryptography.RootOnlyVerifyCertificate(caCertPEM, rawCerts, verifiedChains) }, }

在 Debug 构建下({{if .Config.Debug}}),若cryptography.TLSKeyLogger非空,还会将tlsConfig.KeyLogWriter指向密钥日志器,便于抓包分析 TLS 流量。

帧协议:带签名的长度前缀信封

mTLS 之上的应用层协议是“信封”(Envelope),本质是一个 protobuf 消息(pb.Envelope,定义于 protobuf/sliverpb/sliver.proto)。为了在字节流上可靠地切分消息,WriteEnvelopeReadEnvelope实现了长度前缀帧协议,并且在每个帧前额外附加一条固定长度的 minisign 原始签名,形成如下线格式:

[ uint16 签名算法 | uint64 密钥ID | ed25519 签名(64B) | uint32 数据长度(LE) | protobuf 数据 ] 2 字节 8 字节 64 字节 4 字节 可变

其中签名头总长为2 + 8 + 64 = 74字节,对应 implant/sliver/cryptography/minisign.go 中的RawSigSize = 2 + 8 + ed25519.SignatureSize。这种设计让每个信封的认证独立于 TLS 层——即使将来更换传输层,消息的完整性与来源可信度依然可验证。

WriteEnvelope:发送路径

func WriteEnvelope(w io.Writer, envelope *pb.Envelope) error

流程分五步:

  1. 防御性检查:信封或 writer 为 nil(含 nil 接口/切片等反射可判空的类型)时直接报错。isNilInterface()专门处理*tls.Conn这类 nil 指针被装箱进接口后w == nil判断失效的问题。
  2. 序列化proto.Marshal(envelope),失败则返回[mtls] marshal envelope包装错误。
  3. 取签名密钥:调用mtlsEnvelopeSigningKey()获取(ed25519.PrivateKey, uint64 keyID, error)。该函数用sync.Once保证进程内只派生一次。
  4. 构造并写入签名头:按小端序写入算法cryptography.EdDSA、密钥 ID、ed25519.Sign(signingKey, data)的签名原文。
  5. 写入长度与数据binary.LittleEndian.PutUint32(dataLengthBuf[:], uint32(len(data)))后依次写入 4 字节长度与消息体。所有写入均通过writeAll()处理“短写”(返回io.ErrShortWrite),确保一次调用完整落盘。

信封签名密钥的派生

mtlsEnvelopeSigningKey()的实现是整个认证体系的关键,值得单独拆解:

seed := sha256.Sum256([]byte("env-signing-v1:" + peerKeyPair.Private)) envelopeSigningPriv = ed25519.NewKeyFromSeed(seed[:]) pub := envelopeSigningPriv.Public().(ed25519.PublicKey) digest := blake2b.Sum256(pub) envelopeSigningKeyID = binary.LittleEndian.Uint64(digest[:8])
  • 密钥由 Peer(服务器侧下发)私钥字符串加上固定前缀env-signing-v1:(常量mtlsEnvelopeSigningSeedPrefix)经 SHA-256 派生为 ed25519 种子;
  • 公钥经 blake2b 摘要后取其前 8 字节作为 64 位密钥 ID;
  • 该函数对“占位符未替换”做了检测:若peerKeyPair.Private为空或仍包含模板字符串.Build.PeerPrivateKey,则返回[mtls] missing peer private key错误。

服务端在 server/c2/mtls.go 中实现了完全对称的派生逻辑deriveImplantSigningKey,并通过lookupImplantSigKey遍历数据库中的ImplantBuild记录,按密钥 ID 反查对应的公钥来验签,同时用sync.Map缓存已知密钥。

ReadEnvelope:接收路径

func ReadEnvelope(r io.Reader) (*pb.Envelope, error)

接收路径与发送路径镜像,且防御更严格:

  1. io.ReadFull依次读满 74 字节签名头与 4 字节长度字段,任何一次不足都会返回错误;
  2. 长度字段校验:dataLength <= 0[mtls] zero data lengthdataLength > maxEnvelopeLength(512MB)报errEnvelopeTooLarge——这直接防止了“声明超大长度导致内存分配爆炸”的攻击面;
  3. 读满数据体后调用cryptography.MinisignVerifyRaw(dataBuf, rawSigBuf)验签(见 implant/sliver/cryptography/minisign_raw.go),验签失败返回[mtls] invalid signature
  4. 最后proto.Unmarshal还原为*pb.Envelope

验签内部会核对算法(仅接受EdDSAHashEdDSA,后者先对消息做 blake2b-512 摘要再验)、密钥 ID 是否等于服务器 minisign 公钥的 ID,以及签名长度是否为 ed25519 标准 64 字节,全部通过后才执行ed25519.Verify

值得一提的是,源码中还埋有蜜罐机制:ReadEnvelope开头对空缓冲区或 nil reader 会panic("[[GenerateCanary]]"),该占位符同样由构建系统替换,用于部署诱饵追踪分析者。

WritePing:心跳保活

func WritePing(w io.Writer) error

发送一个固定Nonce: 31337pb.Ping消息,封装为Type: pb.MsgPing的信封后走WriteEnvelope。注释点明了设计意图:“这里不需要真正的随机 nonce,我们只需要往 socket 上写点东西”。心跳按PingInterval = 2 * time.Minute的节奏由 session.go 触发,用于在长时间无任务时维持 NAT 映射与检测对端存活。

会话复用:TLS 之上的 yamux 多路复用

“会话复用”指的是单条 mTLS TCP 连接上叠加 HashiCorp yamux 多路复用会话,将一条物理连接复用为多条逻辑流(stream),每帧信封独占一条短命流。

植入体侧在 beacon.go 与 session.go 中统一执行握手:

if _, err := conn.Write([]byte(mtls.YamuxPreface)); err != nil { ... } // 写入 "MUX/1" muxSession, err = yamux.Client(conn, cfg)

连接建立后:

  • RecvmuxSession.Accept()接受服务器发来的流,读取并返回该流上的一个信封;
  • SendmuxSession.Open()新建一条流,写入信封后关闭流;
  • Close:依次关闭 yamux 会话与底层 TLS 连接,并置空引用防止重复关闭。

服务端在 server/c2/mtls.go 中先br.Peek探测前 5 字节是否为"MUX/1"前言:匹配则丢弃前言并进入 yamux 服务端循环;不匹配则直接拒绝并记录警告日志,这是对旧版无 yamux 连接的安全收口。yamux 会话的并发上限为 128 条并发流(mtlsYamuxMaxConcurrentStreams)与 64 个并发发送(mtlsYamuxMaxConcurrentSends),通过带缓冲 channel 实现信号量限流,防止单个植入体打爆服务器资源。

服务端对称实现与 TLS 1.3 强制

为了理解客户端行为,有必要对照服务端 server/c2/mtls.go:

  • StartMutualTLSListener在指定网卡/端口启动tls.Listen,首启时若无服务端 ECC 证书会自动生成(certs.MtlsC2ServerGenerateECCCertificate);
  • getServerTLSConfig(第 429-465 行)强制MinVersion: tls.VersionTLS13ClientAuth: tls.RequireAndVerifyClientCert,并使用MtlsImplantCA作为ClientCAsRootCAs——即只有持有该 CA 签发的证书的植入体才能接入;
  • 服务端同样在每条 yamux 流上用socketWriteEnvelope/socketReadEnvelope维持与植入体一致的“签名头 + 长度前缀 + protobuf”帧格式,验签时通过lookupImplantSigKey反查植入体公钥;
  • 服务端对信封大小的限制更宽松:ServerMaxMessageSize = (2 * 1024 * 1024 * 1024) - 1(约 2GB),并在超过阈值时将信封数据落盘 spooling,避免占用过多内存。

服务端注释还解释了不做 JARM 指纹混淆的原因:mTLS 流量本身需要强安全属性,且 Go 标准 TLS 服务端指纹在互联网上非常常见,不构成明显异常。

测试用例:边界条件与安全防护验证

模块测试覆盖了帧协议最危险的几个边界:

  • mtls_test.go:
    • TestWriteEnvelope_NilEnvelope:nil 信封必须返回错误;
    • TestWriteEnvelope_NilWriter:nil writer 必须返回错误且不能 panic(测试内用recover断言);
    • TestWriteEnvelope_NilTLSConnWriter:nil*tls.Conn装箱进接口后仍应被isNilInterface识别并报错。
  • read_envelope_test.go:
    • TestReadEnvelopeRejectsOversizedFrame:构造maxEnvelopeLength + 1的长度前缀,断言返回errEnvelopeTooLarge;测试通过带 3 秒超时的 goroutine 执行,若实现出现“按声明长度无界分配”将直接 Fatal,从测试层面杜绝了内存耗尽型 DoS;
    • TestReadEnvelopeRejectsZeroLength:零长度信封必须报错。

这些用例直接对应ReadEnvelope中的长度防线,验证了该模块“先验长度、再分配内存”的安全设计。

排查与调试指南

  • 连接失败且日志出现[mtls] connect returned nil conn:说明tls.Dial成功但返回了空连接对象,优先检查目标端口可达性;
  • [mtls] missing peer private key:构建植入体时未正确注入.Build.PeerPrivateKey,多为构建配置或模板渲染问题;
  • [mtls] envelope exceeds maximum length:对端发送了超过 512MB 的信封,可能是协议不匹配或受到了畸形帧攻击;
  • [mtls] invalid signature:信封验签失败,常见于服务器密钥轮换后旧植入体仍在使用旧 Peer 私钥,或中间人篡改了流量;
  • 服务端日志Rejecting legacy mtls connection (missing yamux preface):对端不是当前版本的 Sliver 植入体,需升级版本;
  • 抓包分析:以 Debug 模式构建植入体可启用TLSKeyLogger,配合 Wireshark 的(Pre)-Master-Secret日志即可解密 mTLS 流量观察信封帧结构。

小结

implant/sliver/transports/mtls 模块以约 300 行代码完成了植入体侧 mTLS 传输的全部关键职责:构建期证书注入与选择、基于RootOnlyVerifyCertificate的安全拨号、带 minisign 原始签名的长度前缀信封帧协议、派生自 Peer 私钥的 ed25519 信封签名、2 分钟间隔的心跳保活,以及基于 yamux 的单连接多流会话复用。它与 server/c2/mtls.go、client/transport/mtls.go 共同构成 Sliver 中贯穿“植入体 — 服务器 — 操作员控制端”的三级 mTLS 信任链,其“双因子认证(证书 + 信封签名)”“先验长后分配”“仅根证书校验”等设计对自研 C2 或加密隧道组件的开发者具有直接借鉴价值。

  • 网络安全

【免费下载链接】sliver

Adversary Emulation Framework

项目地址:https://gitcode.com/gh_mirrors/sl/sliver
点击查看免费下载
上一篇:【亲测免费】 Codecrumbs:代码探索的革命性工具
下一篇:5个关键优势:为什么foobox-cn重新定义了音乐播放器的美学与功能

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询