%include ../asn1/asn1.pac


# The protocol specifies some 32-bit variable-length data fields for the
# key and cert material. This value caps the size of these to help prevent
# excessive buffering and integer overflows in BinPAC. This is 256 KB.
let MAX_DATA_LENGTH: uint32 = 262144;

type TPKT(is_orig: bool) = record {
	version:  uint8;
	reserved: uint8;
	tpkt_len: uint16;

# These data structures are merged together into TPKT
# because there are packets that report incorrect
# lengths in the tpkt length field.  No clue why.

	cotp:     COTP(this);
} &byteorder=bigendian &length=tpkt_len;

type COTP(tpkt: TPKT) = record {
	cotp_len:  uint8;
	pdu:       uint8;
	switch:    case pdu of {
		0xd0    -> connect_confirm: Connect_Confirm(this);
		0xe0    -> client_request:  Connect_Request(this);
		0xf0    -> data:            DT_Data;

		# In case we don't support the PDU we just
		# consume the rest of it and throw it away.
		default -> not_done:  bytestring &restofdata &transient;
	};
} &byteorder=littleendian;

type DT_Data = record {
	tpdu_number:              uint8;
	# multiple octet variant of the ASN.1 type field, should handle this better.
	application_defined_type: uint8;
	application_type:         uint8;

	data: case application_type of {
		0x65    -> client: Client_Header; # 0x65 is a client
		0x66    -> server: Server_Header; # 0x66 is a server
		default -> none:   empty;
	};
} &byteorder=littleendian;

######################################################################
# Data Blocks
######################################################################

type Data_Header = record {
	type:   uint16;
	length: uint16;
} &byteorder=littleendian;

type Data_Block = record {
	header: Data_Header;
	block: case header.type of {
		0xc001  -> client_core:       Client_Core_Data;
		0xc002  -> client_security:   Client_Security_Data;
		0xc003  -> client_network:    Client_Network_Data;
		0xc004  -> client_cluster:    Client_Cluster_Data;
		#0xc005  -> client_monitor:    Client_Monitor_Data;
		#0xc006  -> client_msgchannel: Client_MsgChannel_Data;
		#0xc008  -> client_monitor_ex: Client_MonitorExtended_Data;
		#0xc00A  -> client_multitrans: Client_MultiTransport_Data;

		0x0c01  -> server_core:       Server_Core_Data(header);
		0x0c02  -> server_security:   Server_Security_Data;
		0x0c03  -> server_network:    Server_Network_Data;
		#0x0c04  -> server_msgchannel: Server_MsgChannel_Data;
		#0x0c08  -> server_multitrans: Server_MultiTransport_Data;

		default -> unhandled:  bytestring &restofdata &transient;
	} &length=header.length-4;
} &byteorder=littleendian;

######################################################################
# Client X.224
######################################################################

type RDP_Connect_Request_cookie = record {
	cookie_value:          RE/[^\x0d]*/;
	cookie_terminator:     RE/\x0d\x0a/;
} &byteorder=littleendian;

type Connect_Request(cotp: COTP) = record {
	destination_reference: uint16;
	source_reference:      uint16;
	flow_control:          uint8;
	cookie_mstshash:       RE/(Cookie: mstshash\=)?/;
	# Cookie is optional as per
	# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/18a27ef9-6f9a-4501-b000-94b1fe3c2c10
	switch1: case (sizeof(cookie_mstshash) > 0 ) of {
		0       -> none1:          empty;
		default -> cookie:         RDP_Connect_Request_cookie;
	};
	# Terrifying little case statement to figure out if there
	# is any data left in the COTP structure.
	switch2:   case (offsetof(switch2) + 2 - cotp.cotp_len - 1) of {
		0       -> none2:          empty;
		default -> rdp_neg_req:    RDP_Negotiation_Request;
	};
} &byteorder=littleendian;

