diff options
Diffstat (limited to 'rust/src/smb/smb2.rs')
-rw-r--r-- | rust/src/smb/smb2.rs | 913 |
1 files changed, 913 insertions, 0 deletions
diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs new file mode 100644 index 0000000..a2fe021 --- /dev/null +++ b/rust/src/smb/smb2.rs @@ -0,0 +1,913 @@ +/* Copyright (C) 2017-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. + */ + +use nom7::Err; + +use crate::core::*; + +use crate::smb::smb::*; +use crate::smb::smb2_records::*; +use crate::smb::smb2_session::*; +use crate::smb::smb2_ioctl::*; +use crate::smb::dcerpc::*; +use crate::smb::events::*; +use crate::smb::files::*; +use crate::smb::smb_status::*; + +pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0; +pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1; +pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2; +pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3; +pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4; +pub const SMB2_COMMAND_CREATE: u16 = 5; +pub const SMB2_COMMAND_CLOSE: u16 = 6; +pub const SMB2_COMMAND_FLUSH: u16 = 7; +pub const SMB2_COMMAND_READ: u16 = 8; +pub const SMB2_COMMAND_WRITE: u16 = 9; +pub const SMB2_COMMAND_LOCK: u16 = 10; +pub const SMB2_COMMAND_IOCTL: u16 = 11; +pub const SMB2_COMMAND_CANCEL: u16 = 12; +pub const SMB2_COMMAND_KEEPALIVE: u16 = 13; +pub const SMB2_COMMAND_FIND: u16 = 14; +pub const SMB2_COMMAND_CHANGE_NOTIFY: u16 = 15; +pub const SMB2_COMMAND_GET_INFO: u16 = 16; +pub const SMB2_COMMAND_SET_INFO: u16 = 17; +pub const SMB2_COMMAND_OPLOCK_BREAK: u16 = 18; + +pub fn smb2_command_string(c: u16) -> String { + match c { + SMB2_COMMAND_NEGOTIATE_PROTOCOL => "SMB2_COMMAND_NEGOTIATE_PROTOCOL", + SMB2_COMMAND_SESSION_SETUP => "SMB2_COMMAND_SESSION_SETUP", + SMB2_COMMAND_SESSION_LOGOFF => "SMB2_COMMAND_SESSION_LOGOFF", + SMB2_COMMAND_TREE_CONNECT => "SMB2_COMMAND_TREE_CONNECT", + SMB2_COMMAND_TREE_DISCONNECT => "SMB2_COMMAND_TREE_DISCONNECT", + SMB2_COMMAND_CREATE => "SMB2_COMMAND_CREATE", + SMB2_COMMAND_CLOSE => "SMB2_COMMAND_CLOSE", + SMB2_COMMAND_READ => "SMB2_COMMAND_READ", + SMB2_COMMAND_FLUSH => "SMB2_COMMAND_FLUSH", + SMB2_COMMAND_WRITE => "SMB2_COMMAND_WRITE", + SMB2_COMMAND_LOCK => "SMB2_COMMAND_LOCK", + SMB2_COMMAND_IOCTL => "SMB2_COMMAND_IOCTL", + SMB2_COMMAND_CANCEL => "SMB2_COMMAND_CANCEL", + SMB2_COMMAND_KEEPALIVE => "SMB2_COMMAND_KEEPALIVE", + SMB2_COMMAND_FIND => "SMB2_COMMAND_FIND", + SMB2_COMMAND_CHANGE_NOTIFY => "SMB2_COMMAND_CHANGE_NOTIFY", + SMB2_COMMAND_GET_INFO => "SMB2_COMMAND_GET_INFO", + SMB2_COMMAND_SET_INFO => "SMB2_COMMAND_SET_INFO", + SMB2_COMMAND_OPLOCK_BREAK => "SMB2_COMMAND_OPLOCK_BREAK", + _ => { return (c).to_string(); }, + }.to_string() + +} + +pub fn smb2_dialect_string(d: u16) -> String { + match d { + 0x0202 => "2.02", + 0x0210 => "2.10", + 0x0222 => "2.22", + 0x0224 => "2.24", + 0x02ff => "2.??", + 0x0300 => "3.00", + 0x0302 => "3.02", + 0x0310 => "3.10", + 0x0311 => "3.11", + _ => { return (d).to_string(); }, + }.to_string() +} + +// later we'll use this to determine if we need to +// track a ssn per type +fn smb2_create_new_tx(cmd: u16) -> bool { + match cmd { + SMB2_COMMAND_READ | + SMB2_COMMAND_WRITE | + SMB2_COMMAND_GET_INFO | + SMB2_COMMAND_SET_INFO => { false }, + _ => { true }, + } +} + +fn smb2_read_response_record_generic(state: &mut SMBState, r: &Smb2Record) +{ + if smb2_create_new_tx(r.command) { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.get_generic_tx(2, r.command, &tx_hdr); + if let Some(tx) = tx { + tx.set_status(r.nt_status, false); + tx.response_done = true; + } + } +} + +pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) +{ + let max_queue_size = unsafe { SMB_CFG_MAX_READ_QUEUE_SIZE }; + let max_queue_cnt = unsafe { SMB_CFG_MAX_READ_QUEUE_CNT }; + + smb2_read_response_record_generic(state, r); + + match parse_smb2_response_read(r.data) { + Ok((_, rd)) => { + if rd.len - rd.data.len() as u32 > nbss_remaining { + // Record claims more bytes than are in NBSS record... + state.set_event(SMBEvent::ReadResponseTooLarge); + // Skip the remaining bytes of the record. + state.set_skip(Direction::ToClient, nbss_remaining); + return; + } + 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(Direction::ToClient, nbss_remaining); + return; + } + + if (state.max_read_size != 0 && rd.len > state.max_read_size) || + (unsafe { SMB_CFG_MAX_READ_SIZE != 0 && SMB_CFG_MAX_READ_SIZE < rd.len }) + { + state.set_event(SMBEvent::ReadResponseTooLarge); + state.set_skip(Direction::ToClient, nbss_remaining); + return; + } + + SCLogDebug!("SMBv2: read response => {:?}", rd); + + // get the request info. If we don't have it, there is nothing + // we can do except skip this record. + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET); + let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) { + Some(o) => (o.offset, o.guid), + None => { + SCLogDebug!("SMBv2 READ response: reply to unknown request {:?}",rd); + state.set_skip(Direction::ToClient, nbss_remaining); + return; + }, + }; + SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset); + + let mut set_event_fileoverlap = false; + // look up existing tracker and if we have it update it + let found = match state.get_file_tx_by_fuid_with_open_file(&file_guid, Direction::ToClient) { + Some(tx) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + rd.len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::ReadQueueSizeExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::ReadQueueCntExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &tdf.file_name, rd.data, offset, + rd.len, false, &file_id); + } + } + true + }, + None => { false }, + }; + SCLogDebug!("existing file tx? {}", found); + if !found { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + 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 mut is_dcerpc = if is_pipe || share_name.is_empty() { + state.get_service_for_guid(&file_guid).1 + } else { + false + }; + SCLogDebug!("SMBv2/READ: share_name {:?} is_pipe {} is_dcerpc {}", + share_name, is_pipe, is_dcerpc); + + if share_name.is_empty() && !is_pipe { + SCLogDebug!("SMBv2/READ: no tree connect seen, we don't know if we are a pipe"); + + if smb_dcerpc_probe(rd.data) { + 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); + 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/READ: not DCERPC"); + } + } + + if is_pipe && is_dcerpc { + SCLogDebug!("SMBv2 DCERPC read"); + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + 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_skip(Direction::ToClient, nbss_remaining); + } else { + let file_name = match state.guid2name_map.get(&file_guid) { + Some(n) => { n.to_vec() } + None => { b"<unknown>".to_vec() } + }; + + let tx = state.new_file_tx(&file_guid, &file_name, Direction::ToClient); + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ); + tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.session_id, r.tree_id, 0); // TODO move into new_file_tx + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + tdf.share_name = share_name; + let file_id : u32 = tx.id as u32; + if offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + rd.len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::ReadQueueSizeExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::ReadQueueCntExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, rd.data, offset, + rd.len, false, &file_id); + } + } + } + } + + if set_event_fileoverlap { + state.set_event(SMBEvent::FileOverlap); + } + state.set_file_left(Direction::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(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) +{ + let max_queue_size = unsafe { SMB_CFG_MAX_WRITE_QUEUE_SIZE }; + let max_queue_cnt = unsafe { SMB_CFG_MAX_WRITE_QUEUE_CNT }; + + 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); + tx.request_done = true; + } + match parse_smb2_request_write(r.data) { + Ok((_, wr)) => { + if wr.wr_len - wr.data.len() as u32 > nbss_remaining { + // Record claims more bytes than are in NBSS record... + state.set_event(SMBEvent::WriteRequestTooLarge); + // Skip the remaining bytes of the record. + state.set_skip(Direction::ToServer, nbss_remaining); + return; + } + if (state.max_write_size != 0 && wr.wr_len > state.max_write_size) || + (unsafe { SMB_CFG_MAX_WRITE_SIZE != 0 && SMB_CFG_MAX_WRITE_SIZE < wr.wr_len }) { + state.set_event(SMBEvent::WriteRequestTooLarge); + state.set_skip(Direction::ToServer, nbss_remaining); + return; + } + + /* update key-guid map */ + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID); + state.ssn2vec_map.insert(guid_key, wr.guid.to_vec()); + + let file_guid = wr.guid.to_vec(); + let file_name = match state.guid2name_map.get(&file_guid) { + Some(n) => n.to_vec(), + None => Vec::new(), + }; + + let mut set_event_fileoverlap = false; + let found = match state.get_file_tx_by_fuid_with_open_file(&file_guid, Direction::ToServer) { + Some(tx) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if wr.wr_offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::WriteQueueSizeExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::WriteQueueCntExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, wr.data, wr.wr_offset, + wr.wr_len, false, &file_id); + } + } + true + }, + None => { false }, + }; + if !found { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + 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 mut is_dcerpc = if is_pipe || share_name.is_empty() { + state.get_service_for_guid(wr.guid).1 + } else { + false + }; + SCLogDebug!("SMBv2/WRITE: 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.is_empty() && !is_pipe { + SCLogDebug!("SMBv2/WRITE: no tree connect seen, we don't know if we are a pipe"); + + if smb_dcerpc_probe(wr.data) { + 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"); + } + } + if is_pipe && is_dcerpc { + SCLogDebug!("SMBv2 DCERPC write"); + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE); + smb_write_dcerpc_record(state, vercmd, hdr, wr.data); + } else if is_pipe { + SCLogDebug!("non-DCERPC pipe: skip rest of the record"); + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + let tx = state.new_file_tx(&file_guid, &file_name, Direction::ToServer); + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE); + tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.session_id, r.tree_id, 0); // TODO move into new_file_tx + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if wr.wr_offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::WriteQueueSizeExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::WriteQueueCntExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, wr.data, wr.wr_offset, + wr.wr_len, false, &file_id); + } + } + } + } + + if set_event_fileoverlap { + state.set_event(SMBEvent::FileOverlap); + } + state.set_file_left(Direction::ToServer, wr.wr_len, wr.data.len() as u32, file_guid.to_vec()); + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +pub fn smb2_request_record(state: &mut SMBState, r: &Smb2Record) +{ + SCLogDebug!("SMBv2 request record, command {} tree {} session {}", + &smb2_command_string(r.command), r.tree_id, r.session_id); + + let mut events : Vec<SMBEvent> = Vec::new(); + + let have_tx = match r.command { + SMB2_COMMAND_SET_INFO => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", r); + let have_si_tx = match parse_smb2_request_setinfo(r.data) { + Ok((_, rd)) => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", rd); + + match rd.data { + Smb2SetInfoRequestData::RENAME(ref ren) => { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let mut newname = ren.name.to_vec(); + newname.retain(|&i|i != 0x00); + let oldname = match state.guid2name_map.get(rd.guid) { + Some(n) => { n.to_vec() }, + None => { b"<unknown>".to_vec() }, + }; + let tx = state.new_rename_tx(rd.guid.to_vec(), oldname, newname); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO); + true + } + Smb2SetInfoRequestData::DISPOSITION(ref dis) => { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let fname = match state.guid2name_map.get(rd.guid) { + Some(n) => { n.to_vec() }, + None => { + // try to find latest created file in case of chained commands + let mut guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); + if guid_key.msg_id == 0 { + b"<unknown>".to_vec() + } else { + guid_key.msg_id -= 1; + match state.ssn2vec_map.get(&guid_key) { + Some(n) => { n.to_vec() }, + None => { b"<unknown>".to_vec()}, + } + } + }, + }; + let tx = state.new_setfileinfo_tx(fname, rd.guid.to_vec(), rd.class as u16, rd.infolvl as u16, dis.delete); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO); + true + } + _ => false, + } + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + }; + have_si_tx + }, + SMB2_COMMAND_IOCTL => { + smb2_ioctl_request_record(state, r); + true + }, + SMB2_COMMAND_TREE_DISCONNECT => { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + } + SMB2_COMMAND_NEGOTIATE_PROTOCOL => { + match parse_smb2_request_negotiate_protocol(r.data) { + Ok((_, rd)) => { + let mut dialects : Vec<Vec<u8>> = Vec::new(); + for d in rd.dialects_vec { + SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d)); + let dvec = smb2_dialect_string(d).as_bytes().to_vec(); + dialects.push(dvec); + } + + let found = match state.get_negotiate_tx(2) { + Some(_) => { + SCLogDebug!("WEIRD, should not have NEGOTIATE tx!"); + true + }, + None => { false }, + }; + if !found { + let tx = state.new_negotiate_tx(2); + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.dialects2 = dialects; + tdn.client_guid = Some(rd.client_guid.to_vec()); + } + tx.request_done = true; + } + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_SESSION_SETUP => { + smb2_session_setup_request(state, r); + true + }, + SMB2_COMMAND_TREE_CONNECT => { + match parse_smb2_request_tree_connect(r.data) { + Ok((_, tr)) => { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let mut name_val = tr.share_name.to_vec(); + name_val.retain(|&i|i != 0x00); + if name_val.len() > 1 { + name_val = name_val[1..].to_vec(); + } + + let tx = state.new_treeconnect_tx(name_key, name_val); + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT); + true + } + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_READ => { + match parse_smb2_request_read(r.data) { + Ok((_, rd)) => { + if (state.max_read_size != 0 && rd.rd_len > state.max_read_size) || + (unsafe { SMB_CFG_MAX_READ_SIZE != 0 && SMB_CFG_MAX_READ_SIZE < rd.rd_len }) { + events.push(SMBEvent::ReadRequestTooLarge); + } else { + SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}", + rd.guid, rd.rd_len, rd.rd_offset); + + // store read guid,offset in map + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET); + let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset); + state.ssn2vecoffset_map.insert(guid_key, guidoff); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB2_COMMAND_CREATE => { + match parse_smb2_request_create(r.data) { + Ok((_, cr)) => { + let del = cr.create_options & 0x0000_1000 != 0; + let dir = cr.create_options & 0x0000_0001 != 0; + + SCLogDebug!("create_options {:08x}", cr.create_options); + + let name_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); + state.ssn2vec_map.insert(name_key, cr.data.to_vec()); + + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_create_tx(cr.data, + cr.disposition, del, dir, tx_hdr); + tx.vercmd.set_smb2_cmd(r.command); + SCLogDebug!("TS CREATE TX {} created", tx.id); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_WRITE => { + smb2_write_request_record(state, r, 0); + true // write handling creates both file tx and generic tx + }, + SMB2_COMMAND_CLOSE => { + match parse_smb2_request_close(r.data) { + Ok((_, cd)) => { + let found_ts = match state.get_file_tx_by_fuid(cd.guid, Direction::ToServer) { + Some(tx) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + } + } + tx.request_done = true; + tx.response_done = true; + tx.set_status(SMB_NTSTATUS_SUCCESS, false); + true + }, + None => { false }, + }; + let found_tc = match state.get_file_tx_by_fuid(cd.guid, Direction::ToClient) { + Some(tx) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + } + } + tx.request_done = true; + tx.response_done = true; + tx.set_status(SMB_NTSTATUS_SUCCESS, false); + true + }, + None => { false }, + }; + if !found_ts && !found_tc { + SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + _ => { + false + }, + }; + /* if we don't have a tx, create it here (maybe) */ + if !have_tx && 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); + SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}", + tx.id, r.command, r.session_id, r.tree_id, r.message_id); + tx.set_events(events); + } +} + +pub fn smb2_response_record(state: &mut SMBState, r: &Smb2Record) +{ + SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}", + &smb2_command_string(r.command), r.nt_status, + r.tree_id, r.session_id, r.message_id); + + let mut events : Vec<SMBEvent> = Vec::new(); + + let have_tx = match r.command { + SMB2_COMMAND_IOCTL => { + smb2_ioctl_response_record(state, r); + true + }, + SMB2_COMMAND_SESSION_SETUP => { + smb2_session_setup_response(state, r); + true + }, + SMB2_COMMAND_WRITE => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_write(r.data) + { + Ok((_, _wr)) => { + SCLogDebug!("SMBv2: Write response => {:?}", _wr); + + /* search key-guid map */ + let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID, + r.session_id, r.tree_id, r.message_id); + let _guid_vec = match state.ssn2vec_map.remove(&guid_key) { + Some(p) => p, + None => { + SCLogDebug!("SMBv2 response: GUID NOT FOUND"); + Vec::new() + }, + }; + SCLogDebug!("SMBv2 write response for GUID {:?}", _guid_vec); + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + } + false // the request may have created a generic tx, so handle that here + }, + SMB2_COMMAND_READ => { + if r.nt_status == SMB_NTSTATUS_SUCCESS || + r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { + smb2_read_response_record(state, r, 0); + false + + } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE { + SCLogDebug!("SMBv2: read response => EOF"); + + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET); + let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) { + Some(o) => o.guid, + _ => { + SCLogDebug!("SMBv2 READ response: reply to unknown request"); + Vec::new() + }, + }; + let found = match state.get_file_tx_by_fuid(&file_guid, Direction::ToClient) { + Some(tx) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + } + } + tx.set_status(r.nt_status, false); + tx.request_done = true; + false + }, + None => { false }, + }; + if !found { + SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid); + } + false + } else { + SCLogDebug!("SMBv2 READ: status {}", r.nt_status); + false + } + }, + SMB2_COMMAND_CREATE => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_create(r.data) { + Ok((_, cr)) => { + SCLogDebug!("SMBv2: Create response => {:?}", cr); + + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); + if let Some(mut p) = state.ssn2vec_map.remove(&guid_key) { + p.retain(|&i|i != 0x00); + state.guid2name_map.insert(cr.guid.to_vec(), p); + } else { + SCLogDebug!("SMBv2 response: GUID NOT FOUND"); + } + + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + if let Some(tx) = state.get_generic_tx(2, r.command, &tx_hdr) { + SCLogDebug!("tx {} with {}/{} marked as done", + tx.id, r.command, &smb2_command_string(r.command)); + tx.set_status(r.nt_status, false); + tx.response_done = true; + + if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data { + tdn.create_ts = cr.create_ts.as_unix(); + tdn.last_access_ts = cr.last_access_ts.as_unix(); + tdn.last_write_ts = cr.last_write_ts.as_unix(); + tdn.last_change_ts = cr.last_change_ts.as_unix(); + tdn.size = cr.size; + tdn.guid = cr.guid.to_vec(); + } + } + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + true + } else { + false + } + }, + SMB2_COMMAND_TREE_DISCONNECT => { + // normally removed when processing request, + // but in case we missed that try again here + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + } + SMB2_COMMAND_TREE_CONNECT => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_tree_connect(r.data) { + Ok((_, tr)) => { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let mut share_name = Vec::new(); + let is_pipe = tr.share_type == 2; + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.share_type = tr.share_type; + tdn.is_pipe = is_pipe; + tdn.tree_id = r.tree_id; + share_name = tdn.share_name.to_vec(); + } + // update hdr now that we have a tree_id + tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + tx.response_done = true; + tx.set_status(r.nt_status, false); + true + }, + None => { false }, + }; + if found { + let tree = SMBTree::new(share_name.to_vec(), is_pipe); + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.insert(tree_key, tree); + } + true + } + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + tx.response_done = true; + tx.set_status(r.nt_status, false); + true + }, + None => { false }, + }; + found + } + }, + SMB2_COMMAND_NEGOTIATE_PROTOCOL => { + let res = if r.nt_status == SMB_NTSTATUS_SUCCESS { + parse_smb2_response_negotiate_protocol(r.data) + } else { + parse_smb2_response_negotiate_protocol_error(r.data) + }; + match res { + Ok((_, rd)) => { + SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect)); + + let smb_cfg_max_read_size = unsafe { SMB_CFG_MAX_READ_SIZE }; + if smb_cfg_max_read_size != 0 && rd.max_read_size > smb_cfg_max_read_size { + state.set_event(SMBEvent::NegotiateMaxReadSizeTooLarge); + } + let smb_cfg_max_write_size = unsafe { SMB_CFG_MAX_WRITE_SIZE }; + if smb_cfg_max_write_size != 0 && rd.max_write_size > smb_cfg_max_write_size { + state.set_event(SMBEvent::NegotiateMaxWriteSizeTooLarge); + } + + state.dialect = rd.dialect; + state.max_read_size = rd.max_read_size; + state.max_write_size = rd.max_write_size; + + let found2 = match state.get_negotiate_tx(2) { + Some(tx) => { + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.server_guid = rd.server_guid.to_vec(); + } + tx.set_status(r.nt_status, false); + tx.response_done = true; + true + }, + None => { false }, + }; + // SMB2 response to SMB1 request? + let found1 = !found2 && match state.get_negotiate_tx(1) { + Some(tx) => { + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.server_guid = rd.server_guid.to_vec(); + } + tx.set_status(r.nt_status, false); + tx.response_done = true; + true + }, + None => { false }, + }; + found1 || found2 + }, + _ => { + events.push(SMBEvent::MalformedData); + false + } + } + }, + _ => { + SCLogDebug!("default case: no TX"); + false + }, + }; + if !have_tx { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}", + &smb2_command_string(r.command), + r.session_id, r.tree_id, r.message_id); + let _found = match state.get_generic_tx(2, r.command, &tx_hdr) { + Some(tx) => { + SCLogDebug!("tx {} with {}/{} marked as done", + tx.id, r.command, &smb2_command_string(r.command)); + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + tx.set_status(r.nt_status, false); + tx.set_events(events); + true + }, + _ => { + SCLogDebug!("no tx found for {:?}", r); + false + }, + }; + } +} |