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,
}
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,
}
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);
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);
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,
{
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);
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 {
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);
}
},
_ => {
},
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);
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));
// 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);
use log::*;
use nom::IResult;
+use smb;
use smb::smb::*;
use smb::smb2_records::*;
use smb::smb2_session::*;
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;
},
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");
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);
}
}
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);
};
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);
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