/* 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 //! 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, } #[derive(Debug, PartialEq, Eq)] pub enum RdpTransactionItem { X224ConnectionRequest(X224ConnectionRequest), X224ConnectionConfirm(X224ConnectionConfirm), McsConnectRequest(McsConnectRequest), McsConnectResponse(McsConnectResponse), TlsCertificateChain(Vec), } #[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, tls_parsing: bool, bypass_parsing: bool, } impl State 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::), 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)); } }