From: Victor Julien Date: Tue, 27 Feb 2018 17:12:07 +0000 (+0100) Subject: smb: session setup improvements X-Git-Tag: suricata-4.1.0-beta1~105 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8bef1208989d47c03e07589dae204cd4e995e755;p=thirdparty%2Fsuricata.git smb: session setup improvements Improve ntlmssp version extraction and logging, make its data structures optional. Extract native os/lm from smb1 ssn setup. Move session setup handling into their own files. Only log auth data for the session setup tx. --- diff --git a/rust/src/smb/auth.rs b/rust/src/smb/auth.rs index 6eca8609cd..491818315c 100644 --- a/rust/src/smb/auth.rs +++ b/rust/src/smb/auth.rs @@ -306,15 +306,21 @@ fn parse_secblob_spnego_start(blob: &[u8]) -> IResult<&[u8], &[u8]> IResult::Done(rem, d) } -fn parse_secblob_spnego(state: &mut SMBState, blob: &[u8]) +pub struct SpnegoRequest { + pub krb: Option, + pub ntlmssp: Option, +} + +fn parse_secblob_spnego(blob: &[u8]) -> Option { - let mut ntlmssp = false; - let mut kerberos = false; + let mut have_ntlmssp = false; + let mut have_kerberos = false; let mut kticket : Option = None; + let mut ntlmssp : Option = None; let o = match der_parser::parse_der_sequence(blob) { IResult::Done(_, o) => o, - _ => { return; }, + _ => { return None; }, }; for s in o { SCLogDebug!("s {:?}", s); @@ -337,11 +343,11 @@ fn parse_secblob_spnego(state: &mut SMBState, blob: &[u8]) SCLogDebug!("OID {:?}", oid); match oid.to_string().as_str() { "1.2.840.48018.1.2.2" => { SCLogDebug!("Microsoft Kerberos 5"); }, - "1.2.840.113554.1.2.2" => { SCLogDebug!("Kerberos 5"); kerberos = true; }, + "1.2.840.113554.1.2.2" => { SCLogDebug!("Kerberos 5"); have_kerberos = true; }, "1.2.840.113554.1.2.2.1" => { SCLogDebug!("krb5-name"); }, "1.2.840.113554.1.2.2.2" => { SCLogDebug!("krb5-principal"); }, "1.2.840.113554.1.2.2.3" => { SCLogDebug!("krb5-user-to-user-mech"); }, - "1.3.6.1.4.1.311.2.2.10" => { SCLogDebug!("NTLMSSP"); ntlmssp = true; }, + "1.3.6.1.4.1.311.2.2.10" => { SCLogDebug!("NTLMSSP"); have_ntlmssp = true; }, "1.3.6.1.4.1.311.2.2.30" => { SCLogDebug!("NegoEx"); }, _ => { SCLogNotice!("unexpected OID {:?}", oid); }, } @@ -351,7 +357,7 @@ fn parse_secblob_spnego(state: &mut SMBState, blob: &[u8]) } }, der_parser::DerObjectContent::OctetString(ref os) => { - if kerberos { + if have_kerberos { match parse_kerberos5_request(os) { IResult::Done(_, t) => { kticket = Some(t) @@ -360,16 +366,20 @@ fn parse_secblob_spnego(state: &mut SMBState, blob: &[u8]) } } - if ntlmssp && kticket == None { + if have_ntlmssp && kticket == None { SCLogDebug!("parsing expected NTLMSSP"); - parse_ntlmssp_blob(state, os); + ntlmssp = parse_ntlmssp_blob(os); } }, _ => {}, } } - state.krb_ticket = kticket; + let s = SpnegoRequest { + krb: kticket, + ntlmssp: ntlmssp, + }; + Some(s) } #[derive(Debug,PartialEq)] @@ -377,11 +387,14 @@ pub struct NtlmsspData { pub host: Vec, pub user: Vec, pub domain: Vec, + pub version: Option, } /// take in blob, search for the header and parse it -fn parse_ntlmssp_blob(state: &mut SMBState, blob: &[u8]) +fn parse_ntlmssp_blob(blob: &[u8]) -> Option { + let mut ntlmssp_data : Option = None; + SCLogDebug!("NTLMSSP {:?}", blob); match parse_ntlmssp(blob) { IResult::Done(_, nd) => { @@ -405,8 +418,9 @@ fn parse_ntlmssp_blob(state: &mut SMBState, blob: &[u8]) host: host, user: user, domain: domain, + version: ad.version, }; - state.ntlmssp = Some(d); + ntlmssp_data = Some(d); }, _ => {}, } @@ -416,24 +430,43 @@ fn parse_ntlmssp_blob(state: &mut SMBState, blob: &[u8]) }, _ => {}, } + return ntlmssp_data; } // if spnego parsing fails try to fall back to ntlmssp -pub fn parse_secblob(state: &mut SMBState, blob: &[u8]) +pub fn parse_secblob(blob: &[u8]) -> Option { match parse_secblob_get_spnego(blob) { IResult::Done(_, spnego) => { match parse_secblob_spnego_start(spnego) { IResult::Done(_, spnego_start) => { - parse_secblob_spnego(state, spnego_start); + parse_secblob_spnego(spnego_start) }, _ => { - parse_ntlmssp_blob(state, blob); + match parse_ntlmssp_blob(blob) { + Some(n) => { + let s = SpnegoRequest { + krb: None, + ntlmssp: Some(n), + }; + Some(s) + }, + None => { None }, + } }, } }, _ => { - parse_ntlmssp_blob(state, blob); + match parse_ntlmssp_blob(blob) { + Some(n) => { + let s = SpnegoRequest { + krb: None, + ntlmssp: Some(n), + }; + Some(s) + }, + None => { None }, + } }, } } diff --git a/rust/src/smb/log.rs b/rust/src/smb/log.rs index c22accce13..774e4a98e2 100644 --- a/rust/src/smb/log.rs +++ b/rust/src/smb/log.rs @@ -90,55 +90,89 @@ fn smb_common_header(state: &SMBState, tx: &SMBTransaction) -> Json js.set_boolean("request_done", tx.request_done); js.set_boolean("response_done", tx.request_done); + match tx.type_data { + Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) => { + if let Some(ref ntlmssp) = x.ntlmssp { + let jsd = Json::object(); + let domain = match str::from_utf8(&ntlmssp.domain) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("domain", &domain); - match state.ntlmssp { - Some(ref ntlmssp) => { - let jsd = Json::object(); - let domain = match str::from_utf8(&ntlmssp.domain) { - Ok(v) => v, - Err(_) => "UTF8_ERROR", - }; - jsd.set_string("domain", &domain); + let user = match str::from_utf8(&ntlmssp.user) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("user", &user); - let user = match str::from_utf8(&ntlmssp.user) { - Ok(v) => v, - Err(_) => "UTF8_ERROR", - }; - jsd.set_string("user", &user); + let host = match str::from_utf8(&ntlmssp.host) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("host", &host); - let host = match str::from_utf8(&ntlmssp.host) { - Ok(v) => v, - Err(_) => "UTF8_ERROR", - }; - jsd.set_string("host", &host); - js.set("ntlmssp", jsd); - } - None => {}, - } + if let Some(ref v) = ntlmssp.version { + jsd.set_string("version", v.to_string().as_str()); + } - match state.krb_ticket { - Some(ref ticket) => { - let jsd = Json::object(); - let realm = match str::from_utf8(&ticket.realm) { - Ok(v) => v, - Err(_) => "UTF8_ERROR", - }; - jsd.set_string("realm", &realm); - let jsa = Json::array(); - for sname in &ticket.snames { - let name = match str::from_utf8(&sname) { + js.set("ntlmssp", jsd); + } + + if let Some(ref ticket) = x.krb_ticket { + let jsd = Json::object(); + let realm = match str::from_utf8(&ticket.realm) { Ok(v) => v, Err(_) => "UTF8_ERROR", }; - jsa.array_append_string(&name); + jsd.set_string("realm", &realm); + let jsa = Json::array(); + for sname in &ticket.snames { + let name = match str::from_utf8(&sname) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsa.array_append_string(&name); + } + jsd.set("snames", jsa); + js.set("kerberos", jsd); } - jsd.set("snames", jsa); - js.set("kerberos", jsd); - }, - None => { }, - } - match tx.type_data { + match x.request_host { + Some(ref r) => { + let jsd = Json::object(); + let os = match str::from_utf8(&r.native_os) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("native_os", &os); + let lm = match str::from_utf8(&r.native_lm) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("native_lm", &lm); + js.set("request", jsd); + }, + None => { }, + } + match x.response_host { + Some(ref r) => { + let jsd = Json::object(); + let os = match str::from_utf8(&r.native_os) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("native_os", &os); + let lm = match str::from_utf8(&r.native_lm) { + Ok(v) => v, + Err(_) => "UTF8_ERROR", + }; + jsd.set_string("native_lm", &lm); + js.set("response", jsd); + }, + None => { }, + } + }, Some(SMBTransactionTypeData::CREATE(ref x)) => { let mut name_raw = x.filename.to_vec(); name_raw.retain(|&i|i != 0x00); diff --git a/rust/src/smb/mod.rs b/rust/src/smb/mod.rs index 31761dcd6f..0019da92b5 100644 --- a/rust/src/smb/mod.rs +++ b/rust/src/smb/mod.rs @@ -23,8 +23,11 @@ pub mod ntlmssp_records; pub mod smb; pub mod smb1; +pub mod smb1_session; pub mod smb2; +pub mod smb2_session; pub mod dcerpc; +pub mod session; pub mod log; pub mod detect; pub mod debug; diff --git a/rust/src/smb/ntlmssp_records.rs b/rust/src/smb/ntlmssp_records.rs index c47fa1125c..2f6343fad0 100644 --- a/rust/src/smb/ntlmssp_records.rs +++ b/rust/src/smb/ntlmssp_records.rs @@ -15,17 +15,49 @@ * 02110-1301, USA. */ -use nom::{rest, le_u16, le_u32}; +use nom::{rest, le_u8, le_u16, le_u32}; + +#[derive(Debug,PartialEq)] +pub struct NTLMSSPVersion { + pub ver_major: u8, + pub ver_minor: u8, + pub ver_build: u16, + pub ver_ntlm_rev: u8, +} + +impl NTLMSSPVersion { + pub fn to_string(&self) -> String { + format!("{}.{} build {} rev {}", + self.ver_major, self.ver_minor, + self.ver_build, self.ver_ntlm_rev) + } +} + +named!(parse_ntlm_auth_version, + do_parse!( + ver_major: le_u8 + >> ver_minor: le_u8 + >> ver_build: le_u16 + >> take!(3) + >> ver_ntlm_rev: le_u8 + >> ( NTLMSSPVersion { + ver_major: ver_major, + ver_minor: ver_minor, + ver_build: ver_build, + ver_ntlm_rev: ver_ntlm_rev, + }) +)); #[derive(Debug,PartialEq)] pub struct NTLMSSPAuthRecord<'a> { pub domain: &'a[u8], pub user: &'a[u8], pub host: &'a[u8], + pub version: Option, } named!(pub parse_ntlm_auth_record, - dbg_dmp!(do_parse!( + do_parse!( lm_blob_len: le_u16 >> lm_blob_maxlen: le_u16 >> lm_blob_offset: le_u32 @@ -50,12 +82,15 @@ named!(pub parse_ntlm_auth_record, >> ssnkey_blob_maxlen: le_u16 >> ssnkey_blob_offset: le_u32 + >> nego_flags: bits!(tuple!(take_bits!(u8, 6),take_bits!(u8,1),take_bits!(u32,25))) + >> version: cond!(nego_flags.1==1, parse_ntlm_auth_version) + // subtrack 12 as idenfier (8) and type (4) are cut before we are called - // subtract 48 for the len/offset/maxlen fields above - >> take!(domain_blob_offset - (12 + 48)) + // subtract 60 for the len/offset/maxlen fields above + >> cond!(nego_flags.1==1, take!(domain_blob_offset - (12 + 60))) + // or 52 if we have no version + >> cond!(nego_flags.1==0, take!(domain_blob_offset - (12 + 52))) - //>> lm_blob: take!(lm_blob_len) - //>> ntlmresp_blob: take!(ntlmresp_blob_len) >> domain_blob: take!(domain_blob_len) >> user_blob: take!(user_blob_len) >> host_blob: take!(host_blob_len) @@ -64,8 +99,10 @@ named!(pub parse_ntlm_auth_record, domain: domain_blob, user: user_blob, host: host_blob, + + version: version, }) -))); +)); #[derive(Debug,PartialEq)] pub struct NTLMSSPRecord<'a> { diff --git a/rust/src/smb/session.rs b/rust/src/smb/session.rs new file mode 100644 index 0000000000..ed57c6cf6d --- /dev/null +++ b/rust/src/smb/session.rs @@ -0,0 +1,75 @@ +/* Copyright (C) 2018 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 log::*; + +use smb::smb::*; +use smb::smb1_session::*; +use smb::auth::*; + +#[derive(Debug)] +pub struct SMBTransactionSessionSetup { + pub request_host: Option, + pub response_host: Option, + pub ntlmssp: Option, + pub krb_ticket: Option, +} + +impl SMBTransactionSessionSetup { + pub fn new() -> SMBTransactionSessionSetup { + return SMBTransactionSessionSetup { + request_host: None, + response_host: None, + ntlmssp: None, + krb_ticket: None, + } + } +} + +impl SMBState { + pub fn new_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) + -> (&mut SMBTransaction) + { + let mut tx = self.new_tx(); + + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::SESSIONSETUP( + SMBTransactionSessionSetup::new())); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX SESSIONSETUP created: ID {}", tx.id); + self.transactions.push(tx); + let tx_ref = self.transactions.last_mut(); + return tx_ref.unwrap(); + } + + pub fn get_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let hit = tx.hdr == hdr && match tx.type_data { + Some(SMBTransactionTypeData::SESSIONSETUP(_)) => { true }, + _ => { false }, + }; + if hit { + return Some(tx); + } + } + return None; + } +} diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs index b99e013234..f2c4615738 100644 --- a/rust/src/smb/smb.rs +++ b/rust/src/smb/smb.rs @@ -45,8 +45,8 @@ use smb::smb2_records::*; use smb::smb1::*; use smb::smb2::*; use smb::dcerpc::*; +use smb::session::*; use smb::events::*; -use smb::auth::*; use smb::files::*; pub static mut SURICATA_SMB_FILE_CONFIG: Option<&'static SuricataFileContext> = None; @@ -311,6 +311,7 @@ pub enum SMBTransactionTypeData { NEGOTIATE(SMBTransactionNegotiate), DCERPC(SMBTransactionDCERPC), CREATE(SMBTransactionCreate), + SESSIONSETUP(SMBTransactionSessionSetup), } #[derive(Debug)] @@ -600,8 +601,8 @@ pub struct SMBState<> { pub ts_gap: bool, // last TS update was gap pub tc_gap: bool, // last TC update was gap - ts_trunc: bool, // no more data for TOSERVER - tc_trunc: bool, // no more data for TOCLIENT + pub ts_trunc: bool, // no more data for TOSERVER + pub tc_trunc: bool, // no more data for TOCLIENT /// transactions list pub transactions: Vec, @@ -616,9 +617,6 @@ pub struct SMBState<> { /// dcerpc interfaces, stored here to be able to match /// them while inspecting DCERPC REQUEST txs pub dcerpc_ifaces: Option>, - - pub ntlmssp: Option, - pub krb_ticket: Option, } impl SMBState { @@ -652,8 +650,6 @@ impl SMBState { dialect_vec: None, dialects: None, dcerpc_ifaces: None, - ntlmssp: None, - krb_ticket: None, } } diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs index c75832199b..560f439e77 100644 --- a/rust/src/smb/smb1.rs +++ b/rust/src/smb/smb1.rs @@ -27,13 +27,14 @@ use nom::{IResult}; use core::*; use log::*; -use smb::smb1_records::*; use smb::smb::*; use smb::dcerpc::*; use smb::events::*; -use smb::auth::*; use smb::files::*; +use smb::smb1_records::*; +use smb::smb1_session::*; + // https://msdn.microsoft.com/en-us/library/ee441741.aspx pub const SMB1_COMMAND_CREATE_DIRECTORY: u8 = 0x00; pub const SMB1_COMMAND_DELETE_DIRECTORY: u8 = 0x01; @@ -129,10 +130,6 @@ pub fn smb1_create_new_tx(_cmd: u8) -> bool { pub fn smb1_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 { SCLogDebug!("record: {:?} command {}", r.greeter, r.command); - // init default tx keys - let mut key_ssn_id = r.ssn_id; - let key_tree_id = r.tree_id; - let key_multiplex_id = r.multiplex_id; let mut events : Vec = Vec::new(); let mut no_response_expected = false; @@ -229,21 +226,8 @@ pub fn smb1_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 { }, SMB1_COMMAND_SESSION_SETUP_ANDX => { SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id); - match parse_smb_setup_andx_record(r.data) { - IResult::Done(_, setup) => { - parse_secblob(state, setup.sec_blob); -/* - _ => { - events.push(SMBEvent::MalformedNtlmsspRequest); - }, -*/ - }, - _ => { - events.push(SMBEvent::MalformedData); - }, - } - key_ssn_id = 0; - false + smb1_session_setup_request(state, r); + true }, SMB1_COMMAND_TREE_CONNECT_ANDX => { SCLogDebug!("SMB1_COMMAND_TREE_CONNECT_ANDX"); @@ -353,8 +337,7 @@ pub fn smb1_request_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 { }; if !have_tx { if smb1_create_new_tx(r.command) { - let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, - key_ssn_id as u64, key_tree_id as u32, key_multiplex_id as u64); + let tx_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); let tx = state.new_generic_tx(1, r.command as u16, tx_key); SCLogDebug!("tx {} created for {}/{}", tx.id, r.command, &smb1_command_string(r.command)); tx.set_events(events); @@ -510,8 +493,20 @@ pub fn smb1_response_record<'b>(state: &mut SMBState, r: &SmbRecord<'b>) -> u32 true }, SMB1_COMMAND_SESSION_SETUP_ANDX => { +/* + SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id); + match parse_smb_response_setup_andx_record(r.data) { + IResult::Done(rem, _setup) => { + //parse_secblob(state, setup.sec_blob); + state.response_host = Some(smb1_session_setup_response_host_info(r, rem)); + }, + _ => {}, + } tx_sync = true; false +*/ + smb1_session_setup_response(state, r); + true }, SMB1_COMMAND_LOGOFF_ANDX => { tx_sync = true; diff --git a/rust/src/smb/smb1_records.rs b/rust/src/smb/smb1_records.rs index b65fa0af0c..81864c75d5 100644 --- a/rust/src/smb/smb1_records.rs +++ b/rust/src/smb/smb1_records.rs @@ -400,12 +400,29 @@ named!(pub parse_smb_setup_andx_record, >> skip2: take!(8) >> bcc: le_u16 >> sec_blob: take!(sec_blob_len) - >> skip3: rest + //>> skip3: rest >> (SmbRecordSetupAndX { sec_blob:sec_blob, })) ); +#[derive(Debug,PartialEq)] +pub struct SmbResponseRecordSetupAndX<'a> { + pub sec_blob: &'a[u8], +} + +named!(pub parse_smb_response_setup_andx_record, + do_parse!( + skip1: take!(7) + >> sec_blob_len: le_u16 + >> bcc: le_u16 + >> sec_blob: take!(sec_blob_len) + //>> skip3: rest + >> (SmbResponseRecordSetupAndX { + sec_blob:sec_blob, + })) +); + #[derive(Debug,PartialEq)] pub struct SmbRequestReadAndXRecord<'a> { pub fid: &'a[u8], diff --git a/rust/src/smb/smb1_session.rs b/rust/src/smb/smb1_session.rs new file mode 100644 index 0000000000..ebbcfc9347 --- /dev/null +++ b/rust/src/smb/smb1_session.rs @@ -0,0 +1,234 @@ +/* Copyright (C) 2018 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 nom::{IResult, ErrorKind}; + +use log::*; + +use smb::smb1_records::*; +use smb::smb::*; +use smb::events::*; +use smb::auth::*; + +#[derive(Debug)] +pub struct SessionSetupRequest { + pub native_os: Vec, + pub native_lm: Vec, + pub primary_domain: Vec, +} + +#[derive(Debug)] +pub struct SessionSetupResponse { + pub native_os: Vec, + pub native_lm: Vec, +} + +fn get_unicode_string(blob: &[u8]) -> IResult<&[u8], Vec> +{ + SCLogDebug!("get_unicode_string: blob {} {:?}", blob.len(), blob); + let mut name : Vec = Vec::new(); + let mut c = blob; + while c.len() >= 1 { + if c.len() == 1 && c[0] == 0 { + let rem = &c[1..]; + SCLogDebug!("get_unicode_string: name {:?}", name); + return IResult::Done(rem, name) + } else if c.len() == 1 { + break; + } else if c[0] == 0 && c[1] == 0 { + let rem = &c[2..]; + SCLogDebug!("get_unicode_string: name {:?}", name); + return IResult::Done(rem, name) + } + name.push(c[0]); + c = &c[2..]; + //SCLogNotice!("get_unicode_string: c {:?}", c); + } + IResult::Error(error_code!(ErrorKind::Custom(130))) +} + +named!(pub get_nullterm_string>, + do_parse!( + s: take_until_and_consume!("\x00") + >> ( s.to_vec() ) +)); + +pub fn smb1_session_setup_request_host_info(r: &SmbRecord, blob: &[u8]) -> SessionSetupRequest +{ + if blob.len() > 1 && r.flags2 & 0x8000_u16 != 0 { + let offset = r.data.len() - blob.len(); + let blob = if offset % 2 == 1 { &blob[1..] } else { blob }; + let (native_os, native_lm, primary_domain) = match get_unicode_string(blob) { + IResult::Done(rem, n1) => { + match get_unicode_string(rem) { + IResult::Done(rem, n2) => { + match get_unicode_string(rem) { + IResult::Done(_, n3) => { (n1, n2, n3) }, + _ => { (n1, n2, Vec::new()) }, + } + }, + _ => { (n1, Vec::new(), Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new(), Vec::new()) }, + }; + + SCLogDebug!("name1 {:?} name2 {:?} name3 {:?}", native_os,native_lm,primary_domain); + SessionSetupRequest { + native_os:native_os, + native_lm:native_lm, + primary_domain:primary_domain, + } + } else { + let (native_os, native_lm, primary_domain) = match get_nullterm_string(blob) { + IResult::Done(rem, n1) => { + match get_nullterm_string(rem) { + IResult::Done(rem, n2) => { + match get_nullterm_string(rem) { + IResult::Done(_, n3) => { (n1, n2, n3) }, + _ => { (n1, n2, Vec::new()) }, + } + }, + _ => { (n1, Vec::new(), Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new(), Vec::new()) }, + }; + + SCLogDebug!("session_setup_request_host_info: not unicode"); + SessionSetupRequest { + native_os: native_os, + native_lm: native_lm, + primary_domain: primary_domain, + } + } +} + +pub fn smb1_session_setup_response_host_info(r: &SmbRecord, blob: &[u8]) -> SessionSetupResponse +{ + if blob.len() > 1 && r.flags2 & 0x8000_u16 != 0 { + let offset = r.data.len() - blob.len(); + let blob = if offset % 2 == 1 { &blob[1..] } else { blob }; + let (native_os, native_lm) = match get_unicode_string(blob) { + IResult::Done(rem, n1) => { + match get_unicode_string(rem) { + IResult::Done(_, n2) => { + (n1, n2) + }, + _ => { (n1, Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new()) }, + }; + + SCLogDebug!("name1 {:?} name2 {:?}", native_os,native_lm); + SessionSetupResponse { + native_os:native_os, + native_lm:native_lm, + } + } else { + SCLogDebug!("session_setup_response_host_info: not unicode"); + let (native_os, native_lm) = match get_nullterm_string(blob) { + IResult::Done(rem, n1) => { + match get_nullterm_string(rem) { + IResult::Done(_, n2) => { + (n1, n2) + }, + _ => { (n1, Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new()) }, + }; + SessionSetupResponse { + native_os: native_os, + native_lm: native_lm, + } + } +} + +pub fn smb1_session_setup_request(state: &mut SMBState, r: &SmbRecord) +{ + SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id); + match parse_smb_setup_andx_record(r.data) { + IResult::Done(rem, setup) => { + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let tx = state.new_sessionsetup_tx(hdr); + tx.vercmd.set_smb1_cmd(r.command); + + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref mut td)) = tx.type_data { + match parse_secblob(setup.sec_blob) { + Some(s) => { + td.ntlmssp = s.ntlmssp; + td.krb_ticket = s.krb; + }, + None => { }, + } + td.request_host = Some(smb1_session_setup_request_host_info(r, rem)); + } + }, + _ => { +// events.push(SMBEvent::MalformedData); + }, + } +} + +fn smb1_session_setup_update_tx(tx: &mut SMBTransaction, r: &SmbRecord) +{ + match parse_smb_response_setup_andx_record(r.data) { + IResult::Done(rem, _setup) => { + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref mut td)) = tx.type_data { + td.response_host = Some(smb1_session_setup_response_host_info(r, rem)); + } + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + // update tx even if we can't parse the response + tx.hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); // to overwrite ssn_id 0 + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; +} + +pub fn smb1_session_setup_response(state: &mut SMBState, r: &SmbRecord) +{ + // try exact match with session id already set (e.g. NTLMSSP AUTH phase) + let found = r.ssn_id != 0 && match state.get_sessionsetup_tx( + SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER)) + { + Some(tx) => { + smb1_session_setup_update_tx(tx, r); + SCLogDebug!("smb1_session_setup_response: tx {:?}", tx); + true + }, + None => { false }, + }; + // otherwise try match with ssn id 0 (e.g. NTLMSSP_NEGOTIATE) + if !found { + match state.get_sessionsetup_tx( + SMBCommonHdr::new(SMBHDR_TYPE_HEADER, 0, 0, r.multiplex_id as u64)) + { + Some(tx) => { + smb1_session_setup_update_tx(tx, r); + SCLogDebug!("smb1_session_setup_response: tx {:?}", tx); + }, + None => { + SCLogNotice!("smb1_session_setup_response: tx not found for {:?}", r); + }, + } + } +} diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs index 0a7acf1f2e..a2b9623e53 100644 --- a/rust/src/smb/smb2.rs +++ b/rust/src/smb/smb2.rs @@ -21,9 +21,9 @@ use nom::IResult; use smb::smb::*; use smb::smb2_records::*; +use smb::smb2_session::*; use smb::dcerpc::*; use smb::events::*; -use smb::auth::*; use smb::files::*; pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0; @@ -324,47 +324,8 @@ pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) } }, SMB2_COMMAND_SESSION_SETUP => { - SCLogDebug!("SMB2_COMMAND_SESSION_SETUP: r.data.len() {}", r.data.len()); - match parse_smb2_request_session_setup(r.data) { - IResult::Done(_, setup) => { - SCLogDebug!("SMB2_COMMAND_SESSION_SETUP parsed. rd.data.len() {}", setup.data.len()); - parse_secblob(state, setup.data); -/* - match parse_ntlmssp(rd.data) { - IResult::Done(_, nd) => { - SCLogDebug!("NTLMSSP TYPE {}/{} nd {:?}", - nd.msg_type, &ntlmssp_type_string(nd.msg_type), nd); - match nd.msg_type { - NTLMSSP_NEGOTIATE => { - key_session_id = 0; - }, - NTLMSSP_AUTH => { - match parse_ntlm_auth_record(nd.data) { - IResult::Done(_, ad) => { - SCLogDebug!("auth data {:?}", ad); - state.ntlmssp_host = ad.host.to_vec(); - state.ntlmssp_user = ad.user.to_vec(); - state.ntlmssp_domain = ad.domain.to_vec(); - }, - _ => { - events.push(SMBEvent::MalformedData); - }, - } - }, - _ => { }, - } - }, - _ => { - SCLogNotice!("error parsing ntlmssp"); - }, - } -*/ - }, - _ => { - events.push(SMBEvent::MalformedData); - }, - } - false + smb2_session_setup_request(state, r); + true }, SMB2_COMMAND_TREE_CONNECT => { match parse_smb2_request_tree_connect(r.data) { @@ -490,34 +451,6 @@ pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) } } -fn smb2_response_record_session_setup<'b>(state: &mut SMBState, r: &Smb2Record<'b>) -{ - // first try exact match - let found = match state.get_generic_tx(2, r.command, &SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX)) { - Some(tx) => { - if r.nt_status != SMB_NTSTATUS_PENDING { - tx.response_done = true; - } - tx.set_status(r.nt_status, false); - true - }, - None => false, - }; - // if not found try with ssn id 0. - if !found { - let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, 0, 0, r.message_id); - match state.get_generic_tx(2, r.command, &tx_key) { - Some(tx) => { - if r.nt_status != SMB_NTSTATUS_PENDING { - tx.response_done = true; - } - tx.set_status(r.nt_status, false); - }, - None => { }, - } - } -} - pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) { SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}", @@ -578,7 +511,7 @@ pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) have_ioctl_tx }, SMB2_COMMAND_SESSION_SETUP => { - smb2_response_record_session_setup(state, r); + smb2_session_setup_response(state, r); true }, SMB2_COMMAND_WRITE => { diff --git a/rust/src/smb/smb2_session.rs b/rust/src/smb/smb2_session.rs new file mode 100644 index 0000000000..5c4b26fa46 --- /dev/null +++ b/rust/src/smb/smb2_session.rs @@ -0,0 +1,83 @@ +/* Copyright (C) 2018 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 nom::{IResult}; + +use log::*; + +use smb::smb2_records::*; +use smb::smb::*; +//use smb::events::*; +use smb::auth::*; + +pub fn smb2_session_setup_request(state: &mut SMBState, r: &Smb2Record) +{ + SCLogDebug!("SMB2_COMMAND_SESSION_SETUP: r.data.len() {}", r.data.len()); + match parse_smb2_request_session_setup(r.data) { + IResult::Done(_, setup) => { + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let tx = state.new_sessionsetup_tx(hdr); + tx.vercmd.set_smb2_cmd(r.command); + + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref mut td)) = tx.type_data { + if let Some(s) = parse_secblob(setup.data) { + td.ntlmssp = s.ntlmssp; + td.krb_ticket = s.krb; + } + } + }, + _ => { +// events.push(SMBEvent::MalformedData); + }, + } +} + +fn smb2_session_setup_update_tx(tx: &mut SMBTransaction, r: &Smb2Record) +{ + tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); // to overwrite ssn_id 0 + tx.set_status(r.nt_status, false); + tx.response_done = true; +} + +pub fn smb2_session_setup_response(state: &mut SMBState, r: &Smb2Record) +{ + // try exact match with session id already set (e.g. NTLMSSP AUTH phase) + let found = r.session_id != 0 && match state.get_sessionsetup_tx( + SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER)) + { + Some(tx) => { + smb2_session_setup_update_tx(tx, r); + SCLogDebug!("smb2_session_setup_response: tx {:?}", tx); + true + }, + None => { false }, + }; + // otherwise try match with ssn id 0 (e.g. NTLMSSP_NEGOTIATE) + if !found { + match state.get_sessionsetup_tx( + SMBCommonHdr::new(SMBHDR_TYPE_HEADER, 0, 0, r.message_id)) + { + Some(tx) => { + smb2_session_setup_update_tx(tx, r); + SCLogDebug!("smb2_session_setup_response: tx {:?}", tx); + }, + None => { + SCLogNotice!("smb2_session_setup_response: tx not found for {:?}", r); + }, + } + } +}