type RDP_Negotiation_Request = record {
	type:                uint8;
	flags:               uint8;
	length:              uint16 &enforce(length == 8); # must be set to 8
	requested_protocols: uint32;
} &let {
	PROTOCOL_RDP:       bool = requested_protocols & 0x00;
	PROTOCOL_SSL:       bool = requested_protocols & 0x01;
	PROTOCOL_HYBRID:    bool = requested_protocols & 0x02;
	PROTOCOL_HYBRID_EX: bool = requested_protocols & 0x08;
} &byteorder=littleendian;

######################################################################
# Server X.224
######################################################################

type Connect_Confirm(cotp: COTP) = record {
	destination_reference: uint16;
	source_reference:      uint16;
	flags:                 uint8;
	# Terrifying little case statement to figure out if there
	# is any data left in the COTP structure.
	switch1: case (offsetof(switch1) + 2 - cotp.cotp_len - 1) of {
		0       -> none1:    empty;
		default -> response: Connect_Confirm_Record;
	};
};

type Connect_Confirm_Record = record {
	response_type:         uint8;
	switch1: case response_type of {
		0x02 -> neg_resp: RDP_Negotiation_Response;
		0x03 -> neg_fail: RDP_Negotiation_Failure;
	};
};

type RDP_Negotiation_Response = record {
	flags:               uint8;
	length:              uint16 &enforce(length==8); # must be set to 8
	selected_protocol:   uint32;
} &let {
	# Seems to be SSL encrypted (maybe CredSSP also?)
	# after this message if the selected_protocol is > 0.
	enc_ssl: bool = $context.connection.go_encrypted(selected_protocol) &if(selected_protocol > 0);
} &byteorder=littleendian;

type RDP_Negotiation_Failure = record {
	flags: uint8;
	length: uint16;
	failure_code: uint32;
} &byteorder=littleendian;

######################################################################
# Client MCS
######################################################################

type Client_Header = record {
	type_length:               ASN1Integer;
	calling_domain_selector:   ASN1OctetString;
	called_domain_selector:    ASN1OctetString;
	upward_flag:               ASN1Boolean;
	target_parameters:         ASN1SequenceMeta;
	targ_parameters_pad:       bytestring &length=target_parameters.encoding.length &transient;
	minimum_parameters:        ASN1SequenceMeta;
	min_parameters_pad:        bytestring &length=minimum_parameters.encoding.length &transient;
	maximum_parameters:        ASN1SequenceMeta;
	max_parameters_pad:        bytestring &length=maximum_parameters.encoding.length &transient;
	# BER encoded OctetString and long variant, can be safely skipped for now
	user_data_length:          uint32;
	gcc_connection_data:       GCC_Client_Connection_Data;
	gcc_client_create_request: GCC_Client_Create_Request;
	data_blocks:               Data_Block[] &until($input.length() == 0);
};

type GCC_Client_Connection_Data = record {
	key_object_length:        uint16;
	key_object:               uint8[key_object_length];
	connect_data_connect_pdu: uint16;
} &byteorder=bigendian;

type GCC_Client_Create_Request = record {
	extension_bit:           uint8;
	privileges:              uint8;
	numeric_length:          uint8;
	numeric:                 uint8;
	termination_method:      uint8;
	number_user_data_sets:   uint8;
	user_data_value_present: uint8;
	h221_nonstandard_length: uint8;
	h221_nonstandard_key:    RE/Duca/;
	user_data_value_length:  uint16;
} &byteorder=bigendian;

