DEV Community

Cover image for Exploring Sliver C2 - Part 1 : C2 over mTLS

Posted on • Updated on

Exploring Sliver C2 - Part 1 : C2 over mTLS

What is mTLS ?

mTLSstands for mutual TLS.

In normal TLS, a target server/website is required to provide to visitors browsers its certificate. This certificate is generally issued by a trusted third-party (a Certificate Authority).

Client browsers are shipped by default with a list of public keys from known trusted Certificate Authorities. The client browser can use them to verify if a certificate presented to it by a server/website was issued/signed by one of the Certificate Authorities it knows (trusts).

mTLS enforces that the client too presents a similar certificate to complete a hand-shake, there-by enabling the server to be sure that the client is also whom it pretends to be.

mTLS flow cloudflare1

mTLS Implant side

  • The Implant is the component which is deployed on target/victim endpoint for control.
  • Implant specific code within sliver is located in ./sliver/implant directory.

location of implant code for sliver

  • Implant code related to transport mechanisms, including mTLS is located in ./sliver/implant/sliver/transports

location of transport mechanism code for implant

Poking around mtls.go

  • The file has a quiet curious template-like feature which allows for conditionally importing the log package.
  • Based on wether we are or not in Debug mode as expressed by some magic .Config variable, the log package gets imported.
  • I guess this could be for Opsec reasons, as no one would like production implants to have debug messages enabled on blue team monitored endpoints.
    // {{if .Config.Debug}}
    // {{end}}
Enter fullscreen mode Exit fullscreen mode
  • Other interesting observations where the fact that some variables within the scope of this code get provisioned by this magically injected {{.Config}} object.
  • These variables are apparently:
  • the Certificate Authority's public key (expected to be the key of the trusted CA between the implant and server)
  • the private key of the implant itself (keyPEM)
  • the certificate of the implant certPEM (Implant's public key signed by the CA, with some metadata)
var (
    // PingInterval - Amount of time between in-band "pings"
    PingInterval = 2 * time.Minute

    // caCertPEM - PEM encoded CA certificate
    caCertPEM = `{{.Config.MtlsCACert}}`

    keyPEM  = `{{.Config.MtlsKey}}`
    certPEM = `{{.Config.MtlsCert}}`
Enter fullscreen mode Exit fullscreen mode

There are a few functions too, whose intent i will try to derive based on the comments and code logic.

  • WriteEnvelope:
    • This appears to be some kind of write primitive the implant can use to pass down messages of its protocol through an mTLS socket.
    • It depends on a protobuf defined structure call pb.Envelope
    • Interestingly enough, it sends bytes over the socket in LittleEndian (host order).
    • LittleEndian is generally the byte order used by CPUs to read process memory and instructions.
    • In the network, Big Endian (also called network order) is often more prevalent for byte transmission. 2
// WriteEnvelope - Writes a message to the TLS socket using length prefix framing
// which is a fancy way of saying we write the length of the message then the message
// e.g. [uint32 length|message] so the receiver can delimit messages properly
func WriteEnvelope(connection *tls.Conn, envelope *pb.Envelope) error {
    data, err := proto.Marshal(envelope)
    if err != nil {
        // {{if .Config.Debug}}
        log.Print("Envelope marshaling error: ", err)
        // {{end}}
        return err
    dataLengthBuf := new(bytes.Buffer)
    binary.Write(dataLengthBuf, binary.LittleEndian, uint32(len(data)))
    return nil
Enter fullscreen mode Exit fullscreen mode
  • WritePing:
    • kind of keep alive method to constantly feed something over the socket.
    • just to notify the server that the implant is still there.
    • it uses the WriteEnvelope primitive to pass down these pings over the socket.
    • The ping consists of sending the message 31337 (elite in leet speak), i guess every PingInterval units of time.
// WritePing - Send a "ping" message to the server
func WritePing(connection *tls.Conn) error {
    // {{if .Config.Debug}}
    log.Print("Socket ping")
    // {{end}}

    // We don't need a real nonce here, we just need to write to the socket
    pingBuf, _ := proto.Marshal(&pb.Ping{Nonce: 31337})
    envelope := pb.Envelope{
        Type: pb.MsgPing,
        Data: pingBuf,
    return WriteEnvelope(connection, &envelope)
Enter fullscreen mode Exit fullscreen mode
  • ReadEnvelope:
    • The function name and comments are enough to convince me not to read the whole of it, and infer a little its goal :D.
    • Reads data from the TLS connection.
    // Unmarshal the protobuf envelope
    envelope := &pb.Envelope{}
    err = proto.Unmarshal(dataBuf, envelope)
    if err != nil {
        // {{if .Config.Debug}}
        log.Printf("Unmarshal envelope error: %v", err)
        // {{end}}
        return nil, err

    return envelope, nil
Enter fullscreen mode Exit fullscreen mode
  • MtlsConnect:
    • establish a TLS connection with sliver server given the server ip and port.
    • depends on getTLSConfig to load CA certificate.

At this stage the dependency graph i made for myself is the following.

mind map of function dependencies

It becomes quiet evident that the big players here are the :

  • Envelope structure
  • The magical Config variable.

The Envelope proto definition

// Envelope - Used to encode implant<->server messages since we 
//            cannot use gRPC due to the various transports used.
message Envelope {
  int64 ID = 1;   // Envelope ID used to track request/response
  uint32 Type = 2; // Message type
  bytes Data = 3;  // Actual message data

  bool UnknownMessageType = 4; // Set if the implant did not understand the message
Enter fullscreen mode Exit fullscreen mode
  • from the description of this structure, it is used to encode messages between implant and server
  • Evidence of this could be traced back to:
    • the WriteEnvelope function; pb.Envelope is marshaled into raw bytes before transmission the TLS connection.
    • the ReadEnvelope function; raw bytes are read from a TLS connection, and unmarshaled into a pb.Envelope structure which Go can understand.
  • Moving protocol specific types/structures into proto files gives in my opinion some flexibility in modifying the protocol, because the proto files will act as the single source of truth for the protocol's definition.


  • I'm tempted at this stage to think that a solid detection technique for sliver could rely on identifying these message structures in-memory.
  • We could hypothetically imagine some sort of memory scanner, which will scan process memory space on the monitored system, and attempt to recognise sections of memory whose layout/structure matches the alignment of the protobuf messages defined in sliver's implant protocol.
  • Doubts will get settled anyway by better understanding the protocol, and also how Go structures look like in memory.




Top comments (0)