From bab497ab2cfd7ad90f4876840fbfa3bb21426626 Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Fri, 5 Jun 2020 19:18:05 +0530 Subject: [PATCH] dcerpc: Add multi transaction support DCERPC parser so far provided support for single transactions only. Extend that to support multiple transactions. In order for multiple transactions to work, there is always a transaction identifier for any protocol in its header that lets a response match the request. In DCERPC, for TCP, that param is call_id in the header which is a 32 bit field. For UDP, however since it uses different version of RPC (4.x), this is defined by serial number field defined in the header. This field however is not contiguous and needs to be assembled by the provided serial_low and serial_hi fields. --- rust/src/dcerpc/dcerpc.rs | 490 ++++++++++++++++++++++------------ rust/src/dcerpc/dcerpc_udp.rs | 163 ++++++----- rust/src/dcerpc/detect.rs | 31 ++- rust/src/dcerpc/parser.rs | 4 - src/detect-dce-iface.c | 2 +- src/detect-dce-opnum.c | 3 +- 6 files changed, 434 insertions(+), 259 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 7bc8d4da47b..7617e56d3c7 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -80,6 +80,7 @@ pub const DCERPC_TYPE_SHUTDOWN: u8 = 17; pub const DCERPC_TYPE_CO_CANCEL: u8 = 18; pub const DCERPC_TYPE_ORPHANED: u8 = 19; pub const DCERPC_TYPE_RTS: u8 = 20; +pub const DCERPC_TYPE_UNKNOWN: u8 = 99; pub fn dcerpc_type_string(t: u8) -> String { match t { @@ -104,6 +105,7 @@ pub fn dcerpc_type_string(t: u8) -> String { DCERPC_TYPE_CO_CANCEL => "CO_CANCEL", DCERPC_TYPE_ORPHANED => "ORPHANED", DCERPC_TYPE_RTS => "RTS", + DCERPC_TYPE_UNKNOWN => "UNKNOWN", _ => { return (t).to_string(); } @@ -111,50 +113,98 @@ pub fn dcerpc_type_string(t: u8) -> String { .to_string() } +pub fn get_resp_type_for_req(t: u8) -> u8 { + match t { + DCERPC_TYPE_REQUEST => DCERPC_TYPE_RESPONSE, + DCERPC_TYPE_BIND => DCERPC_TYPE_BINDACK, + DCERPC_TYPE_ALTER_CONTEXT => DCERPC_TYPE_ALTER_CONTEXT_RESP, + _ => DCERPC_TYPE_UNKNOWN, + } +} + +pub fn get_req_type_for_resp(t: u8) -> u8 { + match t { + DCERPC_TYPE_RESPONSE => DCERPC_TYPE_REQUEST, + DCERPC_TYPE_BINDACK => DCERPC_TYPE_BIND, + DCERPC_TYPE_ALTER_CONTEXT_RESP => DCERPC_TYPE_ALTER_CONTEXT, + _ => DCERPC_TYPE_UNKNOWN, + } +} + #[derive(Debug)] -pub struct DCERPCRequest { +pub struct DCERPCTransaction { + pub id: u32, // internal transaction ID pub ctxid: u16, pub opnum: u16, pub first_request_seen: u8, - pub stub_data_buffer: Vec, - pub stub_data_buffer_len: u16, - pub stub_data_buffer_reset: bool, - pub cmd: u8, + pub call_id: u32, // ID to match any request-response pair + pub frag_cnt_ts: u16, + pub frag_cnt_tc: u16, + pub endianness: u8, + pub stub_data_buffer_ts: Vec, + pub stub_data_buffer_tc: Vec, + pub stub_data_buffer_len_ts: u16, + pub stub_data_buffer_len_tc: u16, + pub stub_data_buffer_reset_ts: bool, + pub stub_data_buffer_reset_tc: bool, + pub req_done: bool, + pub resp_done: bool, + pub req_cmd: u8, + pub resp_cmd: u8, + pub tx_data: AppLayerTxData, + pub de_state: Option<*mut core::DetectEngineState>, } -impl DCERPCRequest { - pub fn new() -> DCERPCRequest { - return DCERPCRequest { +impl DCERPCTransaction { + pub fn new() -> DCERPCTransaction { + return DCERPCTransaction { + id: 0, ctxid: 0, opnum: 0, first_request_seen: 0, - stub_data_buffer: Vec::new(), - stub_data_buffer_len: 0, - stub_data_buffer_reset: false, - cmd: DCERPC_TYPE_REQUEST, + call_id: 0, + frag_cnt_ts: 0, + frag_cnt_tc: 0, + endianness: 0, + stub_data_buffer_ts: Vec::new(), + stub_data_buffer_tc: Vec::new(), + stub_data_buffer_len_ts: 0, // TODO maybe retrieve length from buffer and avoid this param + stub_data_buffer_len_tc: 0, + stub_data_buffer_reset_ts: false, + stub_data_buffer_reset_tc: false, + req_done: false, + resp_done: false, + req_cmd: DCERPC_TYPE_REQUEST, + resp_cmd: DCERPC_TYPE_RESPONSE, + tx_data: AppLayerTxData::new(), + de_state: None, }; } -} -#[derive(Debug)] -pub struct DCERPCResponse { - pub stub_data_buffer: Vec, - pub stub_data_buffer_len: u16, - pub stub_data_buffer_reset: bool, - pub cmd: u8, -} + pub fn get_req_ctxid(&self) -> u16 { + self.ctxid + } -impl DCERPCResponse { - pub fn new() -> DCERPCResponse { - return DCERPCResponse { - stub_data_buffer: Vec::new(), - stub_data_buffer_len: 0, - stub_data_buffer_reset: false, - cmd: DCERPC_TYPE_RESPONSE, - }; + pub fn get_first_req_seen(&self) -> u8 { + self.first_request_seen + } + + pub fn get_req_opnum(&self) -> u16 { + self.opnum + } + + pub fn get_endianness(&self) -> u8 { + self.endianness } } +#[derive(Debug)] +pub struct DCERPCRequest { + pub ctxid: u16, + pub opnum: u16, + pub first_request_seen: u8, +} + #[derive(Debug, Clone)] pub struct DCERPCUuidEntry { pub ctxid: u16, @@ -237,19 +287,18 @@ pub struct DCERPCState { pub header: Option, pub bind: Option, pub bindack: Option, - pub request: Option, - pub response: Option, + pub transactions: Vec, pub buffer_ts: Vec, pub buffer_tc: Vec, pub pad: u8, pub padleft: u16, pub bytes_consumed: u16, - pub tx_id: u16, + pub tx_id: u32, pub query_completed: bool, pub data_needed_for_dir: u8, pub prev_dir: u8, - pub de_state: Option<*mut core::DetectEngineState>, - pub tx_data: AppLayerTxData, + pub prev_tx_call_id: u32, + pub clear_bind_cache: bool, } impl DCERPCState { @@ -258,8 +307,7 @@ impl DCERPCState { header: None, bind: None, bindack: None, - request: None, - response: None, + transactions: Vec::new(), buffer_ts: Vec::new(), buffer_tc: Vec::new(), pad: 0, @@ -269,11 +317,21 @@ impl DCERPCState { query_completed: false, data_needed_for_dir: core::STREAM_TOSERVER, prev_dir: core::STREAM_TOSERVER, - de_state: None, - tx_data: AppLayerTxData::new(), + prev_tx_call_id: 0, + clear_bind_cache: false, }; } + fn create_tx(&mut self, call_id: u32) -> DCERPCTransaction { + let mut tx = DCERPCTransaction::new(); + let endianness = self.get_hdr_drep_0() & 0x10; + tx.id = self.tx_id; + tx.call_id = call_id; + tx.endianness = endianness; + self.tx_id += 1; + tx + } + fn get_hdr_drep_0(&self) -> u8 { if let Some(ref hdr) = &self.header { return hdr.packed_drep[0]; @@ -325,28 +383,10 @@ impl DCERPCState { None } - pub fn get_req_ctxid(&self) -> Option { - debug_validate_bug_on!(self.request.is_none()); - if let Some(ref req) = self.request { - return Some(req.ctxid); - } - // Shouldn't happen - None - } - - pub fn get_first_req_seen(&self) -> Option { - debug_validate_bug_on!(self.request.is_none()); - if let Some(ref req) = self.request { - return Some(req.first_request_seen); - } - // Shouldn't happen - None - } - - pub fn get_req_opnum(&self) -> Option { - debug_validate_bug_on!(self.request.is_none()); - if let Some(ref req) = self.request { - return Some(req.opnum); + pub fn get_hdr_call_id(&self) -> Option { + debug_validate_bug_on!(self.header.is_none()); + if let Some(ref hdr) = self.header { + return Some(hdr.call_id); } // Shouldn't happen None @@ -398,6 +438,78 @@ impl DCERPCState { } } + /// Get transaction as per the given transaction ID. Transaction ID with + /// which the lookup is supposed to be done as per the calls from AppLayer + /// parser in C. This requires an internal transaction ID to be maintained. + /// + /// Arguments: + /// * `tx_id`: + /// type: unsigned 32 bit integer + /// description: internal transaction ID to track transactions + /// + /// Return value: + /// Option mutable reference to DCERPCTransaction + pub fn get_tx(&mut self, tx_id: u32) -> Option<&mut DCERPCTransaction> { + for tx in &mut self.transactions { + let found = tx.id == tx_id; + if found { + return Some(tx); + } + } + None + } + + /// Find the transaction as per call ID defined in header. If the tx is not + /// found, create one. + /// + /// Arguments: + /// * `call_id`: + /// type: unsigned 32 bit integer + /// description: call_id param derived from TCP Header + /// * `dir`: + /// type: unsigned 8 bit integer + /// description: direction of the flow + /// + /// Return value: + /// Option mutable reference to DCERPCTransaction + pub fn get_tx_by_call_id(&mut self, call_id: u32, dir: u8) -> Option<&mut DCERPCTransaction> { + let cmd = self.get_hdr_type().unwrap_or(0); + for tx in &mut self.transactions { + let found = tx.call_id == call_id; + if found { + match dir { + core::STREAM_TOSERVER => { + let resp_cmd = get_resp_type_for_req(cmd); + if resp_cmd != tx.resp_cmd { + continue; + } + } + _ => { + let req_cmd = get_req_type_for_resp(cmd); + if req_cmd != tx.req_cmd { + continue; + } + } + } + return Some(tx); + } + } + None + } + + pub fn handle_bind_cache(&mut self, call_id: u32, is_response: bool) { + if self.clear_bind_cache == true { + self.bind = None; + self.bindack = None; + } + if self.prev_tx_call_id == call_id && is_response == true { + self.clear_bind_cache = true; + } else { + self.clear_bind_cache = false; + } + self.prev_tx_call_id = call_id; + } + /// Makes a call to the nom parser for parsing DCERPC Header. /// /// Arguments: @@ -491,6 +603,12 @@ impl DCERPCState { } idx = retval + idx; } + let call_id = self.get_hdr_call_id().unwrap_or(0); + let mut tx = self.create_tx(call_id); + tx.req_cmd = self.get_hdr_type().unwrap_or(0); + tx.req_done = true; + tx.frag_cnt_ts = 1; + self.transactions.push(tx); // Bytes parsed with `parse_dcerpc_bind` + (bytes parsed per bindctxitem [44] * number // of bindctxitems) (input.len() - leftover_bytes.len()) as i32 + retval * numctxitems as i32 @@ -543,38 +661,48 @@ impl DCERPCState { } } - pub fn handle_stub_data(&mut self, input: &[u8], input_len: u16) -> u16 { - let mut retval: u16 = 0; + pub fn handle_stub_data(&mut self, input: &[u8], input_len: u16, dir: u8) -> u16 { + let retval; let hdrpfcflags = self.get_hdr_pfcflags().unwrap_or(0); let padleft = self.padleft; + let call_id = self.get_hdr_call_id().unwrap_or(0); + let hdrtype = self.get_hdr_type(); + let tx; + if let Some(transaction) = self.get_tx_by_call_id(call_id, dir) { + tx = transaction; + } else { + SCLogDebug!("No transaction found matching the call ID: {:?}", call_id); + return 0; + } + // Update the stub params based on the packet type - match self.get_hdr_type() { + match hdrtype { Some(x) => match x { DCERPC_TYPE_REQUEST => { - if let Some(ref mut req) = self.request { - retval = evaluate_stub_params( - input, - input_len, - hdrpfcflags, - padleft, - &mut req.stub_data_buffer, - &mut req.stub_data_buffer_len, - &mut req.stub_data_buffer_reset, - ); - } + retval = evaluate_stub_params( + input, + input_len, + hdrpfcflags, + padleft, + &mut tx.stub_data_buffer_ts, + &mut tx.stub_data_buffer_len_ts, + &mut tx.stub_data_buffer_reset_ts, + ); + tx.req_done = true; + tx.frag_cnt_ts = 1; } DCERPC_TYPE_RESPONSE => { - if let Some(ref mut resp) = self.response { - retval = evaluate_stub_params( - input, - input_len, - hdrpfcflags, - padleft, - &mut resp.stub_data_buffer, - &mut resp.stub_data_buffer_len, - &mut resp.stub_data_buffer_reset, - ); - } + retval = evaluate_stub_params( + input, + input_len, + hdrpfcflags, + padleft, + &mut tx.stub_data_buffer_tc, + &mut tx.stub_data_buffer_len_tc, + &mut tx.stub_data_buffer_reset_tc, + ); + tx.resp_done = true; + tx.frag_cnt_tc = 1; } _ => { SCLogDebug!("Unrecognized packet type"); @@ -616,7 +744,7 @@ impl DCERPCState { let mut input_left = input.len() as u16 - bytes_consumed; let mut parsed = bytes_consumed; while input_left > 0 && parsed < fraglen { - let retval = self.handle_stub_data(&input[parsed as usize..], input_left); + let retval = self.handle_stub_data(&input[parsed as usize..], input_left, dir); if retval > 0 && retval <= input_left { parsed += retval; input_left -= retval; @@ -639,9 +767,26 @@ impl DCERPCState { pub fn process_request_pdu(&mut self, input: &[u8]) -> i32 { let endianness = self.get_endianness(); match parser::parse_dcerpc_request(input, endianness) { - Ok((leftover_input, mut request)) => { - request.cmd = self.get_hdr_type().unwrap_or(0); - self.request = Some(request); + Ok((leftover_input, request)) => { + let call_id = self.get_hdr_call_id().unwrap_or(0); + let hdr_type = self.get_hdr_type().unwrap_or(0); + let mut transaction = self.get_tx_by_call_id(call_id, core::STREAM_TOSERVER); + match transaction { + Some(ref mut tx) => { + tx.req_cmd = hdr_type; + tx.ctxid = request.ctxid; + tx.opnum = request.opnum; + tx.first_request_seen = request.first_request_seen; + } + None => { + let mut tx = self.create_tx(call_id); + tx.req_cmd = hdr_type; + tx.ctxid = request.ctxid; + tx.opnum = request.opnum; + tx.first_request_seen = request.first_request_seen; + self.transactions.push(tx); + } + } let parsed = self.handle_common_stub( &input, (input.len() - leftover_input.len()) as u16, @@ -726,9 +871,10 @@ impl DCERPCState { } else { self.query_completed = true; } - // TODO buffer extra data when input length is more than fraglen parsed = self.bytes_consumed as i32; + let current_call_id = self.get_hdr_call_id().unwrap_or(0); + match self.get_hdr_type() { Some(x) => match x { DCERPC_TYPE_BIND | DCERPC_TYPE_ALTER_CONTEXT => { @@ -736,23 +882,47 @@ impl DCERPCState { if retval == -1 { return AppLayerResult::err(); } + self.handle_bind_cache(current_call_id, false); } DCERPC_TYPE_BINDACK | DCERPC_TYPE_ALTER_CONTEXT_RESP => { retval = self.process_bindack_pdu(&buffer[parsed as usize..]); if retval == -1 { return AppLayerResult::err(); } + let tx = if let Some(mut tx) = self.get_tx_by_call_id(current_call_id, core::STREAM_TOCLIENT) { + tx.resp_cmd = x; + tx + } else { + let mut tx = self.create_tx(current_call_id); + tx.resp_cmd = x; + self.transactions.push(tx); + self.transactions.last_mut().unwrap() + }; + tx.resp_done = true; + tx.frag_cnt_tc = 1; + self.handle_bind_cache(current_call_id, false); } DCERPC_TYPE_REQUEST => { retval = self.process_request_pdu(&buffer[parsed as usize..]); if retval == -1 { return AppLayerResult::err(); } + // In case the response came first, the transaction would complete later when + // the corresponding request also comes through + self.handle_bind_cache(current_call_id, false); } DCERPC_TYPE_RESPONSE => { - let mut response = DCERPCResponse::new(); - response.cmd = self.get_hdr_type().unwrap_or(DCERPC_TYPE_RESPONSE); - self.response = Some(response); + let transaction = self.get_tx_by_call_id(current_call_id, core::STREAM_TOCLIENT); + match transaction { + Some(mut tx) => { + tx.resp_cmd = x; + } + None => { + let mut tx = self.create_tx(current_call_id); + tx.resp_cmd = x; + self.transactions.push(tx); + } + }; retval = self.handle_common_stub( &buffer[parsed as usize..], 0, @@ -761,7 +931,7 @@ impl DCERPCState { if retval == -1 { return AppLayerResult::err(); } - self.tx_id += 1; // Complete response, maybe to be used in future? + self.handle_bind_cache(current_call_id, true); } _ => { SCLogDebug!("Unrecognized packet type"); @@ -786,13 +956,8 @@ impl DCERPCState { } fn evaluate_stub_params( - input: &[u8], - input_len: u16, - hdrflags: u8, - lenleft: u16, - stub_data_buffer: &mut Vec, - stub_data_buffer_len: &mut u16, - stub_data_buffer_reset: &mut bool, + input: &[u8], input_len: u16, hdrflags: u8, lenleft: u16, stub_data_buffer: &mut Vec, + stub_data_buffer_len: &mut u16, stub_data_buffer_reset: &mut bool, ) -> u16 { let stub_len: u16; let fragtype = hdrflags & (PFC_FIRST_FRAG | PFC_LAST_FRAG); @@ -813,8 +978,7 @@ fn evaluate_stub_params( #[no_mangle] pub extern "C" fn rs_parse_dcerpc_request_gap( - state: &mut DCERPCState, - _input_len: u32, + state: &mut DCERPCState, _input_len: u32, ) -> AppLayerResult { if state.handle_gap_ts() == 0 { return AppLayerResult::ok(); @@ -824,8 +988,7 @@ pub extern "C" fn rs_parse_dcerpc_request_gap( #[no_mangle] pub extern "C" fn rs_parse_dcerpc_response_gap( - state: &mut DCERPCState, - _input_len: u32, + state: &mut DCERPCState, _input_len: u32, ) -> AppLayerResult { if state.handle_gap_tc() == 0 { return AppLayerResult::ok(); @@ -835,13 +998,8 @@ pub extern "C" fn rs_parse_dcerpc_response_gap( #[no_mangle] pub extern "C" fn rs_dcerpc_parse_request( - _flow: *mut core::Flow, - state: &mut DCERPCState, - _pstate: *mut std::os::raw::c_void, - input: *const u8, - input_len: u32, - _data: *mut std::os::raw::c_void, - flags: u8, + _flow: *mut core::Flow, state: &mut DCERPCState, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *mut std::os::raw::c_void, flags: u8, ) -> AppLayerResult { if input_len > 0 && input != std::ptr::null_mut() { let buf = build_slice!(input, input_len as usize); @@ -852,13 +1010,8 @@ pub extern "C" fn rs_dcerpc_parse_request( #[no_mangle] pub extern "C" fn rs_dcerpc_parse_response( - _flow: *mut core::Flow, - state: &mut DCERPCState, - _pstate: *mut std::os::raw::c_void, - input: *const u8, - input_len: u32, - _data: *mut std::os::raw::c_void, - flags: u8, + _flow: *mut core::Flow, state: &mut DCERPCState, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *mut std::os::raw::c_void, flags: u8, ) -> AppLayerResult { if input_len > 0 { if input != std::ptr::null_mut() { @@ -890,8 +1043,8 @@ pub extern "C" fn rs_dcerpc_state_transaction_free(_state: *mut std::os::raw::c_ pub extern "C" fn rs_dcerpc_get_tx_detect_state( vtx: *mut std::os::raw::c_void, ) -> *mut core::DetectEngineState { - let dce_state = cast_pointer!(vtx, DCERPCState); - match dce_state.de_state { + let dce_tx = cast_pointer!(vtx, DCERPCTransaction); + match dce_tx.de_state { Some(ds) => ds, None => std::ptr::null_mut(), } @@ -899,34 +1052,41 @@ pub extern "C" fn rs_dcerpc_get_tx_detect_state( #[no_mangle] pub extern "C" fn rs_dcerpc_set_tx_detect_state( - vtx: *mut std::os::raw::c_void, - de_state: *mut core::DetectEngineState, + vtx: *mut std::os::raw::c_void, de_state: *mut core::DetectEngineState, ) -> u8 { - let dce_state = cast_pointer!(vtx, DCERPCState); - dce_state.de_state = Some(de_state); + let dce_tx = cast_pointer!(vtx, DCERPCTransaction); + dce_tx.de_state = Some(de_state); 0 } #[no_mangle] pub extern "C" fn rs_dcerpc_get_tx( - state: *mut std::os::raw::c_void, - _tx_id: u64, -) -> *mut DCERPCState { - let dce_state = cast_pointer!(state, DCERPCState); - dce_state + vtx: *mut std::os::raw::c_void, tx_id: u32, +) -> *mut DCERPCTransaction { + let dce_state = cast_pointer!(vtx, DCERPCState); + match dce_state.get_tx(tx_id) { + Some(tx) => tx, + None => std::ptr::null_mut(), + } } #[no_mangle] -pub extern "C" fn rs_dcerpc_get_tx_cnt(_state: *mut std::os::raw::c_void) -> u8 { - 1 +pub extern "C" fn rs_dcerpc_get_tx_cnt(vtx: *mut std::os::raw::c_void) -> u32 { + let dce_state = cast_pointer!(vtx, DCERPCState); + dce_state.tx_id } #[no_mangle] -pub extern "C" fn rs_dcerpc_get_alstate_progress( - _tx: *mut std::os::raw::c_void, - _direction: u8, -) -> u8 { - 0 +pub extern "C" fn rs_dcerpc_get_alstate_progress(tx: &mut DCERPCTransaction, direction: u8) -> u8 { + if direction == core::STREAM_TOSERVER && tx.req_done { + SCLogDebug!("tx {} TOSERVER progress 1 => {:?}", tx.call_id, tx); + return 1; + } else if direction == core::STREAM_TOCLIENT && tx.resp_done { + SCLogDebug!("tx {} TOCLIENT progress 1 => {:?}", tx.call_id, tx); + return 1; + } + SCLogDebug!("tx {} direction {} progress 0", tx.call_id, direction); + return 0; } #[no_mangle] @@ -939,35 +1099,27 @@ pub extern "C" fn rs_dcerpc_get_tx_data( tx: *mut std::os::raw::c_void) -> *mut AppLayerTxData { - let tx = cast_pointer!(tx, DCERPCState); + let tx = cast_pointer!(tx, DCERPCTransaction); return &mut tx.tx_data; } #[no_mangle] pub unsafe extern "C" fn rs_dcerpc_get_stub_data( - state: &mut DCERPCState, - buf: *mut *const u8, - len: *mut u32, - endianness: *mut u8, - dir: u8, + tx: &mut DCERPCTransaction, buf: *mut *const u8, len: *mut u32, endianness: *mut u8, dir: u8, ) { match dir { core::STREAM_TOSERVER => { - if let Some(ref req) = state.request { - *len = req.stub_data_buffer_len as u32; - *buf = req.stub_data_buffer.as_ptr(); - SCLogDebug!("DCERPC Request stub buffer: Setting buffer to: {:?}", *buf); - } + *len = tx.stub_data_buffer_len_ts as u32; + *buf = tx.stub_data_buffer_ts.as_ptr(); + SCLogDebug!("DCERPC Request stub buffer: Setting buffer to: {:?}", *buf); } _ => { - if let Some(ref resp) = state.response { - *len = resp.stub_data_buffer_len as u32; - *buf = resp.stub_data_buffer.as_ptr(); - SCLogDebug!("DCERPC Response stub buffer: Setting buffer to: {:?}", *buf); - } + *len = tx.stub_data_buffer_len_tc as u32; + *buf = tx.stub_data_buffer_tc.as_ptr(); + SCLogDebug!("DCERPC Response stub buffer: Setting buffer to: {:?}", *buf); } } - *endianness = state.get_hdr_drep_0() & 0x10; + *endianness = tx.get_endianness(); } #[cfg(test)] @@ -1397,13 +1549,12 @@ mod tests { assert_eq!(5, hdr.rpc_vers); assert_eq!(1024, hdr.frag_length); } - if let Some(req) = dcerpc_state.request { - assert_eq!(11, req.ctxid); - assert_eq!(9, req.opnum); - assert_eq!(1, req.first_request_seen); - assert_eq!(1000, req.stub_data_buffer_len); - assert_eq!(true, req.stub_data_buffer_reset); - } + let tx = &dcerpc_state.transactions[0]; + assert_eq!(11, tx.ctxid); + assert_eq!(9, tx.opnum); + assert_eq!(1, tx.first_request_seen); + assert_eq!(1000, tx.stub_data_buffer_len_ts); + assert_eq!(true, tx.stub_data_buffer_reset_ts); } #[test] @@ -1531,9 +1682,8 @@ mod tests { AppLayerResult::ok(), dcerpc_state.handle_input_data(&request3, core::STREAM_TOSERVER) ); - if let Some(ref req) = dcerpc_state.request { - assert_eq!(20, req.stub_data_buffer_len); - } + let tx = &dcerpc_state.transactions[0]; + assert_eq!(20, tx.stub_data_buffer_len_ts); } #[test] @@ -1590,9 +1740,8 @@ mod tests { AppLayerResult::ok(), dcerpc_state.handle_input_data(&request2, core::STREAM_TOSERVER) ); - if let Some(ref req) = dcerpc_state.request { - assert_eq!(12, req.stub_data_buffer_len); - } + let tx = &dcerpc_state.transactions[0]; + assert_eq!(12, tx.stub_data_buffer_len_ts); } #[test] @@ -2099,10 +2248,9 @@ mod tests { AppLayerResult::ok(), dcerpc_state.handle_input_data(request2, core::STREAM_TOSERVER) ); - if let Some(ref req) = dcerpc_state.request { - assert_eq!(2, req.opnum); - assert_eq!(0, req.ctxid); - assert_eq!(14, req.stub_data_buffer_len); - } + let tx = &dcerpc_state.transactions[0]; + assert_eq!(2, tx.opnum); + assert_eq!(0, tx.ctxid); + assert_eq!(14, tx.stub_data_buffer_len_ts); } } diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs index e4e5440d53b..3d1adf663df 100644 --- a/rust/src/dcerpc/dcerpc_udp.rs +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -20,8 +20,7 @@ use std::mem::transmute; use crate::applayer::{AppLayerResult, AppLayerTxData}; use crate::core; use crate::dcerpc::dcerpc::{ - DCERPCRequest, DCERPCResponse, DCERPCUuidEntry, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, - PFC_FIRST_FRAG, + DCERPCTransaction, DCERPCUuidEntry, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, PFC_FIRST_FRAG, }; use crate::dcerpc::parser; use crate::log::*; @@ -55,9 +54,9 @@ pub struct DCERPCHdrUdp { #[derive(Debug)] pub struct DCERPCUDPState { + pub tx_id: u32, pub header: Option, - pub request: Option, - pub response: Option, + pub transactions: Vec, pub fraglenleft: u16, pub uuid_entry: Option, pub uuid_list: Vec, @@ -68,9 +67,9 @@ pub struct DCERPCUDPState { impl DCERPCUDPState { pub fn new() -> DCERPCUDPState { return DCERPCUDPState { + tx_id: 0, header: None, - request: None, - response: None, + transactions: Vec::new(), fraglenleft: 0, uuid_entry: None, uuid_list: Vec::new(), @@ -79,27 +78,44 @@ impl DCERPCUDPState { }; } - fn new_request(&mut self) { - let request = DCERPCRequest::new(); - self.request = Some(request); + fn create_tx(&mut self, serial_no: u16) -> DCERPCTransaction { + let mut tx = DCERPCTransaction::new(); + let endianness = self.get_hdr_drep_0() & 0x10; + tx.id = self.tx_id; + tx.call_id = serial_no as u32; + tx.endianness = endianness; + self.tx_id += 1; + tx } - fn new_response(&mut self) { - let response = DCERPCResponse::new(); - self.response = Some(response); + + fn evaluate_serial_no(&mut self) -> u16 { + let mut serial_no: u16; + let mut serial_hi: u8 = 0; + let mut serial_lo: u8 = 0; + let endianness = self.get_hdr_drep_0(); + if let Some(ref hdr) = &self.header { + serial_hi = hdr.serial_hi; + serial_lo = hdr.serial_lo; + } + if endianness & 0x10 == 0 { + serial_no = serial_lo as u16; + serial_no = serial_no.rotate_left(8) | serial_hi as u16; + } else { + serial_no = serial_hi as u16; + serial_no = serial_no.rotate_left(8) | serial_lo as u16; + } + serial_no } - fn create_new_query(&mut self, pkt_type: u8) { - match pkt_type { - DCERPC_TYPE_REQUEST => { - self.new_request(); - } - DCERPC_TYPE_RESPONSE => { - self.new_response(); - } - _ => { - SCLogDebug!("Unrecognized packet type"); + + fn find_tx(&mut self, serial_no: u16) -> Option<&mut DCERPCTransaction> { + for tx in &mut self.transactions { + let found = tx.call_id == (serial_no as u32); + if found { + return Some(tx); } } + None } fn get_hdr_pkt_type(&self) -> Option { @@ -120,6 +136,14 @@ impl DCERPCUDPState { None } + fn get_hdr_drep_0(&self) -> u8 { + debug_validate_bug_on!(self.header.is_none()); + if let Some(ref hdr) = &self.header { + return hdr.drep[0]; + } + 0 + } + pub fn get_hdr_fraglen(&self) -> Option { debug_validate_bug_on!(self.header.is_none()); if let Some(ref hdr) = &self.header { @@ -130,35 +154,47 @@ impl DCERPCUDPState { } pub fn handle_fragment_data(&mut self, input: &[u8], input_len: u16) -> u16 { - let mut retval: u16 = 0; + let retval: u16; let hdrflags1 = self.get_hdr_flags1().unwrap_or(0); let fraglenleft = self.fraglenleft; + let hdrtype = self.get_hdr_pkt_type().unwrap_or(0); + let serial_no = self.evaluate_serial_no(); + let tx; + if let Some(transaction) = self.find_tx(serial_no) { + tx = transaction; + } else { + SCLogDebug!( + "No transaction found matching the serial number: {:?}", + serial_no + ); + return 0; + } // Update the stub params based on the packet type - match self.get_hdr_pkt_type().unwrap_or(0) { + match hdrtype { DCERPC_TYPE_REQUEST => { - if let Some(ref mut req) = self.request { - retval = evaluate_stub_params( - input, - input_len, - hdrflags1, - fraglenleft, - &mut req.stub_data_buffer, - &mut req.stub_data_buffer_len, - ); - } + retval = evaluate_stub_params( + input, + input_len, + hdrflags1, + fraglenleft, + &mut tx.stub_data_buffer_ts, + &mut tx.stub_data_buffer_len_ts, + ); + tx.req_done = true; + tx.frag_cnt_ts += 1; } DCERPC_TYPE_RESPONSE => { - if let Some(ref mut resp) = self.response { - retval = evaluate_stub_params( - input, - input_len, - hdrflags1, - fraglenleft, - &mut resp.stub_data_buffer, - &mut resp.stub_data_buffer_len, - ); - } + retval = evaluate_stub_params( + input, + input_len, + hdrflags1, + fraglenleft, + &mut tx.stub_data_buffer_tc, + &mut tx.stub_data_buffer_len_tc, + ); + tx.resp_done = true; + tx.frag_cnt_tc += 1; } _ => { SCLogDebug!("Unrecognized packet type"); @@ -210,10 +246,11 @@ impl DCERPCUDPState { } let mut input_left = input.len() as i32 - parsed; - let pkt_type = self.get_hdr_pkt_type().unwrap_or(0); let fraglen = self.get_hdr_fraglen().unwrap_or(0); self.fraglenleft = fraglen; - self.create_new_query(pkt_type); + let serial_no = self.evaluate_serial_no(); + let tx = self.create_tx(serial_no); + self.transactions.push(tx); // Parse rest of the body while parsed >= DCERPC_UDP_HDR_LEN && parsed < fraglen as i32 && input_left > 0 { let retval = self.handle_fragment_data(&input[parsed as usize..], input_left as u16); @@ -231,11 +268,7 @@ impl DCERPCUDPState { } fn evaluate_stub_params( - input: &[u8], - input_len: u16, - hdrflags: u8, - lenleft: u16, - stub_data_buffer: &mut Vec, + input: &[u8], input_len: u16, hdrflags: u8, lenleft: u16, stub_data_buffer: &mut Vec, stub_data_buffer_len: &mut u16, ) -> u16 { let stub_len: u16; @@ -260,13 +293,8 @@ fn evaluate_stub_params( #[no_mangle] pub extern "C" fn rs_dcerpc_udp_parse( - _flow: *mut core::Flow, - state: &mut DCERPCUDPState, - _pstate: *mut std::os::raw::c_void, - input: *const u8, - input_len: u32, - _data: *mut std::os::raw::c_void, - _flags: u8, + _flow: *mut core::Flow, state: &mut DCERPCUDPState, _pstate: *mut std::os::raw::c_void, + input: *const u8, input_len: u32, _data: *mut std::os::raw::c_void, _flags: u8, ) -> AppLayerResult { if input_len > 0 && input != std::ptr::null_mut() { let buf = build_slice!(input, input_len as usize); @@ -289,8 +317,7 @@ pub unsafe extern "C" fn rs_dcerpc_udp_state_new() -> *mut std::os::raw::c_void #[no_mangle] pub extern "C" fn rs_dcerpc_udp_state_transaction_free( - _state: *mut std::os::raw::c_void, - _tx_id: u64, + _state: *mut std::os::raw::c_void, _tx_id: u64, ) { // do nothing } @@ -308,8 +335,7 @@ pub extern "C" fn rs_dcerpc_udp_get_tx_detect_state( #[no_mangle] pub extern "C" fn rs_dcerpc_udp_set_tx_detect_state( - vtx: *mut std::os::raw::c_void, - de_state: *mut core::DetectEngineState, + vtx: *mut std::os::raw::c_void, de_state: *mut core::DetectEngineState, ) -> u8 { let dce_state = cast_pointer!(vtx, DCERPCUDPState); dce_state.de_state = Some(de_state); @@ -327,8 +353,7 @@ pub extern "C" fn rs_dcerpc_udp_get_tx_data( #[no_mangle] pub extern "C" fn rs_dcerpc_udp_get_tx( - state: *mut std::os::raw::c_void, - _tx_id: u64, + state: *mut std::os::raw::c_void, _tx_id: u64, ) -> *mut DCERPCUDPState { let dce_state = cast_pointer!(state, DCERPCUDPState); dce_state @@ -341,8 +366,7 @@ pub extern "C" fn rs_dcerpc_udp_get_tx_cnt(_state: *mut std::os::raw::c_void) -> #[no_mangle] pub extern "C" fn rs_dcerpc_udp_get_alstate_progress( - _tx: *mut std::os::raw::c_void, - _direction: u8, + _tx: *mut std::os::raw::c_void, _direction: u8, ) -> u8 { 0 } @@ -516,8 +540,9 @@ mod tests { dcerpcudp_state.handle_input_data(request) ); assert_eq!(0, dcerpcudp_state.fraglenleft); - if let Some(req) = dcerpcudp_state.request { - assert_eq!(1392, req.stub_data_buffer_len); - } + assert_eq!( + 1392, + dcerpcudp_state.transactions[0].stub_data_buffer_len_ts + ); } } diff --git a/rust/src/dcerpc/detect.rs b/rust/src/dcerpc/detect.rs index 2e9d78f8d60..a7edeaccbb0 100644 --- a/rust/src/dcerpc/detect.rs +++ b/rust/src/dcerpc/detect.rs @@ -16,7 +16,8 @@ */ use super::dcerpc::{ - DCERPCState, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, DCERPC_UUID_ENTRY_FLAG_FF, + DCERPCState, DCERPCTransaction, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, + DCERPC_UUID_ENTRY_FLAG_FF, }; use crate::log::*; use std::ffi::CStr; @@ -100,8 +101,10 @@ fn match_iface_version(version: u16, if_data: &DCEIfaceData) -> bool { } } -fn match_backuuid(state: &mut DCERPCState, if_data: &mut DCEIfaceData) -> u8 { - let mut ret = 1; +fn match_backuuid( + tx: &mut DCERPCTransaction, state: &mut DCERPCState, if_data: &mut DCEIfaceData, +) -> u8 { + let mut ret = 0; if let Some(ref bindack) = state.bindack { for uuidentry in bindack.accepted_uuid_list.iter() { ret = 1; @@ -123,7 +126,7 @@ fn match_backuuid(state: &mut DCERPCState, if_data: &mut DCEIfaceData) -> u8 { break; } } - let ctxid = state.get_req_ctxid().unwrap_or(0); + let ctxid = tx.get_req_ctxid(); ret = ret & ((uuidentry.ctxid == ctxid) as u8); if ret == 0 { SCLogDebug!("CTX IDs/UUIDs do not match"); @@ -243,16 +246,17 @@ fn parse_opnum_data(arg: &str) -> Result { } #[no_mangle] -pub extern "C" fn rs_dcerpc_iface_match(state: &mut DCERPCState, if_data: &mut DCEIfaceData) -> u8 { - let first_req_seen = state.get_first_req_seen().unwrap_or(0); +pub extern "C" fn rs_dcerpc_iface_match( + tx: &mut DCERPCTransaction, state: &mut DCERPCState, if_data: &mut DCEIfaceData, +) -> u8 { + let first_req_seen = tx.get_first_req_seen(); if first_req_seen == 0 { return 0; } match state.get_hdr_type() { Some(x) => match x { - DCERPC_TYPE_REQUEST | DCERPC_TYPE_RESPONSE => { - } + DCERPC_TYPE_REQUEST | DCERPC_TYPE_RESPONSE => {} _ => { return 0; } @@ -262,7 +266,7 @@ pub extern "C" fn rs_dcerpc_iface_match(state: &mut DCERPCState, if_data: &mut D } }; - return match_backuuid(state, if_data); + return match_backuuid(tx, state, if_data); } #[no_mangle] @@ -292,10 +296,13 @@ pub unsafe extern "C" fn rs_dcerpc_iface_free(ptr: *mut c_void) { #[no_mangle] pub unsafe extern "C" fn rs_dcerpc_opnum_match( - state: &mut DCERPCState, - opnum_data: &mut DCEOpnumData, + tx: &mut DCERPCTransaction, opnum_data: &mut DCEOpnumData, ) -> u8 { - let opnum = state.get_req_opnum().unwrap_or(0); // TODO is the default to 0 OK? + let first_req_seen = tx.get_first_req_seen(); + if first_req_seen == 0 { + return 0; + } + let opnum = tx.get_req_opnum(); for range in opnum_data.data.iter() { if range.range2 == DETECT_DCE_OPNUM_RANGE_UNINITIALIZED { if range.range1 == opnum as u32 { diff --git a/rust/src/dcerpc/parser.rs b/rust/src/dcerpc/parser.rs index 0356479e89a..a7552a40cfd 100644 --- a/rust/src/dcerpc/parser.rs +++ b/rust/src/dcerpc/parser.rs @@ -256,10 +256,6 @@ named_args!(pub parse_dcerpc_request(endianness: Endianness) , ctxid: ctxid, opnum: opnum, first_request_seen: 1, - stub_data_buffer: Vec::new(), - stub_data_buffer_len: 0, - stub_data_buffer_reset: false, - cmd: 0, } ) ) diff --git a/src/detect-dce-iface.c b/src/detect-dce-iface.c index 98e379b84c2..589ca37d54a 100644 --- a/src/detect-dce-iface.c +++ b/src/detect-dce-iface.c @@ -126,7 +126,7 @@ static int DetectDceIfaceMatchRust(DetectEngineThreadCtx *det_ctx, if (f->alproto == ALPROTO_DCERPC) { // TODO check if state is NULL - return rs_dcerpc_iface_match(state, (void *)m); + return rs_dcerpc_iface_match(txv, state, (void *)m); } int ret = 0; diff --git a/src/detect-dce-opnum.c b/src/detect-dce-opnum.c index ab46d96a504..fc64e316709 100644 --- a/src/detect-dce-opnum.c +++ b/src/detect-dce-opnum.c @@ -100,8 +100,7 @@ static int DetectDceOpnumMatchRust(DetectEngineThreadCtx *det_ctx, SCEnter(); if (f->alproto == ALPROTO_DCERPC) { - // TODO check for NULL state - return rs_dcerpc_opnum_match(state, (void *)m); + return rs_dcerpc_opnum_match(txv, (void *)m); } if (rs_smb_tx_match_dce_opnum(txv, (void *)m) != 1)