diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/rdp/rdp.rs | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/rdp/rdp.rs')
-rw-r--r-- | rust/src/rdp/rdp.rs | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/rust/src/rdp/rdp.rs b/rust/src/rdp/rdp.rs new file mode 100644 index 0000000..f08026a --- /dev/null +++ b/rust/src/rdp/rdp.rs @@ -0,0 +1,667 @@ +/* Copyright (C) 2019-2022 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 crate::applayer::{self, *}; +use crate::core::{AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use crate::rdp::parser::*; +use nom7::Err; +use std; +use std::collections::VecDeque; +use tls_parser::{parse_tls_plaintext, TlsMessage, TlsMessageHandshake, TlsRecordType}; + +static mut ALPROTO_RDP: AppProto = ALPROTO_UNKNOWN; + +// +// transactions +// + +#[derive(Debug, PartialEq, Eq)] +pub struct CertificateBlob { + pub data: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum RdpTransactionItem { + X224ConnectionRequest(X224ConnectionRequest), + X224ConnectionConfirm(X224ConnectionConfirm), + McsConnectRequest(McsConnectRequest), + McsConnectResponse(McsConnectResponse), + TlsCertificateChain(Vec<CertificateBlob>), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RdpTransaction { + pub id: u64, + pub item: RdpTransactionItem, + // managed by macros `export_tx_get_detect_state!` and `export_tx_set_detect_state!` + tx_data: AppLayerTxData, +} + +impl Transaction for RdpTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl RdpTransaction { + fn new(id: u64, item: RdpTransactionItem) -> Self { + Self { + id, + item, + tx_data: AppLayerTxData::new(), + } + } +} + +#[no_mangle] +pub unsafe 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 tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe 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( + _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, Eq)] +pub struct RdpState { + state_data: AppLayerStateData, + next_id: u64, + transactions: VecDeque<RdpTransaction>, + tls_parsing: bool, + bypass_parsing: bool, +} + +impl State<RdpTransaction> for RdpState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&RdpTransaction> { + self.transactions.get(index) + } +} + +impl RdpState { + fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + next_id: 0, + transactions: VecDeque::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> { + self.transactions.iter().find(|&tx| tx.id == tx_id) + } + + fn new_tx(&mut self, item: RdpTransactionItem) -> RdpTransaction { + self.next_id += 1; + let tx = RdpTransaction::new(self.next_id, item); + return tx; + } + + /// parse buffer captures from client to server + fn parse_ts(&mut self, input: &[u8]) -> AppLayerResult { + // no need to process input buffer + if self.bypass_parsing { + return AppLayerResult::ok(); + } + let mut available = input; + + loop { + if available.is_empty() { + return AppLayerResult::ok(); + } + if self.tls_parsing { + match parse_tls_plaintext(available) { + Ok((remainder, _tls)) => { + // bytes available for further parsing are what remain + available = remainder; + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + return AppLayerResult::err(); + } + } + } else { + // every message should be encapsulated within a T.123 tpkt + match parse_t123_tpkt(available) { + // success + Ok((remainder, t123)) => { + // bytes available for further parsing are what remain + 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_back(tx); + } + + // X.223 data packet, evaluate what it encapsulates + T123TpktChild::Data(x223) => { + #[allow(clippy::single_match)] + match x223.child { + X223DataChild::McsConnectRequest(mcs) => { + let tx = + self.new_tx(RdpTransactionItem::McsConnectRequest(mcs)); + self.transactions.push_back(tx); + } + // unknown message in X.223, skip + _ => (), + } + } + + // unknown message in T.123, skip + _ => (), + } + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + if probe_tls_handshake(available) { + self.tls_parsing = true; + let r = self.parse_ts(available); + if r.status == 1 { + //adds bytes already consumed to incomplete result + let consumed = (input.len() - available.len()) as u32; + return AppLayerResult::incomplete(r.consumed + consumed, r.needed); + } else { + return r; + } + } else { + return AppLayerResult::err(); + } + } + } + } + } + } + + /// parse buffer captures from server to client + fn parse_tc(&mut self, input: &[u8]) -> AppLayerResult { + // no need to process input buffer + if self.bypass_parsing { + return AppLayerResult::ok(); + } + let mut available = input; + + loop { + if available.is_empty() { + return AppLayerResult::ok(); + } + if self.tls_parsing { + match parse_tls_plaintext(available) { + Ok((remainder, tls)) => { + // bytes available for further parsing are what remain + available = remainder; + for message in &tls.msg { + #[allow(clippy::single_match)] + 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_back(tx); + self.bypass_parsing = true; + } + _ => {} + } + } + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + return AppLayerResult::err(); + } + } + } else { + // every message should be encapsulated within a T.123 tpkt + match parse_t123_tpkt(available) { + // success + Ok((remainder, t123)) => { + // bytes available for further parsing are what remain + 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_back(tx); + } + + // X.223 data packet, evaluate what it encapsulates + T123TpktChild::Data(x223) => { + #[allow(clippy::single_match)] + match x223.child { + X223DataChild::McsConnectResponse(mcs) => { + let tx = self + .new_tx(RdpTransactionItem::McsConnectResponse(mcs)); + self.transactions.push_back(tx); + self.bypass_parsing = true; + return AppLayerResult::ok(); + } + + // unknown message in X.223, skip + _ => (), + } + } + + // unknown message in T.123, skip + _ => (), + } + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + if probe_tls_handshake(available) { + self.tls_parsing = true; + let r = self.parse_tc(available); + if r.status == 1 { + //adds bytes already consumed to incomplete result + let consumed = (input.len() - available.len()) as u32; + return AppLayerResult::incomplete(r.consumed + consumed, r.needed); + } else { + return r; + } + } else { + return AppLayerResult::err(); + } + } + } + } + } + } +} + +#[no_mangle] +pub extern "C" fn rs_rdp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = RdpState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_rdp_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(unsafe { Box::from_raw(state as *mut RdpState) }); +} + +#[no_mangle] +pub unsafe 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); +} + +// +// probe +// + +/// probe for T.123 type identifier, as each message is encapsulated in T.123 +fn probe_rdp(input: &[u8]) -> bool { + !input.is_empty() && input[0] == TpktVersion::T123 as u8 +} + +/// probe for T.123 message, whether to client or to server +#[no_mangle] +pub unsafe 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.is_null() { + // 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 ALPROTO_RDP; + } + } + return ALPROTO_UNKNOWN; +} + +/// probe for TLS +fn probe_tls_handshake(input: &[u8]) -> bool { + !input.is_empty() && input[0] == u8::from(TlsRecordType::Handshake) +} + +// +// parse +// + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_parse_ts( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let state = cast_pointer!(state, RdpState); + let buf = stream_slice.as_slice(); + // attempt to parse bytes as `rdp` protocol + return state.parse_ts(buf); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_parse_tc( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let state = cast_pointer!(state, RdpState); + let buf = stream_slice.as_slice(); + // attempt to parse bytes as `rdp` protocol + return state.parse_tc(buf); +} + +export_tx_data_get!(rs_rdp_get_tx_data, RdpTransaction); +export_state_data_get!(rs_rdp_get_state_data, RdpState); + +// +// registration +// + +const PARSER_NAME: &[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: Some(rs_rdp_probe_ts_tc), + probe_tc: Some(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_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_rdp_tx_get_progress, + get_eventinfo: None, + get_eventinfo_byid: None, + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<RdpState, RdpTransaction>), + get_tx_data: rs_rdp_get_tx_data, + get_state_data: rs_rdp_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: 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 crate::rdp::parser::{RdpCookie, X224ConnectionRequest}; + + #[test] + fn test_probe_rdp() { + let buf: &[u8] = &[0x03, 0x00]; + assert!(probe_rdp(buf)); + } + + #[test] + fn test_probe_rdp_other() { + let buf: &[u8] = &[0x04, 0x00]; + assert!(!probe_rdp(buf)); + } + + #[test] + fn test_probe_tls_handshake() { + let buf: &[u8] = &[0x16, 0x00]; + assert!(probe_tls_handshake(buf)); + } + + #[test] + fn test_probe_tls_handshake_other() { + let buf: &[u8] = &[0x17, 0x00]; + assert!(!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] = &[ + 0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00, 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(); + // will consume 0, request length + 1 + assert_eq!(AppLayerResult::incomplete(0, 9), state.parse_ts(buf_1)); + assert_eq!(0, state.transactions.len()); + // exactly aligns with transaction + assert_eq!(AppLayerResult::ok(), 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!(AppLayerResult::err(), state.parse_ts(buf)); + } + + #[test] + fn test_parse_tc_rdp() { + let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x09, 0x02]; + let buf_2: &[u8] = &[0x03, 0x00, 0x00, 0x09, 0x02, 0xf0, 0x80, 0x7f, 0x66]; + let mut state = RdpState::new(); + // will consume 0, request length + 1 + assert_eq!(AppLayerResult::incomplete(0, 6), state.parse_tc(buf_1)); + assert_eq!(0, state.transactions.len()); + // exactly aligns with transaction + assert_eq!(AppLayerResult::ok(), 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!(AppLayerResult::err(), 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_back(tx0); + state.transactions.push_back(tx1); + assert_eq!(2, state.transactions.len()); + assert_eq!(1, state.transactions[0].id); + assert_eq!(2, state.transactions[1].id); + assert!(!state.tls_parsing); + assert!(!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_back(tx0); + state.transactions.push_back(tx1); + state.transactions.push_back(tx2); + assert_eq!(Some(&state.transactions[1]), state.get_tx(2)); + } + + #[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_back(tx0); + state.transactions.push_back(tx1); + state.transactions.push_back(tx2); + state.free_tx(1); + assert_eq!(3, state.next_id); + assert_eq!(2, state.transactions.len()); + assert_eq!(2, state.transactions[0].id); + assert_eq!(3, state.transactions[1].id); + assert_eq!(None, state.get_tx(1)); + } +} |