Another big RDP update.

- New fields for certificate type, number of certificates,
   if certificates are permanent on the server, and the selected
   security protocol.
 - Fixed some issues with X.509 certificate handling over RDP
   (the event handler wasn't sufficiently constrained).
 - Better detection of and transition into encrypted mode.  No more
   binpac parse failures from the test traces anymore!
 - Some event name clean up and new events.
 - X.509 Certificate chains are now handled correctly (was only grabbing
   a single certificate).
This commit is contained in:
Seth Hall 2015-03-05 01:15:12 -05:00
parent 0d04557ac4
commit f45e057779
10 changed files with 364 additions and 135 deletions

View file

@ -51,34 +51,26 @@ void RDP_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
// we'll just move this over to the PIA analyzer.
// Like the comment below says, this is probably the wrong
// way to handle this.
if ( len > 0 && data[0] >= 0x14 && data[0] <= 0x17 )
if ( interp->is_encrypted() )
{
if ( ! pia )
if ( len > 0 && data[0] >= 0x14 && data[0] <= 0x17 )
{
pia = new pia::PIA_TCP(Conn());
if ( AddChildAnalyzer(pia) )
if ( ! pia )
{
pia->FirstPacket(true, 0);
pia->FirstPacket(false, 0);
}
}
pia = new pia::PIA_TCP(Conn());
if ( pia )
{
ForwardStream(len, data, orig);
if ( AddChildAnalyzer(pia) )
{
pia->FirstPacket(true, 0);
pia->FirstPacket(false, 0);
}
}
if ( pia )
ForwardStream(len, data, orig);
}
}
else if ( pia )
{
// This is data that doesn't seem to match
// an SSL record, but we've moved into SSL mode.
// This is probably the wrong way to handle this
// situation but I don't know what these records
// are that don't appear to be SSL/TLS.
return;
}
else
else // if not encrypted
{
try
{

View file

@ -1,25 +1,39 @@
## Generated for X.224 client requests when native RDP encryption is used.
## Generated for X.224 client requests.
##
## c: The connection record for the underlying transport-layer session/flow.
##
## cookie: The cookie included in the request.
event rdp_client_request%(c: connection, cookie: string%);
event rdp_connect_request%(c: connection, cookie: string%);
## Generated for MCS client requests when native RDP encryption is used.
## Generated for RDP Negotiation Response messages.
##
## c: The connection record for the underlying transport-layer session/flow.
##
## selected_security_protocol: The security protocol selected by the server.
event rdp_negotiation_response%(c: connection, selected_security_protocol: count%);
## Generated for RDP Negotiation Failure messages.
##
## c: The connection record for the underlying transport-layer session/flow.
##
## failure_code: The failure code sent by the server.
event rdp_negotiation_failure%(c: connection, failure_code: count%);
## Generated for MCS client requests.
##
## c: The connection record for the underlying transport-layer session/flow.
##
## data: The data contained in the client core data structure.
event rdp_client_core_data%(c: connection, data: RDP::ClientCoreData%);
## Generated for MCS server responses when native RDP encryption is used.
## Generated for MCS server responses.
##
## c: The connection record for the underlying transport-layer session/flow.
##
## result: The 8-bit integer representing the GCC Conference Create Response result.
event rdp_result%(c: connection, result: count%);
event rdp_gcc_server_create_response%(c: connection, result: count%);
## Generated for MCS server responses when native RDP encryption is used.
## Generated for MCS server responses.
##
## c: The connection record for the underlying transport-layer session/flow.
##
@ -27,3 +41,14 @@ event rdp_result%(c: connection, result: count%);
##
## encryption_level: The 32-bit integer representing the encryption level used in the connection.
event rdp_server_security%(c: connection, encryption_method: count, encryption_level: count%);
## Generated for a server certificate section. If multiple X.509
## certificates are included in chain, this event will still
## only be generated a single time.
##
## c: The connection record for the underlying transport-layer session/flow.
##
## cert_type: Indicates the type of certificate.
##
## permanently_issued: Value will be true is the certificate(s) is permanent on the server.
event rdp_server_certificate%(c: connection, cert_type: count, permanently_issued: bool%);

View file

@ -8,11 +8,14 @@ refine flow RDP_Flow += {
function utf16_to_utf8_val(utf16: bytestring): StringVal
%{
size_t utf8size = 3 * utf16.length() + 1;
char* utf8stringnative = new char[utf8size];
std::string resultstring;
size_t widesize = utf16.length();
size_t utf8size = 3 * widesize + 1;
resultstring.resize(utf8size, '\0');
const UTF16* sourcestart = reinterpret_cast<const UTF16*>(utf16.begin());
const UTF16* sourceend = sourcestart + utf16.length();
UTF8* targetstart = reinterpret_cast<UTF8*>(utf8stringnative);
const UTF16* sourceend = sourcestart + widesize;
UTF8* targetstart = reinterpret_cast<UTF8*>(&resultstring[0]);
UTF8* targetend = targetstart + utf8size;
ConversionResult res = ConvertUTF16toUTF8(&sourcestart,
@ -20,33 +23,63 @@ refine flow RDP_Flow += {
&targetstart,
targetend,
strictConversion);
*targetstart = 0;
if ( res != conversionOK )
{
connection()->bro_analyzer()->Weird("Failed UTF-16 to UTF-8 conversion");
return new StringVal(utf16.length(), (const char *) utf16.begin());
}
*targetstart = 0;
// We're relying on no nulls being in the string.
return new StringVal(utf8stringnative);
return new StringVal(resultstring.c_str());
%}
function proc_rdp_client_request(client_request: Client_Request): bool
function proc_rdp_connect_request(cr: Connect_Request): bool
%{
connection()->bro_analyzer()->ProtocolConfirmation();
BifEvent::generate_rdp_client_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
bytestring_to_val(${client_request.cookie_value}));
if ( rdp_connect_request )
{
BifEvent::generate_rdp_connect_request(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
bytestring_to_val(${cr.cookie_value}));
}
return true;
%}
function proc_rdp_result(gcc_response: GCC_Server_Create_Response): bool
function proc_rdp_negotiation_response(nr: RDP_Negotiation_Response): bool
%{
if ( rdp_negotiation_response )
{
BifEvent::generate_rdp_negotiation_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${nr.selected_protocol});
}
return true;
%}
function proc_rdp_negotiation_failure(nf: RDP_Negotiation_Failure): bool
%{
if ( rdp_negotiation_failure )
{
BifEvent::generate_rdp_negotiation_failure(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${nf.failure_code});
}
return true;
%}
function proc_rdp_gcc_server_create_response(gcc_response: GCC_Server_Create_Response): bool
%{
connection()->bro_analyzer()->ProtocolConfirmation();
BifEvent::generate_rdp_result(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${gcc_response.result});
if ( rdp_gcc_server_create_response )
BifEvent::generate_rdp_gcc_server_create_response(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${gcc_response.result});
return true;
%}
@ -55,56 +88,76 @@ refine flow RDP_Flow += {
%{
connection()->bro_analyzer()->ProtocolConfirmation();
RecordVal* ec_flags = new RecordVal(BifType::Record::RDP::EarlyCapabilityFlags);
ec_flags->Assign(0, new Val(${ccore.SUPPORT_ERRINFO_PDU}, TYPE_BOOL));
ec_flags->Assign(1, new Val(${ccore.WANT_32BPP_SESSION}, TYPE_BOOL));
ec_flags->Assign(2, new Val(${ccore.SUPPORT_STATUSINFO_PDU}, TYPE_BOOL));
ec_flags->Assign(3, new Val(${ccore.STRONG_ASYMMETRIC_KEYS}, TYPE_BOOL));
ec_flags->Assign(4, new Val(${ccore.SUPPORT_MONITOR_LAYOUT_PDU}, TYPE_BOOL));
ec_flags->Assign(5, new Val(${ccore.SUPPORT_NETCHAR_AUTODETECT}, TYPE_BOOL));
ec_flags->Assign(6, new Val(${ccore.SUPPORT_DYNVC_GFX_PROTOCOL}, TYPE_BOOL));
ec_flags->Assign(7, new Val(${ccore.SUPPORT_DYNAMIC_TIME_ZONE}, TYPE_BOOL));
ec_flags->Assign(8, new Val(${ccore.SUPPORT_HEARTBEAT_PDU}, TYPE_BOOL));
if ( rdp_client_core_data )
{
RecordVal* ec_flags = new RecordVal(BifType::Record::RDP::EarlyCapabilityFlags);
ec_flags->Assign(0, new Val(${ccore.SUPPORT_ERRINFO_PDU}, TYPE_BOOL));
ec_flags->Assign(1, new Val(${ccore.WANT_32BPP_SESSION}, TYPE_BOOL));
ec_flags->Assign(2, new Val(${ccore.SUPPORT_STATUSINFO_PDU}, TYPE_BOOL));
ec_flags->Assign(3, new Val(${ccore.STRONG_ASYMMETRIC_KEYS}, TYPE_BOOL));
ec_flags->Assign(4, new Val(${ccore.SUPPORT_MONITOR_LAYOUT_PDU}, TYPE_BOOL));
ec_flags->Assign(5, new Val(${ccore.SUPPORT_NETCHAR_AUTODETECT}, TYPE_BOOL));
ec_flags->Assign(6, new Val(${ccore.SUPPORT_DYNVC_GFX_PROTOCOL}, TYPE_BOOL));
ec_flags->Assign(7, new Val(${ccore.SUPPORT_DYNAMIC_TIME_ZONE}, TYPE_BOOL));
ec_flags->Assign(8, new Val(${ccore.SUPPORT_HEARTBEAT_PDU}, TYPE_BOOL));
RecordVal* ccd = new RecordVal(BifType::Record::RDP::ClientCoreData);
ccd->Assign(0, new Val(${ccore.version_major}, TYPE_COUNT));
ccd->Assign(1, new Val(${ccore.version_minor}, TYPE_COUNT));
ccd->Assign(2, new Val(${ccore.desktop_width}, TYPE_COUNT));
ccd->Assign(3, new Val(${ccore.desktop_height}, TYPE_COUNT));
ccd->Assign(4, new Val(${ccore.color_depth}, TYPE_COUNT));
ccd->Assign(5, new Val(${ccore.sas_sequence}, TYPE_COUNT));
ccd->Assign(6, new Val(${ccore.keyboard_layout}, TYPE_COUNT));
ccd->Assign(7, new Val(${ccore.client_build}, TYPE_COUNT));
ccd->Assign(8, utf16_to_utf8_val(${ccore.client_name}));
ccd->Assign(9, new Val(${ccore.keyboard_type}, TYPE_COUNT));
ccd->Assign(10, new Val(${ccore.keyboard_sub}, TYPE_COUNT));
ccd->Assign(11, new Val(${ccore.keyboard_function_key}, TYPE_COUNT));
ccd->Assign(12, utf16_to_utf8_val(${ccore.ime_file_name}));
ccd->Assign(13, new Val(${ccore.post_beta2_color_depth}, TYPE_COUNT));
ccd->Assign(14, new Val(${ccore.client_product_id}, TYPE_COUNT));
ccd->Assign(15, new Val(${ccore.serial_number}, TYPE_COUNT));
ccd->Assign(16, new Val(${ccore.high_color_depth}, TYPE_COUNT));
ccd->Assign(17, new Val(${ccore.supported_color_depths}, TYPE_COUNT));
ccd->Assign(18, ec_flags);
ccd->Assign(19, utf16_to_utf8_val(${ccore.dig_product_id}));
RecordVal* ccd = new RecordVal(BifType::Record::RDP::ClientCoreData);
ccd->Assign(0, new Val(${ccore.version_major}, TYPE_COUNT));
ccd->Assign(1, new Val(${ccore.version_minor}, TYPE_COUNT));
ccd->Assign(2, new Val(${ccore.desktop_width}, TYPE_COUNT));
ccd->Assign(3, new Val(${ccore.desktop_height}, TYPE_COUNT));
ccd->Assign(4, new Val(${ccore.color_depth}, TYPE_COUNT));
ccd->Assign(5, new Val(${ccore.sas_sequence}, TYPE_COUNT));
ccd->Assign(6, new Val(${ccore.keyboard_layout}, TYPE_COUNT));
ccd->Assign(7, new Val(${ccore.client_build}, TYPE_COUNT));
ccd->Assign(8, utf16_to_utf8_val(${ccore.client_name}));
ccd->Assign(9, new Val(${ccore.keyboard_type}, TYPE_COUNT));
ccd->Assign(10, new Val(${ccore.keyboard_sub}, TYPE_COUNT));
ccd->Assign(11, new Val(${ccore.keyboard_function_key}, TYPE_COUNT));
ccd->Assign(12, utf16_to_utf8_val(${ccore.ime_file_name}));
ccd->Assign(13, new Val(${ccore.post_beta2_color_depth}, TYPE_COUNT));
ccd->Assign(14, new Val(${ccore.client_product_id}, TYPE_COUNT));
ccd->Assign(15, new Val(${ccore.serial_number}, TYPE_COUNT));
ccd->Assign(16, new Val(${ccore.high_color_depth}, TYPE_COUNT));
ccd->Assign(17, new Val(${ccore.supported_color_depths}, TYPE_COUNT));
ccd->Assign(18, ec_flags);
ccd->Assign(19, utf16_to_utf8_val(${ccore.dig_product_id}));
BifEvent::generate_rdp_client_core_data(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
ccd);
}
BifEvent::generate_rdp_client_core_data(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
ccd);
return true;
%}
function proc_rdp_server_security(ssd: Server_Security_Data): bool
%{
connection()->bro_analyzer()->ProtocolConfirmation();
BifEvent::generate_rdp_server_security(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${ssd.encryption_method},
${ssd.encryption_level});
if ( rdp_server_security )
BifEvent::generate_rdp_server_security(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${ssd.encryption_method},
${ssd.encryption_level});
return true;
%}
function proc_x509_cert(x509: X509): bool
function proc_rdp_server_certificate(cert: Server_Certificate): bool
%{
if ( rdp_server_certificate )
{
BifEvent::generate_rdp_server_certificate(connection()->bro_analyzer(),
connection()->bro_analyzer()->Conn(),
${cert.cert_type},
${cert.permanently_issued});
}
return true;
%}
function proc_x509_cert_data(x509: X509_Cert_Data): bool
%{
const bytestring& cert = ${x509.cert};
@ -126,8 +179,16 @@ refine flow RDP_Flow += {
%}
};
refine typeattr Client_Request += &let {
proc: bool = $context.flow.proc_rdp_client_request(this);
refine typeattr Connect_Request += &let {
proc: bool = $context.flow.proc_rdp_connect_request(this);
};
refine typeattr RDP_Negotiation_Response += &let {
proc: bool = $context.flow.proc_rdp_negotiation_response(this);
};
refine typeattr RDP_Negotiation_Failure += &let {
proc: bool = $context.flow.proc_rdp_negotiation_failure(this);
};
refine typeattr Client_Core_Data += &let {
@ -135,13 +196,17 @@ refine typeattr Client_Core_Data += &let {
};
refine typeattr GCC_Server_Create_Response += &let {
proc: bool = $context.flow.proc_rdp_result(this);
proc: bool = $context.flow.proc_rdp_gcc_server_create_response(this);
};
refine typeattr Server_Security_Data += &let {
proc: bool = $context.flow.proc_rdp_server_security(this);
};
refine typeattr X509 += &let {
proc: bool = $context.flow.proc_x509_cert(this);
refine typeattr Server_Certificate += &let {
proc: bool = $context.flow.proc_rdp_server_certificate(this);
};
refine typeattr X509_Cert_Data += &let {
proc: bool = $context.flow.proc_x509_cert_data(this);
};

View file

@ -14,12 +14,10 @@ type TPKT(is_orig: bool) = record {
type COTP = record {
cotp_len: uint8;
pdu: uint8;
# Probably should do something with this eventually.
#cotp_crap: padding[cotp_len-2];
switch: case pdu of {
#0xd0 -> cConfirm: Connect_Confirm;
0xe0 -> c_request: Client_Request;
0xf0 -> data: DT_Data;
0xd0 -> connect_confirm: Connect_Confirm;
0xe0 -> client_request: Connect_Request;
0xf0 -> data: DT_Data;
# In case we don't support the PDU we just
# consume the rest of it and throw it away.
@ -75,14 +73,59 @@ type Data_Block = record {
# Client X.224
######################################################################
type Client_Request = record {
type Connect_Request = record {
destination_reference: uint16;
source_reference: uint16;
flow_control: uint8;
cookie_mstshash: RE/Cookie: mstshash\=/;
cookie_value: RE/[^\x0d]*/;
cookie_value: RE/[^\x0d]+/;
cookie_terminator: RE/\x0d\x0a/;
rdp_neg_req: RDP_Negotiation_Request;
} &byteorder=littleendian;
type RDP_Negotiation_Request = record {
type: uint8;
flags: uint8;
length: uint16; # 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 = record {
destination_reference: uint16;
source_reference: uint16;
flags: uint8;
response_type: uint8;
response_switch: 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; # must be set to 8
selected_protocol: uint32;
} &let {
# Seems to be encrypted after this message if
# selected_protocol > 0
enc: bool = $context.connection.go_encrypted(selected_protocol>0);
} &byteorder=littleendian;
type RDP_Negotiation_Failure = record {
flags: uint8;
length: uint16;
failure_code: uint32;
} &byteorder=littleendian;
######################################################################
# Client MCS
######################################################################
@ -93,11 +136,11 @@ type Client_Header = record {
called_domain_selector: ASN1OctetString;
upward_flag: ASN1Boolean;
target_parameters: ASN1SequenceMeta;
targ_parameters_pad: padding[target_parameters.encoding.length];
targ_parameters_pad: bytestring &length=target_parameters.encoding.length &transient;
minimum_parameters: ASN1SequenceMeta;
min_parameters_pad: padding[minimum_parameters.encoding.length];
min_parameters_pad: bytestring &length=minimum_parameters.encoding.length &transient;
maximum_parameters: ASN1SequenceMeta;
max_parameters_pad: padding[maximum_parameters.encoding.length];
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;
@ -174,7 +217,7 @@ type Server_Header = record {
connect_response_called_id: ASN1Integer;
connect_response_domain_parameters: ASN1SequenceMeta;
# Skipping over domain parameters for now.
domain_parameters: padding[connect_response_domain_parameters.encoding.length];
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;
@ -219,20 +262,24 @@ type Server_Security_Data = record {
server_cert_length: uint32;
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
enc: bool = $context.connection.go_encrypted(encryption_level>0);
} &byteorder=littleendian;
type Server_Certificate = record {
version: uint32;
switch: case cert_type of {
0x01 -> proprietary: Server_Proprietary;
0x01 -> proprietary: Server_Proprietary_Cert(this);
0x02 -> x509: X509;
};
} &let {
cert_type: uint32 = version & 0x7FFFFFFF;
permanent_issue: bool = (version & 0x80000000) == 0;
cert_type: uint32 = version & 0x7FFFFFFF;
permanently_issued: bool = (version & 0x80000000) == 0;
} &byteorder=littleendian;
type Server_Proprietary = record {
type Server_Proprietary_Cert(cert: Server_Certificate) = record {
signature_algorithm: uint32;
key_algorithm: uint32;
public_key_blob_type: uint16;
@ -252,8 +299,13 @@ type Public_Key_Blob = record {
} &byteorder=littleendian;
type X509 = record {
pad1: padding[8];
cert: bytestring &restofdata;
num_of_certs: uint32;
certs: X509_Cert_Data[num_of_certs];
} &byteorder=littleendian;
type X509_Cert_Data = record {
cert_len: uint32;
cert: bytestring &length=cert_len;
} &byteorder=littleendian;
######################################################################
@ -314,3 +366,28 @@ function binary_to_int64(bs: bytestring): int64
return rval;
%}
refine connection RDP_Conn += {
%member{
bool is_encrypted_;
%}
%init{
is_encrypted_ = false;
%}
function go_encrypted(should_we: bool): bool
%{
if ( should_we )
{
printf("going encrypted\n");
is_encrypted_ = true;
}
return is_encrypted_;
%}
function is_encrypted(): bool
%{
return is_encrypted_;
%}
};