DEV Community

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

Posted on • Edited 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}}
    "log"
    // {{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)))
    connection.Write(dataLengthBuf.Bytes())
    connection.Write(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.

Conclusion

  • 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.

Reference


  1. https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/ 

  2. https://beej.us/guide/bgnet/html/split/ip-addresses-structs-and-data-munging.html#byte-order 

Top comments (0)