type Client_Core_Data = record {
	version_major:            uint16;
	version_minor:            uint16;
	desktop_width:            uint16;
	desktop_height:           uint16;
	color_depth:              uint16;
	sas_sequence:             uint16;
	keyboard_layout:          uint32;
	client_build:             uint32;
	client_name:              bytestring &length=32;
	keyboard_type:            uint32;
	keyboard_sub:             uint32;
	keyboard_function_key:    uint32;
	ime_file_name:            bytestring &length=64;
	# Everything below here is optional and should be handled better.
	# If some of these fields aren't included it could lead to parse failure.
	post_beta2_color_depth:   uint16;
	client_product_id:        uint16;
	serial_number:            uint32;
	high_color_depth:         uint16;
	supported_color_depths:   uint16;
	early_capability_flags:   uint16;
	dig_product_id:           bytestring &length=64;
	# There are more optional fields here but they are
	# annoying to optionally parse in binpac.
	# Documented here: https://msdn.microsoft.com/en-us/library/cc240510.aspx
} &let {
	SUPPORT_ERRINFO_PDU:        bool = early_capability_flags & 0x01;
	WANT_32BPP_SESSION:         bool = early_capability_flags & 0x02;
	SUPPORT_STATUSINFO_PDU:     bool = early_capability_flags & 0x04;
	STRONG_ASYMMETRIC_KEYS:     bool = early_capability_flags & 0x08;
	SUPPORT_MONITOR_LAYOUT_PDU: bool = early_capability_flags & 0x40;
	SUPPORT_NETCHAR_AUTODETECT: bool = early_capability_flags & 0x80;
	SUPPORT_DYNVC_GFX_PROTOCOL: bool = early_capability_flags & 0x0100;
	SUPPORT_DYNAMIC_TIME_ZONE:  bool = early_capability_flags & 0x0200;
	SUPPORT_HEARTBEAT_PDU:      bool = early_capability_flags & 0x0400;
} &byteorder=littleendian;

type Client_Security_Data = record {
	encryption_methods:	uint32;
	ext_encryption_methods:	uint32;
} &byteorder=littleendian;

type Client_Network_Data = record {
	channel_count: uint32 &enforce(channel_count <= 1000);
	channel_def_array: Client_Channel_Def[channel_count];
} &byteorder=littleendian;

type Client_Cluster_Data = record {
	flags: uint32;
	redir_session_id: uint32;
} &let {
	REDIRECTION_SUPPORTED:                        bool = redir_session_id & 0x00000001;
	SERVER_SESSION_REDIRECTION_VERSION_MASK:      uint8 = (redir_session_id & 0x0000003C);
	REDIRECTED_SESSIONID_FIELD_VALID:             bool = (redir_session_id & 0x00000002);
	REDIRECTED_SMARTCARD:                         bool = redir_session_id & 0x00000040;
} &byteorder=littleendian;

type Client_Channel_Def = record {
	name:     bytestring &length=8;
	options:  uint32;
} &let {
	REMOTE_CONTROL_PERSISTENT:    bool = options & 0x00100000;
	CHANNEL_OPTION_SHOW_PROTOCOL: bool = options & 0x00200000;
	CHANNEL_OPTION_COMPRESS:      bool = options & 0x00400000;
	CHANNEL_OPTION_COMPRESS_RDP:  bool = options & 0x00800000;
	CHANNEL_OPTION_PRI_LOW:       bool = options & 0x02000000;
	CHANNEL_OPTION_PRI_MED:       bool = options & 0x04000000;
	CHANNEL_OPTION_PRI_HIGH:      bool = options & 0x08000000;
	CHANNEL_OPTION_ENCRYPT_CS:    bool = options & 0x10000000;
	CHANNEL_OPTION_ENCRYPT_SC:    bool = options & 0x20000000;
	CHANNEL_OPTION_ENCRYPT_RDP:   bool = options & 0x40000000;
	CHANNEL_OPTION_INITIALIZED:   bool = options & 0x80000000;
} &byteorder=littleendian;

######################################################################
# Server MCS
######################################################################

type Server_Header = record {
	# We don't need this value, but it's ASN.1 integer in definite length
	# so I think we can skip over it.
	type_length:                        uint8[3];
	connect_response_result:            ASN1Enumerated;
	connect_response_called_id:         ASN1Integer;
	connect_response_domain_parameters: ASN1SequenceMeta;
	# Skipping over domain parameters for now.
	domain_parameters:                  bytestring &length=connect_response_domain_parameters.encoding.length &transient;
	# I think this is another definite length encoded value.
	user_data_length:                   uint32;
	gcc_connection_data:                GCC_Server_Connection_Data;
	gcc_create_response:                GCC_Server_Create_Response;
	data_blocks:                        Data_Block[] &until($input.length() == 0);
} &byteorder=littleendian;

