- 网络安全
【免费下载链接】sliver
Adversary Emulation Framework
导读
本文以 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 顶部定义了几个决定传输行为的全局变量,理解它们是阅读后续代码的前提:
| 变量 | 默认值 | 作用 |
|---|---|---|
PingInterval | 2 * 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(),它完成了三件关键工作:
- 加载本端证书:
tls.X509KeyPair([]byte(certPEM), []byte(keyPEM))将构建期注入的 PEM 解析为tls.Certificate。若失败会记录日志并直接os.Exit(5)——这是致命错误,因为没有身份就无法完成双向认证。 - 加载 CA 池:
x509.NewCertPool()+AppendCertsFromPEM([]byte(caCertPEM))将内嵌的 mTLS CA 加入信任根。 - 自定义证书校验:设置
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)。为了在字节流上可靠地切分消息,WriteEnvelope与ReadEnvelope实现了长度前缀帧协议,并且在每个帧前额外附加一条固定长度的 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流程分五步:
- 防御性检查:信封或 writer 为 nil(含 nil 接口/切片等反射可判空的类型)时直接报错。
isNilInterface()专门处理*tls.Conn这类 nil 指针被装箱进接口后w == nil判断失效的问题。 - 序列化:
proto.Marshal(envelope),失败则返回[mtls] marshal envelope包装错误。 - 取签名密钥:调用
mtlsEnvelopeSigningKey()获取(ed25519.PrivateKey, uint64 keyID, error)。该函数用sync.Once保证进程内只派生一次。 - 构造并写入签名头:按小端序写入算法
cryptography.EdDSA、密钥 ID、ed25519.Sign(signingKey, data)的签名原文。 - 写入长度与数据:
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)接收路径与发送路径镜像,且防御更严格:
io.ReadFull依次读满 74 字节签名头与 4 字节长度字段,任何一次不足都会返回错误;- 长度字段校验:
dataLength <= 0报[mtls] zero data length;dataLength > maxEnvelopeLength(512MB)报errEnvelopeTooLarge——这直接防止了“声明超大长度导致内存分配爆炸”的攻击面; - 读满数据体后调用
cryptography.MinisignVerifyRaw(dataBuf, rawSigBuf)验签(见 implant/sliver/cryptography/minisign_raw.go),验签失败返回[mtls] invalid signature; - 最后
proto.Unmarshal还原为*pb.Envelope。
验签内部会核对算法(仅接受EdDSA与HashEdDSA,后者先对消息做 blake2b-512 摘要再验)、密钥 ID 是否等于服务器 minisign 公钥的 ID,以及签名长度是否为 ed25519 标准 64 字节,全部通过后才执行ed25519.Verify。
值得一提的是,源码中还埋有蜜罐机制:ReadEnvelope开头对空缓冲区或 nil reader 会panic("[[GenerateCanary]]"),该占位符同样由构建系统替换,用于部署诱饵追踪分析者。
WritePing:心跳保活
func WritePing(w io.Writer) error发送一个固定Nonce: 31337的pb.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)连接建立后:
- Recv:
muxSession.Accept()接受服务器发来的流,读取并返回该流上的一个信封; - Send:
muxSession.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.VersionTLS13、ClientAuth: tls.RequireAndVerifyClientCert,并使用MtlsImplantCA作为ClientCAs与RootCAs——即只有持有该 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
相关推荐
NVIDIA Profile Inspector终极指南:解锁显卡200+隐藏设置的免费神器
NVIDIA Profile Inspector终极指南:解锁显卡200+隐藏设置的免费神器 你是否曾经对NVIDIA控制面板的功能感到失望?想要更精细地控制显
网络安全Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践
Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践 本文以 Traefik 仓库集成测试夹具 integration/fixtures/t
后端API网关负载均衡微服务网络云原生React Native实战进阶:从通讯录到LBS应用的完整跨平台开发指南
React Native实战进阶:从通讯录到LBS应用的完整跨平台开发指南 在移动应用开发领域,React Native已成为连接iOS与Android生态的技
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考