protocol parser: rdp

Initial implementation of feature 2314:
1. Add protocol parser for RDP
2. Add transactions for RDP negotiation
3. Add eve logging of transactions
pull/4188/head
Zach Kelly 6 years ago committed by Victor Julien
parent 59da7ae302
commit caef8b5b38

@ -1032,3 +1032,190 @@ Example ::
"reason": "timeout",
"alerted": false
}
Event type: RDP
---------------
Initial negotiations between RDP client and server are stored as transactions and logged.
Each RDP record contains a per-flow incrementing "tx_id" field.
The "event_type" field indicates an RDP event subtype. Possible values:
* "initial_request"
* "initial_response"
* "connect_request"
* "connect_response"
* "tls_handshake"
RDP type: Initial Request
~~~~~~~~~~~~~~~~~~~~~~~~~
The optional "cookie" field is a string identifier the RDP client has chosen to provide.
The optional "flags" field is a list of client directives. Possible values:
* "restricted_admin_mode_required"
* "redirected_authentication_mode_required"
* "correlation_info_present"
RDP type: Initial Response
~~~~~~~~~~~~~~~~~~~~~~~~~~
In the event of a standard initial response:
The "protocol" field is the selected protocol. Possible values:
* "rdp"
* "ssl"
* "hybrid"
* "rds_tls"
* "hybrid_ex"
The optional "flags" field is a list of support server modes. Possible values:
* "extended_client_data"
* "dynvc_gfx"
* "restricted_admin"
* "redirected_authentication"
Alternatively, in the event of an error-indicating initial response:
There will be no "protocol" or "flags" fields.
The "error_code" field will contain the numeric code provided by the RDP server.
The "reason" field will contain a text summary of this code. Possible values:
* "ssl required by server" (error code 0x1)
* "ssl not allowed by server" (error code 0x2)
* "ssl cert not on server" (error code 0x3)
* "inconsistent flags" (error code 0x4)
* "hybrid required by server" (error code 0x5)
* "ssl with user auth required by server" (error code 0x6)
RDP type: Connect Request
~~~~~~~~~~~~~~~~~~~~~~~~~
The optional "channel" field is a list of requested data channel names.
Common channels:
* "rdpdr" (device redirection)
* "cliprdr" (shared clipboard)
* "rdpsnd" (sound)
The optional "client" field is a sub-object that may contain the following:
* "version": RDP protocol version. Possible values are "v4", "v5", "v10.0", "v10.1", "v10.2", "v10.3", "v10.4", "v10.5", "v10.6", "v10.7", "unknown".
* "desktop_width": Numeric desktop width value.
* "desktop_height": Numeric desktop height value.
* "color_depth": Numeric color depth. Possible values are 4, 8, 15, 16, 24.
* "keyboard_layout": Locale identifier name, e.g., "en-US".
* "build": OS and SP level, e.g., "Windows XP", "Windows 7 SP1".
* "client_name": Client computer name.
* "keyboard_type": Possible values are "xt", "ico", "at", "enhanced", "1050", "9140", "jp".
* "keyboard_subtype": Numeric code for keyboard.
* "function_keys": Number of function keys on client keyboard.
* "ime": Input method editor (IME) file name.
* "product_id": Product id string.
* "serial_number": Numeric value.
* "capabilities": List of any of the following: "support_errinfo_pdf", "want_32bpp_session", "support_statusinfo_pdu", "strong_asymmetric_keys", "valid_connection_type", "support_monitor_layout_pdu", "support_netchar_autodetect", "support_dynvc_gfx_protocol", "support_dynamic_time_zone", "support_heartbeat_pdu".
* "id": Client product id string.
* "connection_hint": Possible values are "modem", "low_broadband", "satellite", "high_broadband", "wan", "lan", "autodetect".
* "physical_width": Numeric phyical width of display.
* "physical_height": Numeric physical height of display.
* "desktop_orientation": Numeric angle of orientation.
* "scale_factor": Numeric scale factor of desktop.
* "device_scale_factor": Numeric scale factor of display.
RDP type: Connect Response
~~~~~~~~~~~~~~~~~~~~~~~~~~
With this event, the initial RDP negotiation is complete in terms of tracking and logging.
RDP type: TLS Handshake
~~~~~~~~~~~~~~~~~~~~~~~
With this event, the initial RDP negotiation is complete in terms of tracking and logging.
The session will use TLS encryption.
The "x509_serials" field is a list of observed certificate serial numbers, e.g., "16ed2aa0495f259d4f5d99edada570d1".
Examples
~~~~~~~~
RDP logging:
::
"rdp": {
"tx_id": 0,
"event_type": "initial_request",
"cookie": "A70067"
}
"rdp": {
"tx_id": 1,
"event_type": "initial_response"
}
"rdp": {
"tx_id": 2,
"event_type": "connect_request",
"client": {
"version": "v5",
"desktop_width": 1152,
"desktop_height": 864,
"color_depth": 15,
"keyboard_layout": "en-US",
"build": "Windows XP",
"client_name": "ISD2-KM84178",
"keyboard_type": "enhanced",
"function_keys": 12,
"product_id": 1,
"capabilities": [
"support_errinfo_pdf"
],
"id": "55274-OEM-0011903-00107"
},
"channels": [
"rdpdr",
"cliprdr",
"rdpsnd"
]
}
"rdp": {
"tx_id": 3,
"event_type": "connect_response"
}
RDP logging, with transition to TLS:
::
"rdp": {
"tx_id": 0,
"event_type": "initial_request",
"cookie": "AWAKECODI"
}
"rdp": {
"tx_id": 1,
"event_type": "initial_response",
"server_supports": [
"extended_client_data"
],
"protocol": "hybrid"
}
"rdp": {
"tx_id": 2,
"event_type": "tls_handshake",
"x509_serials": [
"16ed2aa0495f259d4f5d99edada570d1"
]
}

@ -16,10 +16,20 @@ debug = []
[dependencies]
nom = "4.2"
bitflags = "1.0"
byteorder = "1.3"
crc = "1.8"
memchr = "2.2"
num = "0.2"
num-derive = "0.2"
num-traits = "0.2"
widestring = "0.4"
der-parser = "1.1"
kerberos-parser = "0.2"
ntp-parser = "0.3"
ipsec-parser = "0.4"
snmp-parser = "0.3.0"
tls-parser = "0.8"
x509-parser = "0.4"

@ -20,10 +20,19 @@
#[macro_use]
extern crate nom;
#[macro_use]
extern crate bitflags;
extern crate byteorder;
extern crate crc;
extern crate memchr;
#[macro_use]
extern crate num_derive;
extern crate widestring;
extern crate der_parser;
extern crate kerberos_parser;
extern crate tls_parser;
extern crate x509_parser;
#[macro_use]
pub mod log;
@ -57,3 +66,4 @@ pub mod ntp;
pub mod tftp;
pub mod dhcp;
pub mod applayertemplate;
pub mod rdp;

@ -0,0 +1,22 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
// Author: Zach Kelly <zach.kelly@lmco.com>
// custom errors the parser can emit
pub const RDP_UNIMPLEMENTED_LENGTH_DETERMINANT: u32 = 128;
pub const RDP_NOT_X224_CLASS_0_ERROR: u32 = 129;

