From 4c7f55e636e2c08f8d96c5e02b1a1f5c4045fd8f Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Fri, 26 Jun 2020 01:32:59 +0530 Subject: [PATCH] dcerpc: handle gap for TCP streams --- rust/src/dcerpc/dcerpc.rs | 145 ++++++++++++++++++++++++++++++++++---- rust/src/dcerpc/log.rs | 4 +- src/app-layer-dcerpc.c | 2 + 3 files changed, 134 insertions(+), 17 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index d271ab3dd7..edb2d7e6ae 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -16,13 +16,13 @@ */ use std::mem::transmute; - use crate::applayer::{AppLayerResult, AppLayerTxData}; use crate::core; use crate::dcerpc::parser; use crate::log::*; use nom::error::ErrorKind; use nom::number::Endianness; +use nom; use std::cmp; // Constant DCERPC UDP Header length @@ -149,6 +149,8 @@ pub struct DCERPCTransaction { pub stub_data_buffer_reset_tc: bool, pub req_done: bool, pub resp_done: bool, + pub req_lost: bool, + pub resp_lost: bool, pub req_cmd: u8, pub resp_cmd: u8, pub tx_data: AppLayerTxData, @@ -174,6 +176,8 @@ impl DCERPCTransaction { stub_data_buffer_reset_tc: false, req_done: false, resp_done: false, + req_lost: false, + resp_lost: false, req_cmd: DCERPC_TYPE_REQUEST, resp_cmd: DCERPC_TYPE_RESPONSE, tx_data: AppLayerTxData::new(), @@ -299,6 +303,10 @@ pub struct DCERPCState { pub prev_dir: u8, pub prev_tx_call_id: u32, pub clear_bind_cache: bool, + pub ts_gap: bool, + pub tc_gap: bool, + pub ts_ssn_gap: bool, + pub tc_ssn_gap: bool, } impl DCERPCState { @@ -319,6 +327,10 @@ impl DCERPCState { prev_dir: core::STREAM_TOSERVER, prev_tx_call_id: 0, clear_bind_cache: false, + ts_gap: false, + tc_gap: false, + ts_ssn_gap: false, + tc_ssn_gap: false, }; } @@ -410,9 +422,11 @@ impl DCERPCState { match direction { core::STREAM_TOSERVER => { self.buffer_ts.clear(); + self.ts_gap = false; } _ => { self.buffer_tc.clear(); + self.tc_gap = false; } } self.bytes_consumed = 0; @@ -510,6 +524,62 @@ impl DCERPCState { self.prev_tx_call_id = call_id; } + pub fn parse_data_gap(&mut self, direction: u8) -> AppLayerResult { + match direction { + core::STREAM_TOSERVER => { + self.ts_gap = true; + self.ts_ssn_gap = true; + }, + _ => { + self.tc_gap = true; + self.tc_ssn_gap = true; + }, + } + AppLayerResult::ok() + } + + pub fn post_gap_housekeeping(&mut self, dir: u8) { + SCLogDebug!("ts ssn gap: {:?}, tc ssn gap: {:?}, dir: {:?}", self.ts_ssn_gap, self.tc_ssn_gap, dir); + if self.ts_ssn_gap && dir == core::STREAM_TOSERVER { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if tx.req_done == false { + tx.req_lost = true; + } + tx.req_done = true; + } + } else if self.tc_ssn_gap && dir == core::STREAM_TOCLIENT { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if tx.req_done == false { + tx.req_lost = true; + } + if tx.resp_done == false { + tx.resp_lost = true; + } + tx.req_done = true; + tx.resp_done = true; + } + } + } + + pub fn search_dcerpc_record<'a>(&mut self, i: &'a[u8]) -> nom::IResult<&'a[u8], &'a[u8]> { + let mut d = i; + while d.len() >= 2 { + if d[0] == 0x05 && d[1] == 0x00 { + return Ok((&d[2..], d)); + } + d = &d[1..]; + } + Err(nom::Err::Incomplete(nom::Needed::Size(2 as usize - d.len()))) + } + /// Makes a call to the nom parser for parsing DCERPC Header. /// /// Arguments: @@ -810,10 +880,49 @@ impl DCERPCState { pub fn handle_input_data(&mut self, input: &[u8], direction: u8) -> AppLayerResult { let mut parsed; let retval; - let input_len = input.len(); + let mut cur_i = input; + let input_len = cur_i.len(); let mut v: Vec; // Set any query's completion status to false in the beginning self.query_completed = false; + + // Skip the record since this means that its in the middle of a known length record + if self.ts_gap || self.tc_gap { + SCLogDebug!("Trying to catch up after GAP (input {})", cur_i.len()); + while cur_i.len() > 0 { // min record size + match self.search_dcerpc_record(cur_i) { + Ok((_, pg)) => { + SCLogDebug!("DCERPC record found"); + let offset = cur_i.len() - pg.len(); + if offset == 1 { + cur_i = &cur_i[offset + 2..]; + continue; // see if we have another record in our data + } + match direction { + core::STREAM_TOSERVER => { + self.ts_gap = false; + break; + }, + _ => { + self.tc_gap = false; + break; + } + } + }, + _ => { + let mut consumed = cur_i.len(); + if consumed < 2 { + consumed = 0; + } else { + consumed = consumed - 1; + } + SCLogDebug!("DCERPC record NOT found"); + return AppLayerResult::incomplete(consumed as u32, 2); + }, + } + } + } + // Overwrite the dcerpc_state data in case of multiple complete queries in the // same direction if self.prev_dir == direction { @@ -827,7 +936,7 @@ impl DCERPCState { return AppLayerResult::err(); } v = self.buffer_ts.split_off(0); - v.extend_from_slice(input); + v.extend_from_slice(cur_i); v.as_slice() } _ => { @@ -836,7 +945,7 @@ impl DCERPCState { return AppLayerResult::err(); } v = self.buffer_tc.split_off(0); - v.extend_from_slice(input); + v.extend_from_slice(cur_i); v.as_slice() } }; @@ -934,7 +1043,7 @@ impl DCERPCState { self.handle_bind_cache(current_call_id, true); } _ => { - SCLogDebug!("Unrecognized packet type"); + SCLogDebug!("Unrecognized packet type: {:?}", x); self.clean_buffer(direction); return AppLayerResult::err(); } @@ -950,6 +1059,7 @@ impl DCERPCState { self.clean_buffer(direction); self.reset_direction(direction); } + self.post_gap_housekeeping(direction); self.prev_dir = direction; return AppLayerResult::ok(); } @@ -978,22 +1088,18 @@ 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(); - } - AppLayerResult::err() + state.parse_data_gap(core::STREAM_TOSERVER) } #[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(); - } - AppLayerResult::err() + state.parse_data_gap(core::STREAM_TOCLIENT) } #[no_mangle] @@ -1001,6 +1107,11 @@ 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, ) -> AppLayerResult { + SCLogDebug!("Handling request"); + /* START with MIDSTREAM set: record might be starting the middle. */ + if flags & (core::STREAM_START|core::STREAM_MIDSTREAM) == (core::STREAM_START|core::STREAM_MIDSTREAM) { + state.ts_gap = true; + } if input_len > 0 && input != std::ptr::null_mut() { let buf = build_slice!(input, input_len as usize); return state.handle_input_data(buf, flags); @@ -1013,6 +1124,10 @@ 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, ) -> AppLayerResult { + /* START with MIDSTREAM set: record might be starting the middle. */ + if flags & (core::STREAM_START|core::STREAM_MIDSTREAM) == (core::STREAM_START|core::STREAM_MIDSTREAM) { + state.tc_gap = true; + } if input_len > 0 { if input != std::ptr::null_mut() { let buf = build_slice!(input, input_len as usize); diff --git a/rust/src/dcerpc/log.rs b/rust/src/dcerpc/log.rs index fd13b82a9e..349dbae1e4 100644 --- a/rust/src/dcerpc/log.rs +++ b/rust/src/dcerpc/log.rs @@ -22,7 +22,7 @@ use crate::jsonbuilder::{JsonBuilder, JsonError}; fn log_dcerpc_header( jsb: &mut JsonBuilder, state: &DCERPCState, tx: &DCERPCTransaction, ) -> Result<(), JsonError> { - if tx.req_done == true { + if tx.req_done == true && tx.req_lost == false { jsb.set_string("request", &dcerpc_type_string(tx.req_cmd))?; match tx.req_cmd { DCERPC_TYPE_REQUEST => { @@ -55,7 +55,7 @@ fn log_dcerpc_header( jsb.set_string("request", "REQUEST_LOST")?; } - if tx.resp_done == true { + if tx.resp_done == true && tx.resp_lost == false { jsb.set_string("response", &dcerpc_type_string(tx.resp_cmd))?; match tx.resp_cmd { DCERPC_TYPE_RESPONSE => { diff --git a/src/app-layer-dcerpc.c b/src/app-layer-dcerpc.c index e9cffaca44..bb0d1baac9 100644 --- a/src/app-layer-dcerpc.c +++ b/src/app-layer-dcerpc.c @@ -180,6 +180,8 @@ void RegisterDCERPCParsers(void) AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_DCERPC, DCERPCGetAlstateProgressCompletionStatus); + /* This parser accepts gaps. */ + AppLayerParserRegisterOptionFlags(IPPROTO_TCP, ALPROTO_DCERPC, APP_LAYER_PARSER_OPT_ACCEPT_GAPS); } else { SCLogInfo("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name);