feat(tls): add support for RDP negotiation before TLS handshake
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/apernet/OpenGFW/analyzer"
|
||||
"github.com/apernet/OpenGFW/analyzer/internal"
|
||||
"github.com/apernet/OpenGFW/analyzer/utils"
|
||||
)
|
||||
|
||||
// RDP does not start TLS right away: the client first sends an X.224
|
||||
// Connection Request and the server answers with a Connection Confirm, each in
|
||||
// a TPKT packet, to agree on a security protocol ([MS-RDPBCGR] 1.3.1.1). Only
|
||||
// then does the TLS handshake begin, on the same connection.
|
||||
//
|
||||
// The tls analyzer used to give up on the first byte of such a stream, which
|
||||
// left RDP invisible to TLS rules, and with it anything that puts a fake RDP
|
||||
// negotiation in front of its TLS. So the negotiation is parsed as an optional
|
||||
// prelude of the hello, and published as rdp_req / rdp_resp.
|
||||
|
||||
const (
|
||||
tpktVersion = 0x03
|
||||
tpktHeaderSize = 4 // version (1 byte) + reserved (1 byte) + length (2 bytes)
|
||||
// x224FixedSize is length indicator (1 byte) + code (1 byte) +
|
||||
// DST-REF (2 bytes) + SRC-REF (2 bytes) + class option (1 byte).
|
||||
x224FixedSize = 7
|
||||
|
||||
x224CodeConnectionRequest = 0xe0
|
||||
x224CodeConnectionConfirm = 0xd0
|
||||
|
||||
rdpNegReq = 0x01
|
||||
rdpNegRsp = 0x02
|
||||
rdpNegFailure = 0x03
|
||||
rdpNegSize = 8
|
||||
)
|
||||
|
||||
var rdpCookiePrefix = []byte("Cookie: ")
|
||||
|
||||
// readPrelude consumes the X.224 connection PDU that precedes the hello on an
|
||||
// RDP connection, if there is one.
|
||||
func (h *tlsHello) readPrelude() utils.LSMAction {
|
||||
b, ok := h.rec.GetByte(false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
if b == internal.RecordTypeHandshake {
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
if b != tpktVersion {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
header, ok := h.rec.Get(tpktHeaderSize, false)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
n := int(binary.BigEndian.Uint16(header[2:4]))
|
||||
if header[1] != 0 || n < tpktHeaderSize+x224FixedSize {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
pdu, ok := h.rec.Get(n, true)
|
||||
if !ok {
|
||||
return utils.LSMActionPause
|
||||
}
|
||||
m := parseX224Connection(pdu[tpktHeaderSize:], h.x224Code)
|
||||
if m == nil {
|
||||
return utils.LSMActionCancel
|
||||
}
|
||||
h.rdp = m
|
||||
h.rdpUpdated = true
|
||||
return utils.LSMActionNext
|
||||
}
|
||||
|
||||
// parseX224Connection parses an X.224 Connection Request or Confirm TPDU
|
||||
// carrying the RDP negotiation, or returns nil if tpdu is not one.
|
||||
func parseX224Connection(tpdu []byte, code byte) analyzer.PropMap {
|
||||
// The length indicator does not count itself. The low nibble of the code
|
||||
// is the credit, and the high nibble of the class option the class, which
|
||||
// is always 0 for RDP.
|
||||
if int(tpdu[0]) != len(tpdu)-1 || tpdu[1]&0xf0 != code || tpdu[6]&0xf0 != 0 {
|
||||
return nil
|
||||
}
|
||||
rest := tpdu[x224FixedSize:]
|
||||
m := analyzer.PropMap{}
|
||||
if code == x224CodeConnectionRequest {
|
||||
// Both the cookie ("Cookie: mstshash=<user>") and the load balancer
|
||||
// routing token ("Cookie: msts=<token>") are terminated by CRLF.
|
||||
if bytes.HasPrefix(rest, rdpCookiePrefix) {
|
||||
i := bytes.Index(rest, []byte("\r\n"))
|
||||
if i < 0 {
|
||||
return nil
|
||||
}
|
||||
m["cookie"] = string(rest[len(rdpCookiePrefix):i])
|
||||
rest = rest[i+2:]
|
||||
}
|
||||
// A client that sends no RDP_NEG_REQ can only do standard RDP
|
||||
// security (PROTOCOL_RDP).
|
||||
m["protocols"] = 0
|
||||
} else {
|
||||
m["protocol"] = 0
|
||||
}
|
||||
if len(rest) == 0 {
|
||||
return m
|
||||
}
|
||||
// Anything after the negotiation structure, such as the correlation info
|
||||
// a client may append, is ignored.
|
||||
if len(rest) < rdpNegSize || binary.LittleEndian.Uint16(rest[2:4]) != rdpNegSize {
|
||||
return nil
|
||||
}
|
||||
v := int(binary.LittleEndian.Uint32(rest[4:8]))
|
||||
switch {
|
||||
case code == x224CodeConnectionRequest && rest[0] == rdpNegReq:
|
||||
m["protocols"] = v
|
||||
case code == x224CodeConnectionConfirm && rest[0] == rdpNegRsp:
|
||||
m["protocol"] = v
|
||||
case code == x224CodeConnectionConfirm && rest[0] == rdpNegFailure:
|
||||
delete(m, "protocol")
|
||||
m["failure"] = v
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user