type GCC_Server_Connection_Data = record {
	key_object_length:        uint16;
	key_object:               uint8[key_object_length];
	connect_data_connect_pdu: uint8;
} &byteorder=bigendian;

type GCC_Server_Create_Response = record {
	extension_bit:           uint8;
	node_id:                 uint16;
	tag_length:              uint8;
	tag:                     uint8;
	result:                  uint8;
	number_user_data_sets:   uint8;
	user_data_value_present: uint8;
	h221_nonstandard_length: uint8;
	h221_nonstandard_key:    RE/McDn/;
	user_data_value_length:  uint16;
} &byteorder=bigendian;

type Server_Core_Data(h: Data_Header) = record {
	version_major: uint16;
	version_minor: uint16;
	switch1:       case h.length of {
		8       -> none:                       empty;
		default -> client_requested_protocols: uint32;
	};
} &byteorder=littleendian;

type Server_Network_Data = record {
	mcs_channel_id: uint16;
	channel_count:  uint16;
} &byteorder=littleendian;

# See MS-RDPBCGR Section 2.2.1.4.3 for reasoning for the &enforce value on
# the server_random_length field.
type Server_Security_Data = record {
	encryption_method:      uint32;
	encryption_level:       uint32;
	server_random_length:   uint32 &enforce((encryption_level == 0 && encryption_method == 0 && server_random_length == 0) || (server_random_length == 0x20));
	server_cert_length:     uint32 &enforce(server_random_length <= MAX_DATA_LENGTH);
	server_random:          bytestring &length=server_random_length;
	server_certificate:     Server_Certificate &length=server_cert_length;
} &let {
	# Seems to be encrypted after this message if
	# encryption level is >0
	# 0 means RDP encryption.
	enc: bool = $context.connection.go_encrypted(0) &if(encryption_method > 0 && encryption_level > 0);
} &byteorder=littleendian;

type Server_Certificate = record {
	version: uint32;
	switch:  case cert_type of {
		0x01 -> proprietary: Server_Proprietary_Cert(this);
		0x02 -> x509:        X509;
	};
} &let {
	cert_type:          uint32 = version & 0x7FFFFFFF;
	permanently_issued: bool   = (version & 0x80000000) == 0;
} &byteorder=littleendian;

type Server_Proprietary_Cert(cert: Server_Certificate) = record {
	signature_algorithm:    uint32;
	key_algorithm:          uint32;
	public_key_blob_type:   uint16;
	public_key_blob_length: uint16;
	public_key_blob:        Public_Key_Blob &length=public_key_blob_length;
	signature_blob_type:    uint16;
	signature_blob_length:  uint16;
	signature_blob:         bytestring &length=signature_blob_length;
} &byteorder=littleendian;

type Public_Key_Blob = record {
	magic:           bytestring &length=4;
	key_length:      uint32 &enforce(key_length <= MAX_DATA_LENGTH);
	bit_length:      uint32;
	public_exponent: uint32;
	modulus:         bytestring &length=key_length;
} &byteorder=littleendian;

type X509 = record {
	num_of_certs: uint32 &enforce(num_of_certs <= 1000);
	certs: X509_Cert_Data[num_of_certs];
} &byteorder=littleendian;

type X509_Cert_Data = record {
	cert_len: uint32 &enforce(cert_len <= MAX_DATA_LENGTH);
	cert: bytestring &length=cert_len;
} &byteorder=littleendian;

refine connection RDP_Conn += {

	%member{
		bool is_encrypted_;
		uint32 encryption_method_;
	%}

	%init{
		is_encrypted_ = false;
		encryption_method_ = 0;
	%}

	function go_encrypted(method: uint32): bool
		%{
		is_encrypted_ = true;
		encryption_method_ = method;

		if ( rdp_begin_encryption )
			{
			zeek::BifEvent::enqueue_rdp_begin_encryption(zeek_analyzer(),
			                                       zeek_analyzer()->Conn(),
			                                       ${method});
			}

		return is_encrypted_;
		%}

	function is_encrypted(): bool
		%{
		return is_encrypted_;
		%}

	function encryption_method(): uint32
		%{
		return encryption_method_;
		%}
};