@ -0,0 +1,652 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
// Author: Zach Kelly <zach.kelly@lmco.com>
use super::rdp::{RdpTransaction, RdpTransactionItem};
use json::{Json, JsonT};
use rdp::parser::*;
use rdp::windows;
use std;
use x509_parser::parse_x509_der;
#[no_mangle]
pub extern "C" fn rs_rdp_to_json(tx: *mut std::os::raw::c_void) -> *mut JsonT {
let tx = cast_pointer!(tx, RdpTransaction);
match to_json(tx) {
Some(js) => js.unwrap(),
None => std::ptr::null_mut(),
}
}
/// populate a json object with transactional information, for logging
fn to_json(tx: &RdpTransaction) -> Option<Json> {
let js = Json::object();
js.set_integer("tx_id", tx.id);
match &tx.item {
RdpTransactionItem::X224ConnectionRequest(ref x224) => {
x224_req_to_json(&js, x224)
}
RdpTransactionItem::X224ConnectionConfirm(x224) => {
x224_conf_to_json(&js, x224)
}
RdpTransactionItem::McsConnectRequest(ref mcs) => {
mcs_req_to_json(&js, mcs);
}
RdpTransactionItem::McsConnectResponse(_) => {
// no additional JSON data beyond `event_type`
js.set_string("event_type", "connect_response");
}
RdpTransactionItem::TlsCertificateChain(chain) => {
js.set_string("event_type", "tls_handshake");
let js_chain = Json::array();
for blob in chain {
match parse_x509_der(&blob.data) {
Ok((_, cert)) => {
js_chain.array_append_string(
&cert.tbs_certificate.serial.to_str_radix(16),
);
}
_ => {}
}
}
js.set("x509_serials", js_chain);
}
}
return Some(js);
}
/// json helper for X224ConnectionRequest
fn x224_req_to_json(js: &Json, x224: &X224ConnectionRequest) {
use rdp::parser::NegotiationRequestFlags as Flags;
js.set_string("event_type", "initial_request");
if let Some(ref cookie) = x224.cookie {
js.set_string("cookie", &cookie.mstshash);
}
if let Some(ref req) = x224.negotiation_request {
if !req.flags.is_empty() {
let flags = Json::array();
if req.flags.contains(Flags::RESTRICTED_ADMIN_MODE_REQUIRED) {
flags.array_append_string("restricted_admin_mode_required");
}
if req
.flags
.contains(Flags::REDIRECTED_AUTHENTICATION_MODE_REQUIRED)
{
flags.array_append_string(
"redirected_authentication_mode_required",
);
}
if req.flags.contains(Flags::CORRELATION_INFO_PRESENT) {
flags.array_append_string("correlation_info_present");
}
js.set("flags", flags);
}
}
}
/// json helper for X224ConnectionConfirm
fn x224_conf_to_json(js: &Json, x224: &X224ConnectionConfirm) {
use rdp::parser::NegotiationResponseFlags as Flags;
js.set_string("event_type", "initial_response");
if let Some(ref from_server) = x224.negotiation_from_server {
match &from_server {
NegotiationFromServer::Response(ref resp) => {
if !resp.flags.is_empty() {
let flags = Json::array();
if resp
.flags
.contains(Flags::EXTENDED_CLIENT_DATA_SUPPORTED)
{
flags.array_append_string("extended_client_data");
}
if resp.flags.contains(Flags::DYNVC_GFX_PROTOCOL_SUPPORTED)
{
flags.array_append_string("dynvc_gfx");
}
// NEGRSP_FLAG_RESERVED not logged
if resp
.flags
.contains(Flags::RESTRICTED_ADMIN_MODE_SUPPORTED)
{
flags.array_append_string("restricted_admin");
}
if resp.flags.contains(
Flags::REDIRECTED_AUTHENTICATION_MODE_SUPPORTED,
) {
flags.array_append_string("redirected_authentication");
}
js.set("server_supports", flags);
}
let protocol = match resp.protocol {
Protocol::ProtocolRdp => "rdp",
Protocol::ProtocolSsl => "ssl",
Protocol::ProtocolHybrid => "hybrid",
Protocol::ProtocolRdsTls => "rds_tls",
Protocol::ProtocolHybridEx => "hybrid_ex",
};
js.set_string("protocol", protocol);
}
NegotiationFromServer::Failure(ref fail) => match fail.code {
NegotiationFailureCode::SslRequiredByServer => {
js.set_integer(
"error_code",
NegotiationFailureCode::SslRequiredByServer as u64,
);
js.set_string("reason", "ssl required by server")
}
NegotiationFailureCode::SslNotAllowedByServer => {
js.set_integer(
"error_code",
NegotiationFailureCode::SslNotAllowedByServer as u64,
);
js.set_string("reason", "ssl not allowed by server")
}
NegotiationFailureCode::SslCertNotOnServer => {
js.set_integer(
"error_code",
NegotiationFailureCode::SslCertNotOnServer as u64,
);
js.set_string("reason", "ssl cert not on server")
}
NegotiationFailureCode::InconsistentFlags => {
js.set_integer(
"error_code",
NegotiationFailureCode::InconsistentFlags as u64,
);
js.set_string("reason", "inconsistent flags")
}
NegotiationFailureCode::HybridRequiredByServer => {
js.set_integer(
"error_code",
NegotiationFailureCode::HybridRequiredByServer as u64,
);
js.set_string("reason", "hybrid required by server")
}
NegotiationFailureCode::SslWithUserAuthRequiredByServer => {
js.set_integer(
"error_code",
NegotiationFailureCode::SslWithUserAuthRequiredByServer
as u64,
);
js.set_string(
"reason",
"ssl with user auth required by server",
)
}
},
}
}
}
/// json helper for McsConnectRequest
fn mcs_req_to_json(js: &Json, mcs: &McsConnectRequest) {
// placeholder string value. We do not simply omit "unknown" values so that they can
// help indicate that a given enum may be out of date (new Windows version, etc.)
let unknown = String::from("unknown");
js.set_string("event_type", "connect_request");
for child in &mcs.children {
match child {
McsConnectRequestChild::CsClientCore(ref client) => {
let js_client = Json::object();
match client.version {
Some(ref ver) => js_client
.set_string("version", &version_to_string(ver, "v")),
None => js_client.set_string("version", &unknown),
}
js_client
.set_integer("desktop_width", client.desktop_width as u64);
js_client.set_integer(
"desktop_height",
client.desktop_height as u64,
);
if let Some(depth) = get_color_depth(client) {
js_client.set_integer("color_depth", depth);
}
// sas_sequence not logged
js_client.set_string(
"keyboard_layout",
&windows::lcid_to_string(client.keyboard_layout, &unknown),
);
js_client.set_string(
"build",
&windows::os_to_string(&client.client_build, &unknown),
);
if client.client_name.len() > 0 {
js_client.set_string("client_name", &client.client_name);
}
if let Some(ref kb) = client.keyboard_type {
js_client
.set_string("keyboard_type", &keyboard_to_string(kb));
}
if client.keyboard_subtype != 0 {
js_client.set_integer(
"keyboard_subtype",
client.keyboard_subtype as u64,
);
}
if client.keyboard_function_key != 0 {
js_client.set_integer(
"function_keys",
client.keyboard_function_key as u64,
);
}
if client.ime_file_name.len() > 0 {
js_client.set_string("ime", &client.ime_file_name);
}
//
// optional fields
//
if let Some(id) = client.client_product_id {
js_client.set_integer("product_id", id as u64);
}
if let Some(serial) = client.serial_number {
if serial != 0 {
js_client.set_integer("serial_number", serial as u64);
}
}
// supported_color_depth not logged
if let Some(ref early_capability_flags) =
client.early_capability_flags
{
use rdp::parser::EarlyCapabilityFlags as Flags;
if !early_capability_flags.is_empty() {
let flags = Json::array();
if early_capability_flags
.contains(Flags::RNS_UD_CS_SUPPORT_ERRINFO_PDF)
{
flags.array_append_string("support_errinfo_pdf");
}
if early_capability_flags
.contains(Flags::RNS_UD_CS_WANT_32BPP_SESSION)
{
flags.array_append_string("want_32bpp_session");
}
if early_capability_flags
.contains(Flags::RNS_UD_CS_SUPPORT_STATUSINFO_PDU)
{
flags.array_append_string("support_statusinfo_pdu");
}
if early_capability_flags
.contains(Flags::RNS_UD_CS_STRONG_ASYMMETRIC_KEYS)
{
flags.array_append_string("strong_asymmetric_keys");
}
// RNS_UD_CS_UNUSED not logged
if early_capability_flags
.contains(Flags::RNS_UD_CS_VALID_CONNECTION_TYPE)
{
flags.array_append_string("valid_connection_type");
}
if early_capability_flags.contains(
Flags::RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU,
) {
flags.array_append_string(
"support_monitor_layout_pdu",
);
}
if early_capability_flags.contains(
Flags::RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT,
) {
flags.array_append_string(
"support_netchar_autodetect",
);
}
if early_capability_flags.contains(
Flags::RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL,
) {
flags.array_append_string(
"support_dynvc_gfx_protocol",
);
}
if early_capability_flags.contains(
Flags::RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE,
) {
flags.array_append_string(
"support_dynamic_time_zone",
);
}
if early_capability_flags
.contains(Flags::RNS_UD_CS_SUPPORT_HEARTBEAT_PDU)
{
flags.array_append_string("support_heartbeat_pdu");
}
js_client.set("capabilities", flags);
}
}
if let Some(ref id) = client.client_dig_product_id {
if id.len() > 0 {
js_client.set_string("id", id);
}
}
if let Some(ref hint) = client.connection_hint {
let s = match hint {
ConnectionHint::ConnectionHintModem => "modem",
ConnectionHint::ConnectionHintBroadbandLow => {
"low_broadband"
}
ConnectionHint::ConnectionHintSatellite => "satellite",
ConnectionHint::ConnectionHintBroadbandHigh => {
"high_broadband"
}
ConnectionHint::ConnectionHintWan => "wan",
ConnectionHint::ConnectionHintLan => "lan",
ConnectionHint::ConnectionHintAutoDetect => {
"autodetect"
}
ConnectionHint::ConnectionHintNotProvided => "",
};
if *hint != ConnectionHint::ConnectionHintNotProvided {
js_client.set_string("connection_hint", s);
}
}
// server_selected_procotol not logged
if let Some(width) = client.desktop_physical_width {
js_client.set_integer("physical_width", width as u64);
}
if let Some(height) = client.desktop_physical_height {
js_client.set_integer("physical_height", height as u64);
}
if let Some(orientation) = client.desktop_orientation {
js_client
.set_integer("desktop_orientation", orientation as u64);
}
if let Some(scale) = client.desktop_scale_factor {
js_client.set_integer("scale_factor", scale as u64);
}
if let Some(scale) = client.device_scale_factor {
js_client.set_integer("device_scale_factor", scale as u64);
}
js.set("client", js_client);
}
McsConnectRequestChild::CsNet(ref net) => {
if net.channels.len() > 0 {
let channels = Json::array();
for channel in &net.channels {
channels.array_append_string(&channel);
}
js.set("channels", channels);
}
}
McsConnectRequestChild::CsUnknown(_) => {}
}
}
}
/// converts RdpClientVersion to a string, using the provided prefix
fn version_to_string<'a>(ver: &RdpClientVersion, prefix: &'a str) -> String {
let mut result = String::from(prefix);
match ver {
RdpClientVersion::V4 => result.push_str("4"),
RdpClientVersion::V5_V8_1 => result.push_str("5"),
RdpClientVersion::V10_0 => result.push_str("10.0"),
RdpClientVersion::V10_1 => result.push_str("10.1"),
RdpClientVersion::V10_2 => result.push_str("10.2"),
RdpClientVersion::V10_3 => result.push_str("10.3"),
RdpClientVersion::V10_4 => result.push_str("10.4"),
RdpClientVersion::V10_5 => result.push_str("10.5"),
RdpClientVersion::V10_6 => result.push_str("10.6"),
RdpClientVersion::V10_7 => result.push_str("10.7"),
};
result
}
/// checks multiple client info fields to determine color depth
fn get_color_depth(client: &CsClientCoreData) -> Option<u64> {
// first check high_color_depth
match client.high_color_depth {
Some(HighColorDepth::HighColor4Bpp) => return Some(4),
Some(HighColorDepth::HighColor8Bpp) => return Some(8),
Some(HighColorDepth::HighColor15Bpp) => return Some(15),
Some(HighColorDepth::HighColor16Bpp) => return Some(16),
Some(HighColorDepth::HighColor24Bpp) => return Some(24),
_ => (),
};
// if not present, try post_beta2_color_depth
match client.post_beta2_color_depth {
Some(PostBeta2ColorDepth::RnsUdColor4Bpp) => return Some(4),
Some(PostBeta2ColorDepth::RnsUdColor8Bpp) => return Some(8),
Some(PostBeta2ColorDepth::RnsUdColor16Bpp555) => return Some(15),
Some(PostBeta2ColorDepth::RnsUdColor16Bpp565) => return Some(16),
Some(PostBeta2ColorDepth::RnsUdColor24Bpp) => return Some(24),
_ => (),
};
// if not present, try color_depth
match client.color_depth {
Some(ColorDepth::RnsUdColor4Bpp) => return Some(4),
Some(ColorDepth::RnsUdColor8Bpp) => return Some(8),
_ => return None,
}
}
fn keyboard_to_string(kb: &KeyboardType) -> String {
let s = match kb {
KeyboardType::KbXt => "xt",
KeyboardType::KbIco => "ico",
KeyboardType::KbAt => "at",
KeyboardType::KbEnhanced => "enhanced",
KeyboardType::Kb1050 => "1050",
KeyboardType::Kb9140 => "9140",
KeyboardType::KbJapanese => "jp",
};
String::from(s)
}
#[cfg(test)]
mod tests {
use super::*;
// for now, unsure how to effectively test Json/JsonT
#[test]
fn test_version_string() {
assert_eq!("v10.7", version_to_string(&RdpClientVersion::V10_7, "v"));
}
#[test]
fn test_color_depth_high() {
let core_data = CsClientCoreData {
version: None,
desktop_width: 1280,
desktop_height: 768,
color_depth: Some(ColorDepth::RnsUdColor4Bpp),
sas_sequence: None,
keyboard_layout: 0x409,
client_build: windows::OperatingSystem {
build: windows::Build::Win10_17763,
suffix: windows::Suffix::Rs5,
},
client_name: String::from("SERVER-XYZ"),
keyboard_type: None,
keyboard_subtype: 0,
keyboard_function_key: 12,
ime_file_name: String::from(""),
post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp),
client_product_id: None,
serial_number: None,
high_color_depth: Some(HighColorDepth::HighColor24Bpp),
supported_color_depth: None,
early_capability_flags: None,
client_dig_product_id: None,
connection_hint: None,
server_selected_protocol: None,
desktop_physical_width: None,
desktop_physical_height: None,
desktop_orientation: None,
desktop_scale_factor: None,
device_scale_factor: None,
};
assert_eq!(Some(24), get_color_depth(&core_data));
}
#[test]
fn test_color_depth_post_beta2() {
let core_data = CsClientCoreData {
version: None,
desktop_width: 1280,
desktop_height: 768,
color_depth: Some(ColorDepth::RnsUdColor4Bpp),
sas_sequence: None,
keyboard_layout: 0x409,
client_build: windows::OperatingSystem {
build: windows::Build::Win10_17763,
suffix: windows::Suffix::Rs5,
},
client_name: String::from("SERVER-XYZ"),
keyboard_type: None,
keyboard_subtype: 0,
keyboard_function_key: 12,
ime_file_name: String::from(""),
post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp),
client_product_id: None,
serial_number: None,
high_color_depth: None,
supported_color_depth: None,
early_capability_flags: None,
client_dig_product_id: None,
connection_hint: None,
server_selected_protocol: None,
desktop_physical_width: None,
desktop_physical_height: None,
desktop_orientation: None,
desktop_scale_factor: None,
device_scale_factor: None,
};
assert_eq!(Some(8), get_color_depth(&core_data));
}
#[test]
fn test_color_depth_basic() {
let core_data = CsClientCoreData {
version: None,
desktop_width: 1280,
desktop_height: 768,
color_depth: Some(ColorDepth::RnsUdColor4Bpp),
sas_sequence: None,
keyboard_layout: 0x409,
client_build: windows::OperatingSystem {
build: windows::Build::Win10_17763,
suffix: windows::Suffix::Rs5,
},
client_name: String::from("SERVER-XYZ"),
keyboard_type: None,
keyboard_subtype: 0,
keyboard_function_key: 12,
ime_file_name: String::from(""),
post_beta2_color_depth: None,
client_product_id: None,
serial_number: None,
high_color_depth: None,
supported_color_depth: None,
early_capability_flags: None,
client_dig_product_id: None,
connection_hint: None,
server_selected_protocol: None,
desktop_physical_width: None,
desktop_physical_height: None,
desktop_orientation: None,
desktop_scale_factor: None,
device_scale_factor: None,
};
assert_eq!(Some(4), get_color_depth(&core_data));
}
#[test]
fn test_color_depth_missing() {
let core_data = CsClientCoreData {
version: None,
desktop_width: 1280,
desktop_height: 768,
color_depth: None,
sas_sequence: None,
keyboard_layout: 0x409,
client_build: windows::OperatingSystem {
build: windows::Build::Win10_17763,
suffix: windows::Suffix::Rs5,
},
client_name: String::from("SERVER-XYZ"),
keyboard_type: None,
keyboard_subtype: 0,
keyboard_function_key: 12,
ime_file_name: String::from(""),
post_beta2_color_depth: None,
client_product_id: None,
serial_number: None,
high_color_depth: None,
supported_color_depth: None,
early_capability_flags: None,
client_dig_product_id: None,
connection_hint: None,
server_selected_protocol: None,
desktop_physical_width: None,
desktop_physical_height: None,
desktop_orientation: None,
desktop_scale_factor: None,
device_scale_factor: None,
};
assert!(get_color_depth(&core_data).is_none());
}
#[test]
fn test_keyboard_string() {
assert_eq!("enhanced", keyboard_to_string(&KeyboardType::KbEnhanced));
}
}

