diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs index 9a3303f6e2..89ef3bbb5a 100644 --- a/rust/src/smb/dcerpc.rs +++ b/rust/src/smb/dcerpc.rs @@ -149,6 +149,7 @@ pub fn dcerpc_uuid_to_string(i: &DCERPCIface) -> String { pub struct SMBTransactionDCERPC { pub opnum: u16, pub req_cmd: u8, + pub req_set: bool, pub res_cmd: u8, pub res_set: bool, pub call_id: u32, @@ -159,10 +160,25 @@ pub struct SMBTransactionDCERPC { } impl SMBTransactionDCERPC { - pub fn new(req: u8, call_id: u32) -> SMBTransactionDCERPC { + fn new_request(req: u8, call_id: u32) -> SMBTransactionDCERPC { return SMBTransactionDCERPC { opnum: 0, req_cmd: req, + req_set: true, + res_cmd: 0, + res_set: false, + call_id: call_id, + frag_cnt_ts: 0, + frag_cnt_tc: 0, + stub_data_ts:Vec::new(), + stub_data_tc:Vec::new(), + } + } + fn new_response(call_id: u32) -> SMBTransactionDCERPC { + return SMBTransactionDCERPC { + opnum: 0, + req_cmd: 0, + req_set: false, res_cmd: 0, res_set: false, call_id: call_id, @@ -179,14 +195,14 @@ impl SMBTransactionDCERPC { } impl SMBState { - pub fn new_dcerpc_tx(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32) + fn new_dcerpc_tx(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32) -> (&mut SMBTransaction) { let mut tx = self.new_tx(); tx.hdr = hdr; tx.vercmd = vercmd; tx.type_data = Some(SMBTransactionTypeData::DCERPC( - SMBTransactionDCERPC::new(cmd, call_id))); + SMBTransactionDCERPC::new_request(cmd, call_id))); SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); self.transactions.push(tx); @@ -194,7 +210,22 @@ impl SMBState { return tx_ref.unwrap(); } - pub fn get_dcerpc_tx(&mut self, hdr: &SMBCommonHdr, vercmd: &SMBVerCmdStat, call_id: u32) + fn new_dcerpc_tx_for_response(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, call_id: u32) + -> (&mut SMBTransaction) + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.vercmd = vercmd; + tx.type_data = Some(SMBTransactionTypeData::DCERPC( + SMBTransactionDCERPC::new_response(call_id))); + + SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); + self.transactions.push(tx); + let tx_ref = self.transactions.last_mut(); + return tx_ref.unwrap(); + } + + fn get_dcerpc_tx(&mut self, hdr: &SMBCommonHdr, vercmd: &SMBVerCmdStat, call_id: u32) -> Option<&mut SMBTransaction> { let dce_hdr = hdr.to_dcerpc(vercmd); @@ -422,6 +453,51 @@ fn smb_read_dcerpc_record_error(state: &mut SMBState, return found; } +fn dcerpc_response_handle<'b>(tx: &mut SMBTransaction, + vercmd: SMBVerCmdStat, + dcer: &DceRpcRecord) +{ + let (_, ntstatus) = vercmd.get_ntstatus(); + match dcer.packet_type { + DCERPC_TYPE_RESPONSE => { + match parse_dcerpc_response_record(dcer.data, dcer.frag_len) { + IResult::Done(_, respr) => { + SCLogDebug!("SMBv1 READ RESPONSE {:?}", respr); + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + SCLogDebug!("CMD 11 found at tx {}", tx.id); + tdn.set_result(DCERPC_TYPE_RESPONSE); + tdn.stub_data_tc.extend_from_slice(&respr.data); + tdn.frag_cnt_tc += 1; + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = dcer.last_frag; + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + }, + DCERPC_TYPE_BINDACK => { + // handled elsewhere + }, + 21...255 => { + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(dcer.packet_type); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + tx.set_event(SMBEvent::MalformedData); + } + _ => { // valid type w/o special processing + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(dcer.packet_type); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + }, + } +} + /// Handle DCERPC reply record. Called for READ, TRANS, IOCTL /// pub fn smb_read_dcerpc_record<'b>(state: &mut SMBState, @@ -432,19 +508,17 @@ pub fn smb_read_dcerpc_record<'b>(state: &mut SMBState, { let (_, ntstatus) = vercmd.get_ntstatus(); - if ntstatus != SMB_NTSTATUS_SUCCESS { + if ntstatus != SMB_NTSTATUS_SUCCESS && ntstatus != SMB_NTSTATUS_BUFFER_OVERFLOW { return smb_read_dcerpc_record_error(state, hdr, vercmd, ntstatus); } SCLogDebug!("lets first see if we have prior data"); // msg_id 0 as this data crosses cmd/reply pairs - let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_TRANS_FRAG, - hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64); - let ekey = SMBHashKeyHdrGuid::new(ehdr, guid.to_vec()); - SCLogDebug!("ekey {:?}", ekey); - let mut prevdata = match state.ssnguid2vec_map.remove(&ekey) { + let ehdr = SMBHashKeyHdrGuid::new(SMBCommonHdr::new(SMBHDR_TYPE_TRANS_FRAG, + hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64), guid.to_vec()); + let mut prevdata = match state.ssnguid2vec_map.remove(&ehdr) { Some(s) => s, - None => Vec::new(), + None => Vec::new(), }; SCLogDebug!("indata {} prevdata {}", indata.len(), prevdata.len()); prevdata.extend_from_slice(&indata); @@ -464,31 +538,10 @@ pub fn smb_read_dcerpc_record<'b>(state: &mut SMBState, dcer.version_major, dcer.version_minor, dcer.data.len(), dcer); if ntstatus == SMB_NTSTATUS_BUFFER_OVERFLOW && data.len() < dcer.frag_len as usize { - SCLogDebug!("short record {} < {}: lets see if we expected this", data.len(), dcer.frag_len); - let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_MAX_SIZE, - hdr.ssn_id as u64, hdr.tree_id as u32, hdr.msg_id as u64); - let max_size = match state.ssn2maxsize_map.remove(&ehdr) { - Some(s) => s, - None => 0, - }; - if max_size as usize == data.len() { - SCLogDebug!("YES this is expected. We should now see a follow up READ for {} bytes", - dcer.frag_len as usize - data.len()); - let store = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { - Some(_) => true, - None => { - SCLogDebug!("no TX found"); - false - }, - }; - if store { - SCLogDebug!("storing partial data in state"); - let ehdr = SMBCommonHdr::new(SMBHDR_TYPE_MAX_SIZE, - hdr.ssn_id as u64, hdr.tree_id as u32, 0 as u64); - state.ssn2vec_map.insert(ehdr, data.to_vec()); - } - return true; // TODO review - } + SCLogDebug!("short record {} < {}: storing partial data in state", + data.len(), dcer.frag_len); + state.ssnguid2vec_map.insert(ehdr, data.to_vec()); + return true; // TODO review } if dcer.packet_type == DCERPC_TYPE_BINDACK { @@ -496,50 +549,20 @@ pub fn smb_read_dcerpc_record<'b>(state: &mut SMBState, return true; } - let tx = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { - Some(tx) => tx, + let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => { + dcerpc_response_handle(tx, vercmd.clone(), &dcer); + true + }, None => { SCLogDebug!("no tx"); - return false; }, - }; - - match dcer.packet_type { - DCERPC_TYPE_RESPONSE => { - match parse_dcerpc_response_record(dcer.data, dcer.frag_len) { - IResult::Done(_, respr) => { - SCLogDebug!("SMBv1 READ RESPONSE {:?}", respr); - if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { - SCLogDebug!("CMD 11 found at tx {}", tx.id); - tdn.set_result(DCERPC_TYPE_RESPONSE); - tdn.stub_data_tc.extend_from_slice(&respr.data); - tdn.frag_cnt_tc += 1; - } - tx.vercmd.set_ntstatus(ntstatus); - tx.response_done = dcer.last_frag; - }, - _ => { - tx.set_event(SMBEvent::MalformedData); - }, - } - }, - DCERPC_TYPE_BINDACK => { - // handled above - }, - 21...255 => { - if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { - tdn.set_result(dcer.packet_type); - } - tx.vercmd.set_ntstatus(ntstatus); - tx.response_done = true; - tx.set_event(SMBEvent::MalformedData); - } - _ => { // valid type w/o special processing - if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { - tdn.set_result(dcer.packet_type); - } - tx.vercmd.set_ntstatus(ntstatus); - tx.response_done = true; + false }, + }; + if !found { + // pick up DCERPC tx even if we missed the request + let tx = state.new_dcerpc_tx_for_response(hdr, vercmd.clone(), dcer.call_id); + dcerpc_response_handle(tx, vercmd, &dcer); } }, _ => { diff --git a/rust/src/smb/log.rs b/rust/src/smb/log.rs index 48c531feb1..2209c19cd5 100644 --- a/rust/src/smb/log.rs +++ b/rust/src/smb/log.rs @@ -312,49 +312,63 @@ fn smb_common_header(state: &SMBState, tx: &SMBTransaction) -> Json }, Some(SMBTransactionTypeData::DCERPC(ref x)) => { let jsd = Json::object(); - jsd.set_string("request", &dcerpc_type_string(x.req_cmd)); + if x.req_set { + jsd.set_string("request", &dcerpc_type_string(x.req_cmd)); + } else { + jsd.set_string("request", "REQUEST_LOST"); + } if x.res_set { jsd.set_string("response", &dcerpc_type_string(x.res_cmd)); } else { jsd.set_string("response", "UNREPLIED"); } - match x.req_cmd { - DCERPC_TYPE_REQUEST => { - jsd.set_integer("opnum", x.opnum as u64); - let req = Json::object(); - req.set_integer("frag_cnt", x.frag_cnt_ts as u64); - req.set_integer("stub_data_size", x.stub_data_ts.len() as u64); - jsd.set("req", req); - let res = Json::object(); - res.set_integer("frag_cnt", x.frag_cnt_tc as u64); - res.set_integer("stub_data_size", x.stub_data_tc.len() as u64); - jsd.set("res", res); - }, - DCERPC_TYPE_BIND => { - match state.dcerpc_ifaces { - Some(ref ifaces) => { - let jsa = Json::array(); - for i in ifaces { - let jso = Json::object(); - let ifstr = dcerpc_uuid_to_string(&i); - jso.set_string("uuid", &ifstr); - let vstr = format!("{}.{}", i.ver, i.ver_min); - jso.set_string("version", &vstr); - - if i.acked { - jso.set_integer("ack_result", i.ack_result as u64); - jso.set_integer("ack_reason", i.ack_reason as u64); + if x.req_set { + match x.req_cmd { + DCERPC_TYPE_REQUEST => { + jsd.set_integer("opnum", x.opnum as u64); + let req = Json::object(); + req.set_integer("frag_cnt", x.frag_cnt_ts as u64); + req.set_integer("stub_data_size", x.stub_data_ts.len() as u64); + jsd.set("req", req); + }, + DCERPC_TYPE_BIND => { + match state.dcerpc_ifaces { + Some(ref ifaces) => { + let jsa = Json::array(); + for i in ifaces { + let jso = Json::object(); + let ifstr = dcerpc_uuid_to_string(&i); + jso.set_string("uuid", &ifstr); + let vstr = format!("{}.{}", i.ver, i.ver_min); + jso.set_string("version", &vstr); + + if i.acked { + jso.set_integer("ack_result", i.ack_result as u64); + jso.set_integer("ack_reason", i.ack_reason as u64); + } + + jsa.array_append(jso); } - jsa.array_append(jso); - } - - jsd.set("interfaces", jsa); - }, - _ => {}, - } - }, - _ => {}, + jsd.set("interfaces", jsa); + }, + _ => {}, + } + }, + _ => {}, + } + } + if x.res_set { + match x.res_cmd { + DCERPC_TYPE_RESPONSE => { + let res = Json::object(); + res.set_integer("frag_cnt", x.frag_cnt_tc as u64); + res.set_integer("stub_data_size", x.stub_data_tc.len() as u64); + jsd.set("res", res); + }, + // we don't handle BINDACK w/o BIND + _ => {}, + } } jsd.set_integer("call_id", x.call_id as u64); js.set("dcerpc", jsd); diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs index 84d0e1b303..0e4ddc3554 100644 --- a/rust/src/smb/smb.rs +++ b/rust/src/smb/smb.rs @@ -1119,6 +1119,7 @@ impl SMBState { Ok("lsarpc") => ("lsarpc", true), Ok("samr") => ("samr", true), Ok("spoolss") => ("spoolss", true), + Ok("suricata::dcerpc") => ("unknown", true), Err(_) => ("MALFORMED", false), Ok(&_) => { SCLogDebug!("don't know {}", String::from_utf8_lossy(&n)); diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs index fad9dcaa19..14e0b7b1ba 100644 --- a/rust/src/smb/smb1.rs +++ b/rust/src/smb/smb1.rs @@ -869,9 +869,9 @@ pub fn smb1_trans_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) // if we get status 'BUFFER_OVERFLOW' this is only a part of // the data. Store it in the ssn/tree for later use. if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { - state.ssnguid2vec_map.insert(SMBHashKeyHdrGuid::new( - SMBCommonHdr::from1(r, SMBHDR_TYPE_TRANS_FRAG), fid), - rd.data.to_vec()); + let key = SMBHashKeyHdrGuid::new(SMBCommonHdr::from1(r, SMBHDR_TYPE_TRANS_FRAG), fid); + SCLogDebug!("SMBv1/TRANS: queueing data for len {} key {:?}", rd.data.len(), key); + state.ssnguid2vec_map.insert(key, rd.data.to_vec()); } else if is_dcerpc { SCLogDebug!("SMBv1 TRANS TO PIPE"); let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs index 67d0d970da..f7454bb7b8 100644 --- a/rust/src/smb/smb2.rs +++ b/rust/src/smb/smb2.rs @@ -19,6 +19,7 @@ use core::*; use log::*; use nom::IResult; +use smb; use smb::smb::*; use smb::smb2_records::*; use smb::smb2_session::*; @@ -118,7 +119,11 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) match parse_smb2_response_read(r.data) { IResult::Done(_, rd) => { - if r.nt_status != SMB_NTSTATUS_SUCCESS { + if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { + SCLogDebug!("SMBv2/READ: incomplete record, expecting a follow up"); + // fall through + + } else if r.nt_status != SMB_NTSTATUS_SUCCESS { SCLogDebug!("SMBv2: read response error code received: skip record"); state.set_skip(STREAM_TOCLIENT, rd.len, rd.data.len() as u32); return; @@ -152,19 +157,48 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) }, None => { false }, }; + SCLogDebug!("existing file tx? {}", found); if !found { let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); - let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) { + let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) { Some(n) => (n.name.to_vec(), n.is_pipe), _ => { (Vec::new(), false) }, }; - let is_dcerpc = is_pipe && match state.get_service_for_guid(&file_guid) { + let mut is_dcerpc = is_pipe && match state.get_service_for_guid(&file_guid) { (_, x) => x, }; + SCLogDebug!("SMBv2/READ: share_name {:?} is_pipe {} is_dcerpc {}", share_name, is_pipe, is_dcerpc); + + if share_name.len() == 0 && !is_pipe { + SCLogDebug!("SMBv2/READ: no tree connect seen, we don't know if we are a pipe"); + + match smb::dcerpc_records::parse_dcerpc_record(rd.data) { + IResult::Done(_, recr) => { + SCLogDebug!("SMBv2/READ: could be DCERPC {:?}", recr); + if recr.version_major == 5 && recr.version_minor < 3 && + recr.frag_len > 0 && recr.packet_type <= 20 { + SCLogDebug!("SMBv2/READ: looks like dcerpc"); + // insert fake tree to assist in follow up lookups + let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); + state.ssn2tree_map.insert(tree_key, tree); + state.guid2name_map.insert(file_guid.to_vec(), b"suricata::dcerpc".to_vec()); + + is_pipe = true; + is_dcerpc = true; + } else { + SCLogDebug!("SMBv2/READ: not DCERPC"); + } + }, + _ => { + SCLogDebug!("SMBv2/READ: not DCERPC"); + }, + } + } + if is_pipe && is_dcerpc { SCLogDebug!("SMBv2 DCERPC read"); let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); - let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_READ); + let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_READ, r.nt_status); smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data); } else if is_pipe { SCLogDebug!("non-DCERPC pipe"); @@ -191,6 +225,7 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) state.set_file_left(STREAM_TOCLIENT, rd.len, rd.data.len() as u32, file_guid.to_vec()); } _ => { + SCLogDebug!("SMBv2: failed to parse read response"); state.set_event(SMBEvent::MalformedData); } } @@ -198,6 +233,7 @@ pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) { + SCLogDebug!("SMBv2/WRITE: request record"); if smb2_create_new_tx(r.command) { let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); let tx = state.new_generic_tx(2, r.command, tx_key); @@ -229,13 +265,47 @@ pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) }; if !found { let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); - let is_pipe = match state.ssn2tree_map.get(&tree_key) { - Some(n) => { n.is_pipe }, - _ => { false }, + let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => { (n.name.to_vec(), n.is_pipe) }, + _ => { (Vec::new(), false) }, }; - let is_dcerpc = is_pipe && match state.get_service_for_guid(&wr.guid) { - (_, x) => x, + let mut is_dcerpc = if is_pipe || (share_name.len() == 0 && !is_pipe) { + match state.get_service_for_guid(&wr.guid) { + (_, x) => x, + } + } else { + false }; + SCLogDebug!("share_name {:?} is_pipe {} is_dcerpc {}", share_name, is_pipe, is_dcerpc); + + // if we missed the TREE connect we can't be sure if 'is_dcerpc' is correct + if share_name.len() == 0 && !is_pipe { + SCLogDebug!("SMBv2/WRITE: no tree connect seen, we don't know if we are a pipe"); + + match smb::dcerpc_records::parse_dcerpc_record(wr.data) { + IResult::Done(_, recr) => { + SCLogDebug!("SMBv2/WRITE: could be DCERPC {:?}", recr); + if recr.version_major == 5 && recr.version_minor < 3 && + recr.frag_len > 0 && recr.packet_type <= 20 { + SCLogDebug!("SMBv2/WRITE: looks like we have dcerpc"); + + let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); + state.ssn2tree_map.insert(tree_key, tree); + if !is_dcerpc { + state.guid2name_map.insert(file_guid.to_vec(), + b"suricata::dcerpc".to_vec()); + } + is_pipe = true; + is_dcerpc = true; + } else { + SCLogDebug!("SMBv2/WRITE: not DCERPC"); + } + }, + _ => { + SCLogDebug!("SMBv2/WRITE: not DCERPC"); + }, + } + } if is_pipe && is_dcerpc { SCLogDebug!("SMBv2 DCERPC write"); let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); @@ -522,7 +592,8 @@ pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) false // the request may have created a generic tx, so handle that here }, SMB2_COMMAND_READ => { - if r.nt_status == SMB_NTSTATUS_SUCCESS { + if r.nt_status == SMB_NTSTATUS_SUCCESS || + r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { smb2_read_response_record(state, &r); false