feat(tls): add support for RDP negotiation before TLS handshake

This commit is contained in:
2026-09-30 16:54:53 +08:00
parent ae7135ab64
commit c879e10ac1
5 changed files with 377 additions and 16 deletions
+29 -16
View File
@@ -51,9 +51,10 @@ type tlsHello struct {
msg utils.ByteBuffer // handshake layer bytes collected so far
lsm *utils.LinearStateMachine
msgType byte
minLen int // smallest valid message body
parse func(*utils.ByteBuffer) analyzer.PropMap // parses the message body
msgType byte
x224Code byte // X.224 connection PDU that may precede the hello on RDP
minLen int // smallest valid message body
parse func(*utils.ByteBuffer) analyzer.PropMap // parses the message body
recLeft int // body bytes of the current record not consumed yet
msgLen int // message body length, -1 until the handshake header is read
@@ -63,6 +64,9 @@ type tlsHello struct {
m analyzer.PropMap
updated bool
done bool
rdp analyzer.PropMap // RDP negotiation, nil if there was none
rdpUpdated bool
}
func newTLSStream(logger analyzer.Logger) *tlsStream {
@@ -70,18 +74,18 @@ func newTLSStream(logger analyzer.Logger) *tlsStream {
// minimal ClientHello body: protocol version (2 bytes) + random (32 bytes) +
// + session ID (1 byte) + cipher suites (4 bytes) +
// + compression methods (2 bytes) + no extensions
s.req.init(internal.TypeClientHello, 41, internal.ParseTLSClientHelloMsgData)
s.req.init(internal.TypeClientHello, x224CodeConnectionRequest, 41, internal.ParseTLSClientHelloMsgData)
// minimal ServerHello body: server version (2 bytes) + random (32 bytes) +
// + session ID (>=1 byte) + cipher suite (2 bytes) +
// + compression method (1 byte) + no extensions
s.resp.init(internal.TypeServerHello, 38, internal.ParseTLSServerHelloMsgData)
s.resp.init(internal.TypeServerHello, x224CodeConnectionConfirm, 38, internal.ParseTLSServerHelloMsgData)
return s
}
func (h *tlsHello) init(msgType byte, minLen int, parse func(*utils.ByteBuffer) analyzer.PropMap) {
h.msgType, h.minLen, h.parse = msgType, minLen, parse
func (h *tlsHello) init(msgType, x224Code byte, minLen int, parse func(*utils.ByteBuffer) analyzer.PropMap) {
h.msgType, h.x224Code, h.minLen, h.parse = msgType, x224Code, minLen, parse
h.msgLen = -1
h.lsm = utils.NewLinearStateMachine(h.readMessage, h.parseMessage)
h.lsm = utils.NewLinearStateMachine(h.readPrelude, h.readMessage, h.parseMessage)
}
func (s *tlsStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
@@ -114,17 +118,26 @@ func (s *tlsStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyz
// update returns the properties to publish for this direction, or nil if
// there is nothing to publish. The parsed message goes under key, the record
// count under key + "_records". The record count is kept out of the message
// map so that a non-nil message map still means a fully parsed hello.
// map so that a non-nil message map still means a fully parsed hello. An RDP
// negotiation goes under "rdp_" + key as soon as it is parsed, whatever
// publish says.
func (h *tlsHello) update(key string, publish bool) analyzer.PropMap {
if !publish || h.records == 0 {
m := analyzer.PropMap{}
if h.rdpUpdated {
m["rdp_"+key] = h.rdp
h.rdpUpdated = false
}
if publish && h.records > 0 {
m[key+"_records"] = h.records
if h.updated {
m[key] = h.m
h.updated = false
}
h.reported = h.records
}
if len(m) == 0 {
return nil
}
m := analyzer.PropMap{key + "_records": h.records}
if h.updated {
m[key] = h.m
h.updated = false
}
h.reported = h.records
return m
}