@ -0,0 +1,27 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
//! RDP parser and application layer
//!
//! written by Zach Kelly <zach.kelly@lmco.com>
pub mod error;
pub mod log;
pub mod parser;
pub mod rdp;
pub mod util;
pub mod windows;

File diff suppressed because it is too large Load Diff

@ -0,0 +1,705 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
// Author: Zach Kelly <zach.kelly@lmco.com>
//! RDP application layer
use core::{
self, AppProto, DetectEngineState, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP,
};
use nom;
use parser::*;
use rdp::parser::*;
use std;
use std::mem::transmute;
use tls_parser::{
parse_tls_plaintext, TlsMessage, TlsMessageHandshake, TlsRecordType,
};
static mut ALPROTO_RDP: AppProto = ALPROTO_UNKNOWN;
//
// transactions
//
#[derive(Debug, PartialEq)]
pub struct CertificateBlob {
pub data: Vec<u8>,
}
#[derive(Debug, PartialEq)]
pub enum RdpTransactionItem {
X224ConnectionRequest(X224ConnectionRequest),
X224ConnectionConfirm(X224ConnectionConfirm),
McsConnectRequest(McsConnectRequest),
McsConnectResponse(McsConnectResponse),
TlsCertificateChain(Vec<CertificateBlob>),
}
#[derive(Debug, PartialEq)]
pub struct RdpTransaction {
pub id: u64,
pub item: RdpTransactionItem,
// managed by macros `export_tx_get_detect_state!` and `export_tx_set_detect_state!`
de_state: Option<*mut DetectEngineState>,
}
impl RdpTransaction {
fn new(id: u64, item: RdpTransactionItem) -> Self {
Self {
id,
item,
de_state: None,
}
}
fn free(&mut self) {
if let Some(de_state) = self.de_state {
core::sc_detect_engine_state_free(de_state);
}
}
}
impl Drop for RdpTransaction {
fn drop(&mut self) {
self.free();
}
}
#[no_mangle]
pub extern "C" fn rs_rdp_state_get_tx(
state: *mut std::os::raw::c_void,
tx_id: u64,
) -> *mut std::os::raw::c_void {
let state = cast_pointer!(state, RdpState);
match state.get_tx(tx_id) {
Some(tx) => {
return unsafe { transmute(tx) };
}
None => {
return std::ptr::null_mut();
}
}
}
#[no_mangle]
pub extern "C" fn rs_rdp_state_get_tx_count(
state: *mut std::os::raw::c_void,
) -> u64 {
let state = cast_pointer!(state, RdpState);
return state.next_id;
}
#[no_mangle]
pub extern "C" fn rs_rdp_tx_get_progress_complete(
_direction: u8,
) -> std::os::raw::c_int {
// a parser can implement a multi-step tx completion by using an arbitrary `n`
return 1;
}
#[no_mangle]
pub extern "C" fn rs_rdp_tx_get_progress(
_tx: *mut std::os::raw::c_void,
_direction: u8,
) -> std::os::raw::c_int {
// tx complete when `rs_rdp_tx_get_progress(...) == rs_rdp_tx_get_progress_complete(...)`
// here, all transactions are immediately complete on insert
return 1;
}
//
// state
//
#[derive(Debug, PartialEq)]
pub struct RdpState {
next_id: u64,
to_client: Vec<u8>,
to_server: Vec<u8>,
transactions: Vec<RdpTransaction>,
tls_parsing: bool,
bypass_parsing: bool,
}
impl RdpState {
fn new() -> Self {
Self {
next_id: 0,
to_client: Vec::new(),
to_server: Vec::new(),
transactions: Vec::new(),
tls_parsing: false,
bypass_parsing: false,
}
}
fn free_tx(&mut self, tx_id: u64) {
let len = self.transactions.len();
let mut found = false;
let mut index = 0;
for ii in 0..len {
let tx = &self.transactions[ii];
if tx.id == tx_id {
found = true;
index = ii;
break;
}
}
if found {
self.transactions.remove(index);
}
}
fn get_tx(&self, tx_id: u64) -> Option<&RdpTransaction> {
for tx in &self.transactions {
if tx.id == tx_id {
return Some(tx);
}
}
return None;
}
fn new_tx(&mut self, item: RdpTransactionItem) -> RdpTransaction {
let tx = RdpTransaction::new(self.next_id, item);
self.next_id += 1;
return tx;
}
/// parse buffer captures from client to server
fn parse_ts(&mut self, input: &[u8]) -> bool {
// no need to process input buffer
if self.bypass_parsing {
return true;
}
// combine residual buffer with provided buffer
self.to_server.extend(input);
let temp: Vec<u8> = self.to_server.split_off(0);
let mut available = temp.as_slice();
loop {
if available.len() == 0 {
return true;
}
if self.tls_parsing {
match parse_tls_plaintext(&available) {
Ok((remainder, _tls)) => {
// update awaiting-parsing buffer
available = remainder;
}
Err(nom::Err::Incomplete(_)) => {
// save unparsed residual buffer for next parse
self.to_server.extend(available);
return true;
}
Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
return false;
}
}
} else {
// every message should be encapsulated within a T.123 tpkt
match parse_t123_tpkt(&available) {
// success
Ok((remainder, t123)) => {
// update awaiting-parsing buffer
available = remainder;
// evaluate message within the tpkt
match t123.child {
// X.224 connection request
T123TpktChild::X224ConnectionRequest(x224) => {
let tx = self.new_tx(
RdpTransactionItem::X224ConnectionRequest(
x224,
),
);
self.transactions.push(tx);
}
// X.223 data packet, evaluate what it encapsulates
T123TpktChild::Data(x223) => {
match x223.child {
X223DataChild::McsConnectRequest(mcs) => {
let tx =
self.new_tx(RdpTransactionItem::McsConnectRequest(mcs));
self.transactions.push(tx);
}
// unknown message in X.223, skip
_ => (),
}
}
// unknown message in T.123, skip
_ => (),
}
}
Err(nom::Err::Incomplete(_)) => {
// save unparsed residual buffer for next parse
self.to_server.extend(available);
return true;
}
Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
if probe_tls_handshake(available) {
self.tls_parsing = true;
return self.parse_ts(available);
} else {
return false;
}
}
}
}
}
}
/// parse buffer captures from server to client
fn parse_tc(&mut self, input: &[u8]) -> bool {
// no need to process input buffer
if self.bypass_parsing {
return true;
}
// combine residual buffer with provided buffer
self.to_client.extend(input);
let temp: Vec<u8> = self.to_client.split_off(0);
let mut available = temp.as_slice();
loop {
if available.len() == 0 {
return true;
}
if self.tls_parsing {
match parse_tls_plaintext(&available) {
Ok((remainder, tls)) => {
// update awaiting-parsing buffer
available = remainder;
for message in &tls.msg {
match message {
TlsMessage::Handshake(
TlsMessageHandshake::Certificate(contents),
) => {
let mut chain = Vec::new();
for cert in &contents.cert_chain {
chain.push(CertificateBlob {
data: cert.data.to_vec(),
});
}
let tx = self.new_tx(
RdpTransactionItem::TlsCertificateChain(
chain,
),
);
self.transactions.push(tx);
self.bypass_parsing = true;
}
_ => {}
}
}
}
Err(nom::Err::Incomplete(_)) => {
// save unparsed residual buffer for next parse
self.to_client.extend(available);
return true;
}
Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
return false;
}
}
} else {
// every message should be encapsulated within a T.123 tpkt
match parse_t123_tpkt(&available) {
// success
Ok((remainder, t123)) => {
// update awaiting-parsing buffer
available = remainder;
// evaluate message within the tpkt
match t123.child {
// X.224 connection confirm
T123TpktChild::X224ConnectionConfirm(x224) => {
let tx = self.new_tx(
RdpTransactionItem::X224ConnectionConfirm(
x224,
),
);
self.transactions.push(tx);
}
// X.223 data packet, evaluate what it encapsulates
T123TpktChild::Data(x223) => {
match x223.child {
X223DataChild::McsConnectResponse(mcs) => {
let tx = self
.new_tx(RdpTransactionItem::McsConnectResponse(mcs));
self.transactions.push(tx);
self.bypass_parsing = true;
return true;
}
// unknown message in X.223, skip
_ => (),
}
}
// unknown message in T.123, skip
_ => (),
}
}
Err(nom::Err::Incomplete(_)) => {
self.to_client.extend(available);
return true;
}
Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
if probe_tls_handshake(available) {
self.tls_parsing = true;
return self.parse_tc(available);
} else {
return false;
}
}
}
}
}
}
}
#[no_mangle]
pub extern "C" fn rs_rdp_state_new() -> *mut std::os::raw::c_void {
let state = RdpState::new();
let boxed = Box::new(state);
return unsafe { std::mem::transmute(boxed) };
}
#[no_mangle]
pub extern "C" fn rs_rdp_state_free(state: *mut std::os::raw::c_void) {
let _drop: Box<RdpState> = unsafe { std::mem::transmute(state) };
}
#[no_mangle]
pub extern "C" fn rs_rdp_state_tx_free(
state: *mut std::os::raw::c_void,
tx_id: u64,
) {
let state = cast_pointer!(state, RdpState);
state.free_tx(tx_id);
}
//
// detection state
//
export_tx_get_detect_state!(rs_rdp_tx_get_detect_state, RdpTransaction);
export_tx_set_detect_state!(rs_rdp_tx_set_detect_state, RdpTransaction);
//
// probe
//
/// probe for T.123 type identifier, as each message is encapsulated in T.123
fn probe_rdp(input: &[u8]) -> bool {
input.len() > 0 && input[0] == TpktVersion::T123 as u8
}
/// probe for T.123 message, whether to client or to server
#[no_mangle]
pub extern "C" fn rs_rdp_probe_ts_tc(
_flow: *const Flow,
_direction: u8,
input: *const u8,
input_len: u32,
_rdir: *mut u8,
) -> AppProto {
if input != std::ptr::null_mut() {
// probe bytes for `rdp` protocol pattern
let slice = build_slice!(input, input_len as usize);
// Some sessions immediately (first byte) switch to TLS/SSL, e.g.
// https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=view&target=rdp-ssl.pcap.gz
// but this callback will not be exercised, so `probe_tls_handshake` not needed here.
if probe_rdp(slice) {
return unsafe { ALPROTO_RDP };
}
}
return ALPROTO_UNKNOWN;
}
/// probe for TLS
fn probe_tls_handshake(input: &[u8]) -> bool {
input.len() > 0 && input[0] == u8::from(TlsRecordType::Handshake)
}
//
// parse
//
#[no_mangle]
pub extern "C" fn rs_rdp_parse_ts(
_flow: *const Flow,
state: *mut std::os::raw::c_void,
_pstate: *mut std::os::raw::c_void,
input: *const u8,
input_len: u32,
_data: *const std::os::raw::c_void,
_flags: u8,
) -> i32 {
let state = cast_pointer!(state, RdpState);
let buf = build_slice!(input, input_len as usize);
// attempt to parse bytes as `rdp` protocol
if state.parse_ts(buf) {
return 1;
}
// no need for further parsing
return -1;
}
#[no_mangle]
pub extern "C" fn rs_rdp_parse_tc(
_flow: *const Flow,
state: *mut std::os::raw::c_void,
_pstate: *mut std::os::raw::c_void,
input: *const u8,
input_len: u32,
_data: *const std::os::raw::c_void,
_flags: u8,
) -> i32 {
let state = cast_pointer!(state, RdpState);
let buf = build_slice!(input, input_len as usize);
// attempt to parse bytes as `rdp` protocol
if state.parse_tc(buf) {
return 1;
}
// no need for further parsing
return -1;
}
//
// registration
//
const PARSER_NAME: &'static [u8] = b"rdp\0";
#[no_mangle]
pub unsafe extern "C" fn rs_rdp_register_parser() {
let default_port = std::ffi::CString::new("[3389]").unwrap();
let parser = RustParser {
name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
default_port: default_port.as_ptr(),
ipproto: IPPROTO_TCP,
probe_ts: rs_rdp_probe_ts_tc,
probe_tc: rs_rdp_probe_ts_tc,
min_depth: 0,
max_depth: 16,
state_new: rs_rdp_state_new,
state_free: rs_rdp_state_free,
tx_free: rs_rdp_state_tx_free,
parse_ts: rs_rdp_parse_ts,
parse_tc: rs_rdp_parse_tc,
get_tx_count: rs_rdp_state_get_tx_count,
get_tx: rs_rdp_state_get_tx,
tx_get_comp_st: rs_rdp_tx_get_progress_complete,
tx_get_progress: rs_rdp_tx_get_progress,
get_tx_logged: None,
set_tx_logged: None,
get_de_state: rs_rdp_tx_get_detect_state,
set_de_state: rs_rdp_tx_set_detect_state,
get_events: None,
get_eventinfo: None,
get_eventinfo_byid: None,
localstorage_new: None,
localstorage_free: None,
get_tx_mpm_id: None,
set_tx_mpm_id: None,
get_files: None,
get_tx_iterator: None,
};
let ip_proto_str = std::ffi::CString::new("tcp").unwrap();
if AppLayerProtoDetectConfProtoDetectionEnabled(
ip_proto_str.as_ptr(),
parser.name,
) != 0
{
let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
ALPROTO_RDP = alproto;
if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name)
!= 0
{
let _ = AppLayerRegisterParser(&parser, alproto);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rdp::parser::{RdpCookie, X224ConnectionRequest};
#[test]
fn test_probe_rdp() {
let buf: &[u8] = &[0x03, 0x00];
assert_eq!(true, probe_rdp(&buf));
}
#[test]
fn test_probe_rdp_other() {
let buf: &[u8] = &[0x04, 0x00];
assert_eq!(false, probe_rdp(&buf));
}
#[test]
fn test_probe_tls_handshake() {
let buf: &[u8] = &[0x16, 0x00];
assert_eq!(true, probe_tls_handshake(&buf));
}
#[test]
fn test_probe_tls_handshake_other() {
let buf: &[u8] = &[0x17, 0x00];
assert_eq!(false, probe_tls_handshake(&buf));
}
#[test]
fn test_parse_ts_rdp() {
let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00];
let buf_2: &[u8] = &[
0x00, 0x00, 0x00, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x20,
0x6d, 0x73, 0x74, 0x73, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x75, 0x73,
0x65, 0x72, 0x31, 0x32, 0x33, 0x0d, 0x0a,
];
let mut state = RdpState::new();
assert_eq!(true, state.parse_ts(&buf_1));
assert_eq!(0, state.transactions.len());
assert_eq!(true, state.parse_ts(&buf_2));
assert_eq!(1, state.transactions.len());
let item =
RdpTransactionItem::X224ConnectionRequest(X224ConnectionRequest {
cdt: 0,
dst_ref: 0,
src_ref: 0,
class: 0,
options: 0,
cookie: Some(RdpCookie {
mstshash: String::from("user123"),
}),
negotiation_request: None,
data: Vec::new(),
});
assert_eq!(item, state.transactions[0].item);
}
#[test]
fn test_parse_ts_other() {
let buf: &[u8] = &[0x03, 0x00, 0x00, 0x01, 0x00];
let mut state = RdpState::new();
assert_eq!(false, state.parse_ts(&buf));
}
#[test]
fn test_parse_tc_rdp() {
let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x09, 0x02];
let buf_2: &[u8] = &[0xf0, 0x80, 0x7f, 0x66];
let mut state = RdpState::new();
assert_eq!(true, state.parse_tc(&buf_1));
assert_eq!(0, state.transactions.len());
assert_eq!(true, state.parse_tc(&buf_2));
assert_eq!(1, state.transactions.len());
let item =
RdpTransactionItem::McsConnectResponse(McsConnectResponse {});
assert_eq!(item, state.transactions[0].item);
}
#[test]
fn test_parse_tc_other() {
let buf: &[u8] = &[0x03, 0x00, 0x00, 0x01, 0x00];
let mut state = RdpState::new();
assert_eq!(false, state.parse_tc(&buf));
}
#[test]
fn test_state_new_tx() {
let mut state = RdpState::new();
let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let tx0 = state.new_tx(item0);
let tx1 = state.new_tx(item1);
assert_eq!(2, state.next_id);
state.transactions.push(tx0);
state.transactions.push(tx1);
assert_eq!(2, state.transactions.len());
assert_eq!(0, state.transactions[0].id);
assert_eq!(1, state.transactions[1].id);
assert_eq!(false, state.tls_parsing);
assert_eq!(false, state.bypass_parsing);
}
#[test]
fn test_state_get_tx() {
let mut state = RdpState::new();
let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let item2 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let tx0 = state.new_tx(item0);
let tx1 = state.new_tx(item1);
let tx2 = state.new_tx(item2);
state.transactions.push(tx0);
state.transactions.push(tx1);
state.transactions.push(tx2);
assert_eq!(Some(&state.transactions[1]), state.get_tx(1));
}
#[test]
fn test_state_free_tx() {
let mut state = RdpState::new();
let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let item2 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
children: Vec::new(),
});
let tx0 = state.new_tx(item0);
let tx1 = state.new_tx(item1);
let tx2 = state.new_tx(item2);
state.transactions.push(tx0);
state.transactions.push(tx1);
state.transactions.push(tx2);
state.free_tx(1);
assert_eq!(3, state.next_id);
assert_eq!(2, state.transactions.len());
assert_eq!(0, state.transactions[0].id);
assert_eq!(2, state.transactions[1].id);
assert_eq!(None, state.get_tx(1));
}
}

