mirror of https://github.com/OISF/suricata
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 transactionspull/4188/head
parent
59da7ae302
commit
caef8b5b38
@ -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));
|
||||
}
|
||||
}
|
@ -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__ */
|
Loading…
Reference in New Issue