What is mTLS
?
mTLS
stands 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 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.
- Implant code related to transport mechanisms, including mTLS is located in
./sliver/implant/sliver/transports
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, thelog
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}}
- 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}}`
)
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
}
-
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 everyPingInterval
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)
}
-
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
-
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.
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
}
- 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 apb.Envelope
structure which Go can understand.
- the
- 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.
Top comments (0)