@ -0,0 +1,187 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
// Author: Zach Kelly <zach.kelly@lmco.com>
use byteorder::ReadBytesExt;
use memchr::memchr;
use nom;
use nom::{ErrorKind, IResult, Needed};
use rdp::error::RDP_UNIMPLEMENTED_LENGTH_DETERMINANT;
use std::io::Cursor;
use widestring::U16CString;
/// converts a raw u8 slice of little-endian wide chars into a String
pub fn le_slice_to_string(input: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
let mut vec = Vec::new();
let mut cursor = Cursor::new(input);
loop {
match cursor.read_u16::<byteorder::LittleEndian>() {
Ok(x) => {
if x == 0 {
break;
};
vec.push(x)
}
Err(_) => break,
}
}
match U16CString::new(vec) {
Ok(x) => match x.to_string() {
Ok(x) => Ok(x),
Err(e) => Err(e.into()),
},
Err(e) => Err(e.into()),
}
}
/// converts a raw u8 slice of null-padded utf7 chars into a String, dropping the nulls
pub fn utf7_slice_to_string(input: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
let s = match memchr(b'\0', input) {
Some(end) => &input[..end],
None => &input[..],
};
match std::str::from_utf8(s) {
Ok(s) => Ok(String::from(s)),
Err(e) => Err(e.into()),
}
}
/// parses a PER length determinant, to determine the length of the data following
/// x.691-spec: section 10.9
pub fn parse_per_length_determinant(input: &[u8]) -> IResult<&[u8], u32> {
if input.is_empty() {
// need a single byte to begin length determination
Err(nom::Err::Incomplete(Needed::Size(1)))
} else {
let bit7 = input[0] >> 7;
match bit7 {
0b0 => {
// byte starts with 0b0. Length stored in the lower 7 bits of the current byte
let length = input[0] as u32 & 0x7f;
Ok((&input[1..], length))
}
_ => {
let bit6 = input[0] >> 6 & 0x1;
match bit6 {
0b0 => {
// byte starts with 0b10. Length stored in the remaining 6 bits and the next byte
if input.len() < 2 {
Err(nom::Err::Incomplete(Needed::Size(2)))
} else {
let length = ((input[0] as u32 & 0x3f) << 8) | input[1] as u32;
Ok((&input[2..], length))
}
}
_ => {
// byte starts with 0b11. Without an example to confirm 16K+ lengths are properly
// handled, leaving this branch unimplemented
Err(nom::Err::Error(error_position!(
input,
ErrorKind::Custom(RDP_UNIMPLEMENTED_LENGTH_DETERMINANT)
)))
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use nom;
use rdp::error::RDP_UNIMPLEMENTED_LENGTH_DETERMINANT;
#[test]
fn test_le_string_abc() {
let abc = &[0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(String::from("ABC"), le_slice_to_string(abc).unwrap());
}
#[test]
fn test_le_string_empty() {
let empty = &[];
assert_eq!(String::from(""), le_slice_to_string(empty).unwrap());
}
#[test]
fn test_le_string_invalid() {
let not_utf16le = &[0x00, 0xd8, 0x01, 0x00];
assert!(le_slice_to_string(not_utf16le).is_err());
}
#[test]
fn test_utf7_string_abc() {
let abc = &[0x41, 0x42, 0x43, 0x00, 0x00];
assert_eq!(String::from("ABC"), utf7_slice_to_string(abc).unwrap());
}
#[test]
fn test_utf7_string_empty() {
let empty = &[];
assert_eq!(String::from(""), utf7_slice_to_string(empty).unwrap());
}
#[test]
fn test_utf7_string_invalid() {
let not_utf7 = &[0x80];
assert!(utf7_slice_to_string(not_utf7).is_err());
}
#[test]
fn test_length_single_length() {
let bytes = &[0x28];
assert_eq!(Ok((&[][..], 0x28)), parse_per_length_determinant(bytes));
}
#[test]
fn test_length_double_length() {
let bytes = &[0x81, 0x28];
assert_eq!(Ok((&[][..], 0x128)), parse_per_length_determinant(bytes));
}
#[test]
fn test_length_single_length_incomplete() {
let bytes = &[];
assert_eq!(
Err(nom::Err::Incomplete(nom::Needed::Size(1))),
parse_per_length_determinant(bytes)
)
}
#[test]
fn test_length_16k_unimplemented() {
let bytes = &[0xc0];
assert_eq!(
Err(nom::Err::Error(error_position!(
&bytes[..],
ErrorKind::Custom(RDP_UNIMPLEMENTED_LENGTH_DETERMINANT)
))),
parse_per_length_determinant(bytes)
)
}
#[test]
fn test_length_double_length_incomplete() {
let bytes = &[0x81];
assert_eq!(
Err(nom::Err::Incomplete(nom::Needed::Size(2))),
parse_per_length_determinant(bytes)
)
}
}

@ -0,0 +1,660 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
// Author: Zach Kelly <zach.kelly@lmco.com>
/// converts a locale identifier into a locale name
/// <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f>
pub fn lcid_to_string<'a>(lcid: u32, default: &'a str) -> String {
let s = match lcid {
0x0001 => "ar",
0x0002 => "bg",
0x0003 => "ca",
0x0004 => "zh-Hans",
0x0005 => "cs",
0x0006 => "da",
0x0007 => "de",
0x0008 => "el",
0x0009 => "en",
0x000A => "es",
0x000B => "fi",
0x000C => "fr",
0x000D => "he",
0x000E => "hu",
0x000F => "is",
0x0010 => "it",
0x0011 => "ja",
0x0012 => "ko",
0x0013 => "nl",
0x0014 => "no",
0x0015 => "pl",
0x0016 => "pt",
0x0017 => "rm",
0x0018 => "ro",
0x0019 => "ru",
0x001A => "hr",
0x001B => "sk",
0x001C => "sq",
0x001D => "sv",
0x001E => "th",
0x001F => "tr",
0x0020 => "ur",
0x0021 => "id",
0x0022 => "uk",
0x0023 => "be",
0x0024 => "sl",
0x0025 => "et",
0x0026 => "lv",
0x0027 => "lt",
0x0028 => "tg",
0x0029 => "fa",
0x002A => "vi",
0x002B => "hy",
0x002C => "az",
0x002D => "eu",
0x002E => "hsb",
0x002F => "mk",
0x0030 => "st",
0x0031 => "ts",
0x0032 => "tn",
0x0033 => "ve",
0x0034 => "xh",
0x0035 => "zu",
0x0036 => "af",
0x0037 => "ka",
0x0038 => "fo",
0x0039 => "hi",
0x003A => "mt",
0x003B => "se",
0x003C => "ga",
0x003D => "yi",
0x003E => "ms",
0x003F => "kk",
0x0040 => "ky",
0x0041 => "sw",
0x0042 => "tk",
0x0043 => "uz",
0x0044 => "tt",
0x0045 => "bn",
0x0046 => "pa",
0x0047 => "gu",
0x0048 => "or",
0x0049 => "ta",
0x004A => "te",
0x004B => "kn",
0x004C => "ml",
0x004D => "as",
0x004E => "mr",
0x004F => "sa",
0x0050 => "mn",
0x0051 => "bo",
0x0052 => "cy",
0x0053 => "km",
0x0054 => "lo",
0x0055 => "my",
0x0056 => "gl",
0x0057 => "kok",
0x0058 => "mni",
0x0059 => "sd",
0x005A => "syr",
0x005B => "si",
0x005C => "chr",
0x005D => "iu",
0x005E => "am",
0x005F => "tzm",
0x0060 => "ks",
0x0061 => "ne",
0x0062 => "fy",
0x0063 => "ps",
0x0064 => "fil",
0x0065 => "dv",
0x0066 => "bin",
0x0067 => "ff",
0x0068 => "ha",
0x0069 => "ibb",
0x006A => "yo",
0x006B => "quz",
0x006C => "nso",
0x006D => "ba",
0x006E => "lb",
0x006F => "kl",
0x0070 => "ig",
0x0071 => "kr",
0x0072 => "om",
0x0073 => "ti",
0x0074 => "gn",
0x0075 => "haw",
0x0076 => "la",
0x0077 => "so",
0x0078 => "ii",
0x0079 => "pap",
0x007A => "arn",
0x007C => "moh",
0x007E => "br",
0x0080 => "ug",
0x0081 => "mi",
0x0082 => "oc",
0x0083 => "co",
0x0084 => "gsw",
0x0085 => "sah",
0x0086 => "qut",
0x0087 => "rw",
0x0088 => "wo",
0x008C => "prs",
0x0091 => "gd",
0x0092 => "ku",
0x0093 => "quc",
0x0401 => "ar-SA",
0x0402 => "bg-BG",
0x0403 => "ca-ES",
0x0404 => "zh-TW",
0x0405 => "cs-CZ",
0x0406 => "da-DK",
0x0407 => "de-DE",
0x0408 => "el-GR",
0x0409 => "en-US",
0x040A => "es-ES_tradnl",
0x040B => "fi-FI",
0x040C => "fr-FR",
0x040D => "he-IL",
0x040E => "hu-HU",
0x040F => "is-IS",
0x0410 => "it-IT",
0x0411 => "ja-JP",
0x0412 => "ko-KR",
0x0413 => "nl-NL",
0x0414 => "nb-NO",
0x0415 => "pl-PL",
0x0416 => "pt-BR",
0x0417 => "rm-CH",
0x0418 => "ro-RO",
0x0419 => "ru-RU",
0x041A => "hr-HR",
0x041B => "sk-SK",
0x041C => "sq-AL",
0x041D => "sv-SE",
0x041E => "th-TH",
0x041F => "tr-TR",
0x0420 => "ur-PK",
0x0421 => "id-ID",
0x0422 => "uk-UA",
0x0423 => "be-BY",
0x0424 => "sl-SI",
0x0425 => "et-EE",
0x0426 => "lv-LV",
0x0427 => "lt-LT",
0x0428 => "tg-Cyrl-TJ",
0x0429 => "fa-IR",
0x042A => "vi-VN",
0x042B => "hy-AM",
0x042C => "az-Latn-AZ",
0x042D => "eu-ES",
0x042E => "hsb-DE",
0x042F => "mk-MK",
0x0430 => "st-ZA",
0x0431 => "ts-ZA",
0x0432 => "tn-ZA",
0x0433 => "ve-ZA",
0x0434 => "xh-ZA",
0x0435 => "zu-ZA",
0x0436 => "af-ZA",
0x0437 => "ka-GE",
0x0438 => "fo-FO",
0x0439 => "hi-IN",
0x043A => "mt-MT",
0x043B => "se-NO",
0x043D => "yi-Hebr",
0x043E => "ms-MY",
0x043F => "kk-KZ",
0x0440 => "ky-KG",
0x0441 => "sw-KE",
0x0442 => "tk-TM",
0x0443 => "uz-Latn-UZ",
0x0444 => "tt-RU",
0x0445 => "bn-IN",
0x0446 => "pa-IN",
0x0447 => "gu-IN",
0x0448 => "or-IN",
0x0449 => "ta-IN",
0x044A => "te-IN",
0x044B => "kn-IN",
0x044C => "ml-IN",
0x044D => "as-IN",
0x044E => "mr-IN",
0x044F => "sa-IN",
0x0450 => "mn-MN",
0x0451 => "bo-CN",
0x0452 => "cy-GB",
0x0453 => "km-KH",
0x0454 => "lo-LA",
0x0455 => "my-MM",
0x0456 => "gl-ES",
0x0457 => "kok-IN",
0x0458 => "mni-IN",
0x0459 => "sd-Deva-IN",
0x045A => "syr-SY",
0x045B => "si-LK",
0x045C => "chr-Cher-US",
0x045D => "iu-Cans-CA",
0x045E => "am-ET",
0x045F => "tzm-Arab-MA",
0x0460 => "ks-Arab",
0x0461 => "ne-NP",
0x0462 => "fy-NL",
0x0463 => "ps-AF",
0x0464 => "fil-PH",
0x0465 => "dv-MV",
0x0466 => "bin-NG",
0x0467 => "fuv-NG",
0x0468 => "ha-Latn-NG",
0x0469 => "ibb-NG",
0x046A => "yo-NG",
0x046B => "quz-BO",
0x046C => "nso-ZA",
0x046D => "ba-RU",
0x046E => "lb-LU",
0x046F => "kl-GL",
0x0470 => "ig-NG",
0x0471 => "kr-NG",
0x0472 => "om-ET",
0x0473 => "ti-ET",
0x0474 => "gn-PY",
0x0475 => "haw-US",
0x0476 => "la-Latn",
0x0477 => "so-SO",
0x0478 => "ii-CN",
0x0479 => "pap-029",
0x047A => "arn-CL",
0x047C => "moh-CA",
0x047E => "br-FR",
0x0480 => "ug-CN",
0x0481 => "mi-NZ",
0x0482 => "oc-FR",
0x0483 => "co-FR",
0x0484 => "gsw-FR",
0x0485 => "sah-RU",
0x0486 => "qut-GT",
0x0487 => "rw-RW",
0x0488 => "wo-SN",
0x048C => "prs-AF",
0x048D => "plt-MG",
0x048E => "zh-yue-HK",
0x048F => "tdd-Tale-CN",
0x0490 => "khb-Talu-CN",
0x0491 => "gd-GB",
0x0492 => "ku-Arab-IQ",
0x0493 => "quc-CO",
0x0501 => "qps-ploc",
0x05FE => "qps-ploca",
0x0801 => "ar-IQ",
0x0803 => "ca-ES-valencia",
0x0804 => "zh-CN",
0x0807 => "de-CH",
0x0809 => "en-GB",
0x080A => "es-MX",
0x080C => "fr-BE",
0x0810 => "it-CH",
0x0811 => "ja-Ploc-JP",
0x0813 => "nl-BE",
0x0814 => "nn-NO",
0x0816 => "pt-PT",
0x0818 => "ro-MD",
0x0819 => "ru-MD",
0x081A => "sr-Latn-CS",
0x081D => "sv-FI",
0x0820 => "ur-IN",
0x082C => "az-Cyrl-AZ",
0x082E => "dsb-DE",
0x0832 => "tn-BW",
0x083B => "se-SE",
0x083C => "ga-IE",
0x083E => "ms-BN",
0x0843 => "uz-Cyrl-UZ",
0x0845 => "bn-BD",
0x0846 => "pa-Arab-PK",
0x0849 => "ta-LK",
0x0850 => "mn-Mong-CN",
0x0851 => "bo-BT",
0x0859 => "sd-Arab-PK",
0x085D => "iu-Latn-CA",
0x085F => "tzm-Latn-DZ",
0x0860 => "ks-Deva",
0x0861 => "ne-IN",
0x0867 => "ff-Latn-SN",
0x086B => "quz-EC",
0x0873 => "ti-ER",
0x09FF => "qps-plocm",
0x0C01 => "ar-EG",
0x0C04 => "zh-HK",
0x0C07 => "de-AT",
0x0C09 => "en-AU",
0x0C0A => "es-ES",
0x0C0C => "fr-CA",
0x0C1A => "sr-Cyrl-CS",
0x0C3B => "se-FI",
0x0C50 => "mn-Mong-MN",
0x0C51 => "dz-BT",
0x0C5F => "tmz-MA",
0x0C6b => "quz-PE",
0x1001 => "ar-LY",
0x1004 => "zh-SG",
0x1007 => "de-LU",
0x1009 => "en-CA",
0x100A => "es-GT",
0x100C => "fr-CH",
0x101A => "hr-BA",
0x103B => "smj-NO",
0x105F => "tzm-Tfng-MA",
0x1401 => "ar-DZ",
0x1404 => "zh-MO",
0x1407 => "de-LI",
0x1409 => "en-NZ",
0x140A => "es-CR",
0x140C => "fr-LU",
0x141A => "bs-Latn-BA",
0x143B => "smj-SE",
0x1801 => "ar-MA",
0x1809 => "en-IE",
0x180A => "es-PA",
0x180C => "fr-MC",
0x181A => "sr-Latn-BA",
0x183B => "sma-NO",
0x1C01 => "ar-TN",
0x1C09 => "en-ZA",
0x1C0A => "es-DO",
0x1C1A => "sr-Cyrl-BA",
0x1C3B => "sma-SE",
0x2001 => "ar-OM",
0x2009 => "en-JM",
0x200A => "es-VE",
0x200C => "fr-RE",
0x201A => "bs-Cyrl-BA",
0x203B => "sms-FI",
0x2401 => "ar-YE",
0x2409 => "en-029",
0x240A => "es-CO",
0x240C => "fr-CD",
0x241A => "sr-Latn-RS",
0x243B => "smn-FI",
0x2801 => "ar-SY",
0x2809 => "en-BZ",
0x280A => "es-PE",
0x280C => "fr-SN",
0x281A => "sr-Cyrl-RS",
0x2C01 => "ar-JO",
0x2C09 => "en-TT",
0x2C0A => "es-AR",
0x2C0C => "fr-CM",
0x2C1A => "sr-Latn-ME",
0x3001 => "ar-LB",
0x3009 => "en-ZW",
0x300A => "es-EC",
0x300C => "fr-CI",
0x301A => "sr-Cyrl-ME",
0x3401 => "ar-KW",
0x3409 => "en-PH",
0x340A => "es-CL",
0x340C => "fr-ML",
0x3801 => "ar-AE",
0x3809 => "en-ID",
0x380A => "es-UY",
0x380C => "fr-MA",
0x3c01 => "ar-BH",
0x3c09 => "en-HK",
0x3c0A => "es-PY",
0x3c0C => "fr-HT",
0x4001 => "ar-QA",
0x4009 => "en-IN",
0x400A => "es-BO",
0x4401 => "ar-Ploc-SA",
0x4409 => "en-MY",
0x440A => "es-SV",
0x4801 => "ar-145",
0x4809 => "en-SG",
0x480A => "es-HN",
0x4C09 => "en-AE",
0x4C0A => "es-NI",
0x5009 => "en-BH",
0x500A => "es-PR",
0x5409 => "en-EG",
0x540A => "es-US",
0x5809 => "en-JO",
0x580A => "es-419",
0x5C09 => "en-KW",
0x5C0A => "es-CU",
0x6009 => "en-TR",
0x6409 => "en-YE",
0x641A => "bs-Cyrl",
0x681A => "bs-Latn",
0x6C1A => "sr-Cyrl",
0x701A => "sr-Latn",
0x703B => "smn",
0x742C => "az-Cyrl",
0x743B => "sms",
0x7804 => "zh",
0x7814 => "nn",
0x781A => "bs",
0x782C => "az-Latn",
0x783B => "sma",
0x7843 => "uz-Cyrl",
0x7850 => "mn-Cyrl",
0x785D => "iu-Cans",
0x785F => "tzm-Tfng",
0x7C04 => "zh-Hant",
0x7C14 => "nb",
0x7C1A => "sr",
0x7C28 => "tg-Cyrl",
0x7C2E => "dsb",
0x7C3B => "smj",
0x7C43 => "uz-Latn",
0x7C46 => "pa-Arab",
0x7C50 => "mn-Mong",
0x7C59 => "sd-Arab",
0x7C5C => "chr-Cher",
0x7C5D => "iu-Latn",
0x7C5F => "tzm-Latn",
0x7C67 => "ff-Latn",
0x7C68 => "ha-Latn",
0x7C92 => "ku-Arab",
_ => default,
};
String::from(s)
}
/// Windows operating system type (build and suffix/pack)
#[derive(Clone, Debug, PartialEq)]
pub struct OperatingSystem {
pub build: Build,
pub suffix: Suffix,
}
// <https://en.wikipedia.org/wiki/Windows_NT#Releases>
#[derive(Clone, Debug, FromPrimitive, PartialEq)]
#[allow(non_camel_case_types)]
pub enum Build {
Other,
Win31 = 528,
Win35 = 807,
Win351 = 1057,
Win40 = 1381,
Win2000 = 2195,
WinXP = 2600,
Vista_6000 = 6000,
Vista_6001 = 6001,
Vista_6002 = 6002,
Win7_7600 = 7600,
Win7_7601 = 7601,
Win8 = 9200,
Win81 = 9600,
Win10_10240 = 10240,
Win10_10586 = 10586,
Win10_14393 = 14393,
Win10_15063 = 15063,
Win10_16299 = 16299,
Win10_17134 = 17134,
Win10_17763 = 17763,
Server2003 = 3790,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Suffix {
Empty,
Rtm,
Sp1,
Sp2,
Th1,
Th2,
Rs1,
Rs2,
Rs3,
Rs4,
Rs5,
}
/// convert a build number into an OperatingSystem type
pub fn build_number_to_os(number: u32) -> OperatingSystem {
let build = match num::FromPrimitive::from_u32(number) {
Some(x) => x,
None => Build::Other,
};
let suffix = match number {
6000 => Suffix::Rtm,
7600 => Suffix::Rtm,
6001 => Suffix::Sp1,
6002 => Suffix::Sp2,
7601 => Suffix::Sp1,
10240 => Suffix::Th1,
10586 => Suffix::Th2,
14393 => Suffix::Rs1,
15063 => Suffix::Rs2,
16299 => Suffix::Rs3,
17134 => Suffix::Rs4,
17763 => Suffix::Rs5,
_ => Suffix::Empty,
};
OperatingSystem { build, suffix }
}
/// convert an OperatingSystem into a string description
pub fn os_to_string<'a>(os: &OperatingSystem, default: &'a str) -> String {
let s = match os.build {
Build::Win31 => "Windows NT 3.1",
Build::Win35 => "Windows NT 3.5",
Build::Win351 => "Windows NT 3.51",
Build::Win40 => "Windows NT 4.0",
Build::Win2000 => "Windows 2000",
Build::WinXP => "Windows XP",
Build::Vista_6000 => "Windows Vista",
Build::Vista_6001 => "Windows Vista",
Build::Vista_6002 => "Windows Vista",
Build::Win7_7600 => "Windows 7",
Build::Win7_7601 => "Windows 7",
Build::Win8 => "Windows 8",
Build::Win81 => "Windows 8.1",
Build::Win10_10240 => "Windows 10",
Build::Win10_10586 => "Windows 10",
Build::Win10_14393 => "Windows 10",
Build::Win10_15063 => "Windows 10",
Build::Win10_16299 => "Windows 10",
Build::Win10_17134 => "Windows 10",
Build::Win10_17763 => "Windows 10",
Build::Server2003 => "Windows Server 2003",
Build::Other => default,
};
let mut result = String::from(s);
match os.suffix {
Suffix::Rtm => result.push_str(" RTM"),
Suffix::Sp1 => result.push_str(" SP1"),
Suffix::Sp2 => result.push_str(" SP2"),
Suffix::Th1 => result.push_str(" TH1"),
Suffix::Th2 => result.push_str(" TH2"),
Suffix::Rs1 => result.push_str(" RS1"),
Suffix::Rs2 => result.push_str(" RS2"),
Suffix::Rs3 => result.push_str(" RS3"),
Suffix::Rs4 => result.push_str(" RS4"),
Suffix::Rs5 => result.push_str(" RS5"),
Suffix::Empty => (),
};
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lcid_string_en() {
let default = "default-lcid-name";
assert_eq!("en-US", lcid_to_string(0x409, default));
}
#[test]
fn test_lcid_string_default() {
let default = "default-lcid-name";
assert_eq!(default, lcid_to_string(0xffff, default));
}
#[test]
fn test_build_os_win10() {
let w10_rs5 = OperatingSystem {
build: Build::Win10_17763,
suffix: Suffix::Rs5,
};
assert_eq!(w10_rs5, build_number_to_os(17763));
}
#[test]
fn test_build_os_other() {
let other = OperatingSystem {
build: Build::Other,
suffix: Suffix::Empty,
};
assert_eq!(other, build_number_to_os(1));
}
#[test]
fn test_os_string_win7_sp1() {
let w7_sp1 = "Windows 7 SP1";
let default = "default-os-name";
let w7_os = OperatingSystem {
build: Build::Win7_7601,
suffix: Suffix::Sp1,
};
assert_eq!(w7_sp1, os_to_string(&w7_os, default));
}
#[test]
fn test_os_string_win81() {
let w81 = "Windows 8.1";
let default = "default-os-name";
let w81_os = OperatingSystem {
build: Build::Win81,
suffix: Suffix::Empty,
};
assert_eq!(w81, os_to_string(&w81_os, default));
}
#[test]
fn test_os_string_default() {
let default = "default-os-name";
let other_os = OperatingSystem {
build: Build::Other,
suffix: Suffix::Empty,
};
assert_eq!(default, os_to_string(&other_os, default));
}
}

@ -51,6 +51,7 @@ app-layer-krb5.c app-layer-krb5.h \
app-layer-dhcp.c app-layer-dhcp.h \
app-layer-template.c app-layer-template.h \
app-layer-template-rust.c app-layer-template-rust.h \
app-layer-rdp.c app-layer-rdp.h \
app-layer-ssh.c app-layer-ssh.h \
app-layer-ssl.c app-layer-ssl.h \
conf.c conf.h \
@ -342,6 +343,7 @@ output-json-dhcp.c output-json-dhcp.h \
output-json-snmp.c output-json-snmp.h \
output-json-template.c output-json-template.h \
output-json-template-rust.c output-json-template-rust.h \
output-json-rdp.c output-json-rdp.h \
output-json-metadata.c output-json-metadata.h \
output-lua.c output-lua.h \
output-packet.c output-packet.h \

@ -69,6 +69,7 @@
#include "app-layer-snmp.h"
#include "app-layer-template.h"
#include "app-layer-template-rust.h"
#include "app-layer-rdp.h"
#include "conf.h"
#include "util-spm.h"
@ -1508,6 +1509,7 @@ void AppLayerParserRegisterProtocolParsers(void)
RegisterSNMPParsers();
RegisterTemplateRustParsers();
RegisterTemplateParsers();
RegisterRdpParsers();
/** IMAP */
AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, "imap");

@ -108,6 +108,9 @@ const char *AppProtoToString(AppProto alproto)
case ALPROTO_TEMPLATE_RUST:
proto_name = "template-rust";
break;
case ALPROTO_RDP:
proto_name = "rdp";
break;
case ALPROTO_FAILED:
proto_name = "failed";
break;
@ -149,6 +152,7 @@ AppProto StringToAppProto(const char *proto_name)
if (strcmp(proto_name,"snmp")==0) return ALPROTO_SNMP;
if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE;
if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST;
if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP;
if (strcmp(proto_name,"failed")==0) return ALPROTO_FAILED;
return ALPROTO_UNKNOWN;

@ -53,6 +53,7 @@ enum AppProtoEnum {
ALPROTO_SNMP,
ALPROTO_TEMPLATE,
ALPROTO_TEMPLATE_RUST,
ALPROTO_RDP,
/* used by the probing parser when alproto detection fails
* permanently for that particular stream */

@ -0,0 +1,42 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Zach Kelly <zach.kelly@lmco.com>
*
* Application layer parser for RDP
*/
#include "suricata-common.h"
#include "stream.h"
#include "conf.h"
#include "util-unittest.h"
#include "app-layer-detect-proto.h"
#include "app-layer-parser.h"
#include "app-layer-rdp.h"
#include "rust-rdp-rdp-gen.h"
void RegisterRdpParsers(void) {
/* only register if enabled in config */
if (ConfGetNode("app-layer.protocols.rdp") == NULL) {
return;
}
SCLogDebug("Registering rdp parser");
rs_rdp_register_parser();
}

@ -0,0 +1,30 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Zach Kelly <zach.kelly@lmco.com>
*/
#ifndef __APP_LAYER_RDP_H__
#define __APP_LAYER_RDP_H__
void RegisterRdpParsers(void);
void RdpParserRegisterTests(void);
#endif /* __APP_LAYER_RDP_H__ */

@ -0,0 +1,180 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Zach Kelly <zach.kelly@lmco.com>
*
* Application layer logger for RDP
*/
#include "suricata-common.h"
#include "debug.h"
#include "detect.h"
#include "pkt-var.h"
#include "conf.h"
#include "threads.h"
#include "threadvars.h"
#include "tm-threads.h"
#include "util-unittest.h"
#include "util-buffer.h"
#include "util-debug.h"
#include "util-byte.h"
#include "output.h"
#include "output-json.h"
#include "app-layer.h"
#include "app-layer-parser.h"
#include "app-layer-rdp.h"
#include "output-json-rdp.h"
#include "rust-rdp-log-gen.h"
typedef struct LogRdpFileCtx_ {
LogFileCtx *file_ctx;
uint32_t flags;
} LogRdpFileCtx;
typedef struct LogRdpLogThread_ {
LogRdpFileCtx *rdplog_ctx;
uint32_t count;
MemBuffer *buffer;
} LogRdpLogThread;
static int JsonRdpLogger(ThreadVars *tv, void *thread_data,
const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
{
LogRdpLogThread *thread = thread_data;
json_t *js = CreateJSONHeader(p, LOG_DIR_PACKET, "rdp");
if (unlikely(js == NULL)) {
return TM_ECODE_FAILED;
}
json_t *rdp_js = rs_rdp_to_json(tx);
if (unlikely(rdp_js == NULL)) {
goto error;
}
json_object_set_new(js, "rdp", rdp_js);
MemBufferReset(thread->buffer);
OutputJSONBuffer(js, thread->rdplog_ctx->file_ctx, &thread->buffer);
json_decref(js);
return TM_ECODE_OK;
error:
json_decref(js);
return TM_ECODE_FAILED;
}
static void OutputRdpLogDeInitCtxSub(OutputCtx *output_ctx)
{
LogRdpFileCtx *rdplog_ctx = (LogRdpFileCtx *)output_ctx->data;
SCFree(rdplog_ctx);
SCFree(output_ctx);
}
static OutputInitResult OutputRdpLogInitSub(ConfNode *conf,
OutputCtx *parent_ctx)
{
OutputInitResult result = { NULL, false };
OutputJsonCtx *ajt = parent_ctx->data;
LogRdpFileCtx *rdplog_ctx = SCCalloc(1, sizeof(*rdplog_ctx));
if (unlikely(rdplog_ctx == NULL)) {
return result;
}
rdplog_ctx->file_ctx = ajt->file_ctx;
OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
if (unlikely(output_ctx == NULL)) {
SCFree(rdplog_ctx);
return result;
}
output_ctx->data = rdplog_ctx;
output_ctx->DeInit = OutputRdpLogDeInitCtxSub;
AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_RDP);
SCLogDebug("rdp log sub-module initialized.");
result.ctx = output_ctx;
result.ok = true;
return result;
}
#define OUTPUT_BUFFER_SIZE 65535
static TmEcode JsonRdpLogThreadInit(ThreadVars *t, const void *initdata, void **data)
{
LogRdpLogThread *thread = SCCalloc(1, sizeof(*thread));
if (unlikely(thread == NULL)) {
return TM_ECODE_FAILED;
}
if (initdata == NULL) {
SCLogDebug("Error getting context for EveLogRdp. \"initdata\" is NULL.");
SCFree(thread);
return TM_ECODE_FAILED;
}
thread->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
if (unlikely(thread->buffer == NULL)) {
SCFree(thread);
return TM_ECODE_FAILED;
}
thread->rdplog_ctx = ((OutputCtx *)initdata)->data;
*data = (void *)thread;
return TM_ECODE_OK;
}
static TmEcode JsonRdpLogThreadDeinit(ThreadVars *t, void *data)
{
LogRdpLogThread *thread = (LogRdpLogThread *)data;
if (thread == NULL) {
return TM_ECODE_OK;
}
if (thread->buffer != NULL) {
MemBufferFree(thread->buffer);
}
SCFree(thread);
return TM_ECODE_OK;
}
void JsonRdpLogRegister(void)
{
if (ConfGetNode("app-layer.protocols.rdp") == NULL) {
return;
}
/* Register as an eve sub-module. */
OutputRegisterTxSubModule(
LOGGER_JSON_RDP,
"eve-log",
"JsonRdpLog",
"eve-log.rdp",
OutputRdpLogInitSub,
ALPROTO_RDP,
JsonRdpLogger,
JsonRdpLogThreadInit,
JsonRdpLogThreadDeinit,
NULL
);
SCLogDebug("rdp json logger registered.");
}

@ -0,0 +1,29 @@
/* Copyright (C) 2019 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*
* \author Zach Kelly <zach.kelly@lmco.com>
*/
#ifndef __OUTPUT_JSON_RDP_H__
#define __OUTPUT_JSON_RDP_H__
void JsonRdpLogRegister(void);
#endif /* __OUTPUT_JSON_RDP_H__ */

@ -77,6 +77,7 @@
#include "output-json-snmp.h"
#include "output-json-template.h"
#include "output-json-template-rust.h"
#include "output-json-rdp.h"
#include "output-lua.h"
#include "output-json-dnp3.h"
#include "output-json-metadata.h"
@ -1113,4 +1114,6 @@ void OutputRegisterLoggers(void)
JsonTemplateLogRegister();
/* Template Rust JSON logger. */
JsonTemplateRustLogRegister();
/* RDP JSON logger. */
JsonRdpLogRegister();
}

@ -442,6 +442,7 @@ typedef enum {
LOGGER_JSON_SNMP,
LOGGER_JSON_TEMPLATE_RUST,
LOGGER_JSON_TEMPLATE,
LOGGER_JSON_RDP,
LOGGER_ALERT_DEBUG,
LOGGER_ALERT_FAST,

@ -1320,6 +1320,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id)
CASE_CODE (LOGGER_JSON_TLS);
CASE_CODE (LOGGER_JSON_TEMPLATE_RUST);
CASE_CODE (LOGGER_JSON_TEMPLATE);
CASE_CODE (LOGGER_JSON_RDP);
CASE_CODE (LOGGER_TLS_STORE);
CASE_CODE (LOGGER_TLS);
CASE_CODE (LOGGER_FILE_STORE);

@ -233,6 +233,7 @@ outputs:
#- dnp3
- ftp
- rdp
- nfs
- smb
- tftp
@ -785,6 +786,8 @@ app-layer:
ftp:
enabled: yes
# memcap: 64mb
rdp:
enabled: yes
ssh:
enabled: yes
smtp:

Loading…
Cancel
Save