]>
Commit | Line | Data |
---|---|---|
75d7c9d6 VJ |
1 | /* Copyright (C) 2017 Open Information Security Foundation |
2 | * | |
3 | * You can copy, redistribute or modify this Program under the terms of | |
4 | * the GNU General Public License version 2 as published by the Free | |
5 | * Software Foundation. | |
6 | * | |
7 | * This program is distributed in the hope that it will be useful, | |
8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
10 | * GNU General Public License for more details. | |
11 | * | |
12 | * You should have received a copy of the GNU General Public License | |
13 | * version 2 along with this program; if not, write to the Free Software | |
14 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
15 | * 02110-1301, USA. | |
16 | */ | |
17 | ||
13b73997 PC |
18 | use nom; |
19 | ||
42e5065a | 20 | use crate::core::*; |
75d7c9d6 | 21 | |
42e5065a JI |
22 | use crate::smb::smb::*; |
23 | use crate::smb::smb2_records::*; | |
24 | use crate::smb::smb2_session::*; | |
25 | use crate::smb::smb2_ioctl::*; | |
26 | use crate::smb::dcerpc::*; | |
27 | use crate::smb::events::*; | |
28 | use crate::smb::files::*; | |
75d7c9d6 VJ |
29 | |
30 | pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0; | |
31 | pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1; | |
32 | pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2; | |
33 | pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3; | |
34 | pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4; | |
35 | pub const SMB2_COMMAND_CREATE: u16 = 5; | |
36 | pub const SMB2_COMMAND_CLOSE: u16 = 6; | |
894a73ee | 37 | pub const SMB2_COMMAND_FLUSH: u16 = 7; |
75d7c9d6 VJ |
38 | pub const SMB2_COMMAND_READ: u16 = 8; |
39 | pub const SMB2_COMMAND_WRITE: u16 = 9; | |
894a73ee | 40 | pub const SMB2_COMMAND_LOCK: u16 = 10; |
75d7c9d6 | 41 | pub const SMB2_COMMAND_IOCTL: u16 = 11; |
894a73ee | 42 | pub const SMB2_COMMAND_CANCEL: u16 = 12; |
75d7c9d6 VJ |
43 | pub const SMB2_COMMAND_KEEPALIVE: u16 = 13; |
44 | pub const SMB2_COMMAND_FIND: u16 = 14; | |
894a73ee | 45 | pub const SMB2_COMMAND_CHANGE_NOTIFY: u16 = 15; |
75d7c9d6 VJ |
46 | pub const SMB2_COMMAND_GET_INFO: u16 = 16; |
47 | pub const SMB2_COMMAND_SET_INFO: u16 = 17; | |
894a73ee | 48 | pub const SMB2_COMMAND_OPLOCK_BREAK: u16 = 18; |
75d7c9d6 VJ |
49 | |
50 | pub fn smb2_command_string(c: u16) -> String { | |
51 | match c { | |
52 | SMB2_COMMAND_NEGOTIATE_PROTOCOL => "SMB2_COMMAND_NEGOTIATE_PROTOCOL", | |
53 | SMB2_COMMAND_SESSION_SETUP => "SMB2_COMMAND_SESSION_SETUP", | |
54 | SMB2_COMMAND_SESSION_LOGOFF => "SMB2_COMMAND_SESSION_LOGOFF", | |
55 | SMB2_COMMAND_TREE_CONNECT => "SMB2_COMMAND_TREE_CONNECT", | |
56 | SMB2_COMMAND_TREE_DISCONNECT => "SMB2_COMMAND_TREE_DISCONNECT", | |
57 | SMB2_COMMAND_CREATE => "SMB2_COMMAND_CREATE", | |
58 | SMB2_COMMAND_CLOSE => "SMB2_COMMAND_CLOSE", | |
59 | SMB2_COMMAND_READ => "SMB2_COMMAND_READ", | |
894a73ee | 60 | SMB2_COMMAND_FLUSH => "SMB2_COMMAND_FLUSH", |
75d7c9d6 | 61 | SMB2_COMMAND_WRITE => "SMB2_COMMAND_WRITE", |
894a73ee | 62 | SMB2_COMMAND_LOCK => "SMB2_COMMAND_LOCK", |
75d7c9d6 | 63 | SMB2_COMMAND_IOCTL => "SMB2_COMMAND_IOCTL", |
894a73ee | 64 | SMB2_COMMAND_CANCEL => "SMB2_COMMAND_CANCEL", |
75d7c9d6 VJ |
65 | SMB2_COMMAND_KEEPALIVE => "SMB2_COMMAND_KEEPALIVE", |
66 | SMB2_COMMAND_FIND => "SMB2_COMMAND_FIND", | |
894a73ee | 67 | SMB2_COMMAND_CHANGE_NOTIFY => "SMB2_COMMAND_CHANGE_NOTIFY", |
75d7c9d6 VJ |
68 | SMB2_COMMAND_GET_INFO => "SMB2_COMMAND_GET_INFO", |
69 | SMB2_COMMAND_SET_INFO => "SMB2_COMMAND_SET_INFO", | |
894a73ee | 70 | SMB2_COMMAND_OPLOCK_BREAK => "SMB2_COMMAND_OPLOCK_BREAK", |
75d7c9d6 VJ |
71 | _ => { return (c).to_string(); }, |
72 | }.to_string() | |
73 | ||
74 | } | |
75 | ||
76 | pub fn smb2_dialect_string(d: u16) -> String { | |
77 | match d { | |
78 | 0x0202 => "2.02", | |
79 | 0x0210 => "2.10", | |
80 | 0x0222 => "2.22", | |
81 | 0x0224 => "2.24", | |
82 | 0x02ff => "2.??", | |
83 | 0x0300 => "3.00", | |
84 | 0x0302 => "3.02", | |
85 | 0x0310 => "3.10", | |
86 | 0x0311 => "3.11", | |
87 | _ => { return (d).to_string(); }, | |
88 | }.to_string() | |
89 | } | |
90 | ||
91 | // later we'll use this to determine if we need to | |
92 | // track a ssn per type | |
32b19fac VJ |
93 | fn smb2_create_new_tx(cmd: u16) -> bool { |
94 | match cmd { | |
95 | SMB2_COMMAND_READ | | |
96 | SMB2_COMMAND_WRITE | | |
97 | SMB2_COMMAND_GET_INFO | | |
98 | SMB2_COMMAND_SET_INFO => { false }, | |
99 | _ => { true }, | |
100 | } | |
75d7c9d6 VJ |
101 | } |
102 | ||
103 | fn smb2_read_response_record_generic<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
104 | { | |
105 | if smb2_create_new_tx(r.command) { | |
106 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
107 | let tx = state.get_generic_tx(2, r.command as u16, &tx_hdr); | |
108 | if let Some(tx) = tx { | |
109 | tx.set_status(r.nt_status, false); | |
110 | tx.response_done = true; | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
116 | { | |
117 | smb2_read_response_record_generic(state, r); | |
118 | ||
75d7c9d6 | 119 | match parse_smb2_response_read(r.data) { |
13b73997 | 120 | Ok((_, rd)) => { |
ac4e8885 VJ |
121 | if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { |
122 | SCLogDebug!("SMBv2/READ: incomplete record, expecting a follow up"); | |
123 | // fall through | |
124 | ||
125 | } else if r.nt_status != SMB_NTSTATUS_SUCCESS { | |
9dd7c381 | 126 | SCLogDebug!("SMBv2: read response error code received: skip record"); |
89cb3379 | 127 | state.set_skip(Direction::ToClient, rd.len, rd.data.len() as u32); |
9dd7c381 VJ |
128 | return; |
129 | } | |
130 | ||
75d7c9d6 VJ |
131 | SCLogDebug!("SMBv2: read response => {:?}", rd); |
132 | ||
133 | // get the request info. If we don't have it, there is nothing | |
134 | // we can do except skip this record. | |
135 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); | |
136 | let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) { | |
137 | Some(o) => (o.offset, o.guid), | |
138 | None => { | |
aa8d64c2 | 139 | SCLogDebug!("SMBv2 READ response: reply to unknown request {:?}",rd); |
89cb3379 | 140 | state.set_skip(Direction::ToClient, rd.len, rd.data.len() as u32); |
75d7c9d6 VJ |
141 | return; |
142 | }, | |
143 | }; | |
144 | SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset); | |
145 | ||
caa79468 | 146 | let mut set_event_fileoverlap = false; |
75d7c9d6 | 147 | // look up existing tracker and if we have it update it |
89cb3379 | 148 | let found = match state.get_file_tx_by_fuid(&file_guid, Direction::ToClient) { |
75d7c9d6 VJ |
149 | Some((tx, files, flags)) => { |
150 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
151 | let file_id : u32 = tx.id as u32; | |
caa79468 PA |
152 | if offset < tdf.file_tracker.tracked { |
153 | set_event_fileoverlap = true; | |
154 | } | |
75d7c9d6 VJ |
155 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, |
156 | &tdf.file_name, rd.data, offset, | |
157 | rd.len, 0, false, &file_id); | |
158 | } | |
159 | true | |
160 | }, | |
161 | None => { false }, | |
162 | }; | |
ac4e8885 | 163 | SCLogDebug!("existing file tx? {}", found); |
75d7c9d6 VJ |
164 | if !found { |
165 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
ac4e8885 | 166 | let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) { |
75d7c9d6 VJ |
167 | Some(n) => (n.name.to_vec(), n.is_pipe), |
168 | _ => { (Vec::new(), false) }, | |
169 | }; | |
4d044483 | 170 | let mut is_dcerpc = if is_pipe || (share_name.len() == 0 && !is_pipe) { |
da0a976e | 171 | state.get_service_for_guid(&file_guid).1 |
4d044483 VJ |
172 | } else { |
173 | false | |
75d7c9d6 | 174 | }; |
4d044483 VJ |
175 | SCLogDebug!("SMBv2/READ: share_name {:?} is_pipe {} is_dcerpc {}", |
176 | share_name, is_pipe, is_dcerpc); | |
ac4e8885 VJ |
177 | |
178 | if share_name.len() == 0 && !is_pipe { | |
179 | SCLogDebug!("SMBv2/READ: no tree connect seen, we don't know if we are a pipe"); | |
180 | ||
4d044483 VJ |
181 | if smb_dcerpc_probe(rd.data) == true { |
182 | SCLogDebug!("SMBv2/READ: looks like dcerpc"); | |
183 | // insert fake tree to assist in follow up lookups | |
184 | let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); | |
185 | state.ssn2tree_map.insert(tree_key, tree); | |
186 | if !is_dcerpc { | |
187 | state.guid2name_map.insert(file_guid.to_vec(), b"suricata::dcerpc".to_vec()); | |
188 | } | |
189 | is_pipe = true; | |
190 | is_dcerpc = true; | |
191 | } else { | |
192 | SCLogDebug!("SMBv2/READ: not DCERPC"); | |
ac4e8885 VJ |
193 | } |
194 | } | |
195 | ||
75d7c9d6 VJ |
196 | if is_pipe && is_dcerpc { |
197 | SCLogDebug!("SMBv2 DCERPC read"); | |
198 | let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); | |
ac4e8885 | 199 | let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_READ, r.nt_status); |
75d7c9d6 VJ |
200 | smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data); |
201 | } else if is_pipe { | |
202 | SCLogDebug!("non-DCERPC pipe"); | |
89cb3379 | 203 | state.set_skip(Direction::ToClient, rd.len, rd.data.len() as u32); |
75d7c9d6 VJ |
204 | } else { |
205 | let file_name = match state.guid2name_map.get(&file_guid) { | |
206 | Some(n) => { n.to_vec() }, | |
15978d4e | 207 | None => { b"<unknown>".to_vec() }, |
75d7c9d6 | 208 | }; |
89cb3379 | 209 | let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, Direction::ToClient); |
75d7c9d6 VJ |
210 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
211 | let file_id : u32 = tx.id as u32; | |
caa79468 PA |
212 | if offset < tdf.file_tracker.tracked { |
213 | set_event_fileoverlap = true; | |
214 | } | |
75d7c9d6 VJ |
215 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, |
216 | &file_name, rd.data, offset, | |
217 | rd.len, 0, false, &file_id); | |
218 | tdf.share_name = share_name; | |
219 | } | |
220 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ); | |
221 | tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, | |
222 | r.session_id, r.tree_id, 0); // TODO move into new_file_tx | |
223 | } | |
224 | } | |
225 | ||
caa79468 PA |
226 | if set_event_fileoverlap { |
227 | state.set_event(SMBEvent::FileOverlap); | |
228 | } | |
89cb3379 | 229 | state.set_file_left(Direction::ToClient, rd.len, rd.data.len() as u32, file_guid.to_vec()); |
75d7c9d6 VJ |
230 | } |
231 | _ => { | |
ac4e8885 | 232 | SCLogDebug!("SMBv2: failed to parse read response"); |
75d7c9d6 VJ |
233 | state.set_event(SMBEvent::MalformedData); |
234 | } | |
235 | } | |
236 | } | |
237 | ||
238 | pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
239 | { | |
ac4e8885 | 240 | SCLogDebug!("SMBv2/WRITE: request record"); |
75d7c9d6 VJ |
241 | if smb2_create_new_tx(r.command) { |
242 | let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
243 | let tx = state.new_generic_tx(2, r.command, tx_key); | |
244 | tx.request_done = true; | |
245 | } | |
246 | match parse_smb2_request_write(r.data) { | |
13b73997 | 247 | Ok((_, wr)) => { |
75d7c9d6 VJ |
248 | /* update key-guid map */ |
249 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID); | |
250 | state.ssn2vec_map.insert(guid_key, wr.guid.to_vec()); | |
251 | ||
252 | let file_guid = wr.guid.to_vec(); | |
253 | let file_name = match state.guid2name_map.get(&file_guid) { | |
254 | Some(n) => n.to_vec(), | |
255 | None => Vec::new(), | |
256 | }; | |
257 | ||
caa79468 | 258 | let mut set_event_fileoverlap = false; |
89cb3379 | 259 | let found = match state.get_file_tx_by_fuid(&file_guid, Direction::ToServer) { |
75d7c9d6 VJ |
260 | Some((tx, files, flags)) => { |
261 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
262 | let file_id : u32 = tx.id as u32; | |
caa79468 PA |
263 | if wr.wr_offset < tdf.file_tracker.tracked { |
264 | set_event_fileoverlap = true; | |
265 | } | |
75d7c9d6 VJ |
266 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, |
267 | &file_name, wr.data, wr.wr_offset, | |
268 | wr.wr_len, 0, false, &file_id); | |
269 | } | |
270 | true | |
271 | }, | |
272 | None => { false }, | |
273 | }; | |
274 | if !found { | |
275 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
ac4e8885 VJ |
276 | let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) { |
277 | Some(n) => { (n.name.to_vec(), n.is_pipe) }, | |
278 | _ => { (Vec::new(), false) }, | |
75d7c9d6 | 279 | }; |
ac4e8885 | 280 | let mut is_dcerpc = if is_pipe || (share_name.len() == 0 && !is_pipe) { |
da0a976e | 281 | state.get_service_for_guid(wr.guid).1 |
ac4e8885 VJ |
282 | } else { |
283 | false | |
75d7c9d6 | 284 | }; |
4d044483 VJ |
285 | SCLogDebug!("SMBv2/WRITE: share_name {:?} is_pipe {} is_dcerpc {}", |
286 | share_name, is_pipe, is_dcerpc); | |
ac4e8885 VJ |
287 | |
288 | // if we missed the TREE connect we can't be sure if 'is_dcerpc' is correct | |
289 | if share_name.len() == 0 && !is_pipe { | |
290 | SCLogDebug!("SMBv2/WRITE: no tree connect seen, we don't know if we are a pipe"); | |
291 | ||
4d044483 VJ |
292 | if smb_dcerpc_probe(wr.data) == true { |
293 | SCLogDebug!("SMBv2/WRITE: looks like we have dcerpc"); | |
ac4e8885 | 294 | |
4d044483 VJ |
295 | let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); |
296 | state.ssn2tree_map.insert(tree_key, tree); | |
297 | if !is_dcerpc { | |
298 | state.guid2name_map.insert(file_guid.to_vec(), | |
299 | b"suricata::dcerpc".to_vec()); | |
300 | } | |
301 | is_pipe = true; | |
302 | is_dcerpc = true; | |
303 | } else { | |
304 | SCLogDebug!("SMBv2/WRITE: not DCERPC"); | |
ac4e8885 VJ |
305 | } |
306 | } | |
75d7c9d6 VJ |
307 | if is_pipe && is_dcerpc { |
308 | SCLogDebug!("SMBv2 DCERPC write"); | |
309 | let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); | |
310 | let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE); | |
311 | smb_write_dcerpc_record(state, vercmd, hdr, wr.data); | |
312 | } else if is_pipe { | |
313 | SCLogDebug!("non-DCERPC pipe: skip rest of the record"); | |
89cb3379 | 314 | state.set_skip(Direction::ToServer, wr.wr_len, wr.data.len() as u32); |
75d7c9d6 | 315 | } else { |
89cb3379 | 316 | let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, Direction::ToServer); |
75d7c9d6 VJ |
317 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
318 | let file_id : u32 = tx.id as u32; | |
caa79468 PA |
319 | if wr.wr_offset < tdf.file_tracker.tracked { |
320 | set_event_fileoverlap = true; | |
321 | } | |
75d7c9d6 VJ |
322 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, |
323 | &file_name, wr.data, wr.wr_offset, | |
324 | wr.wr_len, 0, false, &file_id); | |
325 | } | |
326 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE); | |
327 | tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, | |
328 | r.session_id, r.tree_id, 0); // TODO move into new_file_tx | |
329 | } | |
330 | } | |
caa79468 PA |
331 | |
332 | if set_event_fileoverlap { | |
333 | state.set_event(SMBEvent::FileOverlap); | |
334 | } | |
89cb3379 | 335 | state.set_file_left(Direction::ToServer, wr.wr_len, wr.data.len() as u32, file_guid.to_vec()); |
75d7c9d6 VJ |
336 | }, |
337 | _ => { | |
338 | state.set_event(SMBEvent::MalformedData); | |
339 | }, | |
340 | } | |
341 | } | |
342 | ||
343 | pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
344 | { | |
345 | SCLogDebug!("SMBv2 request record, command {} tree {} session {}", | |
346 | &smb2_command_string(r.command), r.tree_id, r.session_id); | |
347 | ||
75d7c9d6 VJ |
348 | let mut events : Vec<SMBEvent> = Vec::new(); |
349 | ||
350 | let have_tx = match r.command { | |
7b61f2c5 VJ |
351 | SMB2_COMMAND_SET_INFO => { |
352 | SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", r); | |
353 | let have_si_tx = match parse_smb2_request_setinfo(r.data) { | |
13b73997 | 354 | Ok((_, rd)) => { |
7b61f2c5 VJ |
355 | SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", rd); |
356 | ||
fde753d9 PA |
357 | match rd.data { |
358 | Smb2SetInfoRequestData::RENAME(ref ren) => { | |
359 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
360 | let mut newname = ren.name.to_vec(); | |
361 | newname.retain(|&i|i != 0x00); | |
362 | let oldname = match state.guid2name_map.get(rd.guid) { | |
363 | Some(n) => { n.to_vec() }, | |
364 | None => { b"<unknown>".to_vec() }, | |
365 | }; | |
366 | let tx = state.new_rename_tx(rd.guid.to_vec(), oldname, newname); | |
367 | tx.hdr = tx_hdr; | |
368 | tx.request_done = true; | |
369 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO); | |
370 | true | |
371 | } | |
372 | Smb2SetInfoRequestData::DISPOSITION(ref dis) => { | |
373 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
374 | let fname = match state.guid2name_map.get(rd.guid) { | |
375 | Some(n) => { n.to_vec() }, | |
9b8be5a6 PA |
376 | None => { |
377 | // try to find latest created file in case of chained commands | |
378 | let mut guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); | |
379 | guid_key.msg_id = guid_key.msg_id - 1; | |
380 | match state.ssn2vec_map.get(&guid_key) { | |
381 | Some(n) => { n.to_vec() }, | |
382 | None => { b"<unknown>".to_vec()}, | |
383 | } | |
384 | }, | |
fde753d9 PA |
385 | }; |
386 | let tx = state.new_setfileinfo_tx(fname, rd.guid.to_vec(), rd.class as u16, rd.infolvl as u16, dis.delete); | |
387 | tx.hdr = tx_hdr; | |
388 | tx.request_done = true; | |
389 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO); | |
390 | true | |
391 | } | |
392 | _ => false, | |
7b61f2c5 VJ |
393 | } |
394 | }, | |
13b73997 | 395 | Err(nom::Err::Incomplete(_n)) => { |
7b61f2c5 VJ |
396 | SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _n); |
397 | events.push(SMBEvent::MalformedData); | |
398 | false | |
399 | }, | |
13b73997 PC |
400 | Err(nom::Err::Error(_e)) | |
401 | Err(nom::Err::Failure(_e)) => { | |
7b61f2c5 VJ |
402 | SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _e); |
403 | events.push(SMBEvent::MalformedData); | |
404 | false | |
405 | }, | |
406 | }; | |
407 | have_si_tx | |
408 | }, | |
75d7c9d6 | 409 | SMB2_COMMAND_IOCTL => { |
283be3ca VJ |
410 | smb2_ioctl_request_record(state, r); |
411 | true | |
75d7c9d6 VJ |
412 | }, |
413 | SMB2_COMMAND_TREE_DISCONNECT => { | |
414 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
415 | state.ssn2tree_map.remove(&tree_key); | |
416 | false | |
417 | } | |
418 | SMB2_COMMAND_NEGOTIATE_PROTOCOL => { | |
419 | match parse_smb2_request_negotiate_protocol(r.data) { | |
13b73997 | 420 | Ok((_, rd)) => { |
75d7c9d6 VJ |
421 | let mut dialects : Vec<Vec<u8>> = Vec::new(); |
422 | for d in rd.dialects_vec { | |
423 | SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d)); | |
424 | let dvec = smb2_dialect_string(d).as_bytes().to_vec(); | |
425 | dialects.push(dvec); | |
426 | } | |
427 | ||
428 | let found = match state.get_negotiate_tx(2) { | |
429 | Some(_) => { | |
ea1e13cb | 430 | SCLogDebug!("WEIRD, should not have NEGOTIATE tx!"); |
75d7c9d6 VJ |
431 | true |
432 | }, | |
433 | None => { false }, | |
434 | }; | |
435 | if !found { | |
436 | let tx = state.new_negotiate_tx(2); | |
437 | if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { | |
438 | tdn.dialects2 = dialects; | |
6d56edc3 | 439 | tdn.client_guid = Some(rd.client_guid.to_vec()); |
75d7c9d6 VJ |
440 | } |
441 | tx.request_done = true; | |
442 | } | |
443 | true | |
444 | }, | |
445 | _ => { | |
446 | events.push(SMBEvent::MalformedData); | |
447 | false | |
448 | }, | |
449 | } | |
450 | }, | |
451 | SMB2_COMMAND_SESSION_SETUP => { | |
8bef1208 VJ |
452 | smb2_session_setup_request(state, r); |
453 | true | |
75d7c9d6 VJ |
454 | }, |
455 | SMB2_COMMAND_TREE_CONNECT => { | |
456 | match parse_smb2_request_tree_connect(r.data) { | |
13b73997 | 457 | Ok((_, tr)) => { |
75d7c9d6 VJ |
458 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); |
459 | let mut name_val = tr.share_name.to_vec(); | |
460 | name_val.retain(|&i|i != 0x00); | |
461 | if name_val.len() > 1 { | |
462 | name_val = name_val[1..].to_vec(); | |
463 | } | |
464 | ||
465 | let tx = state.new_treeconnect_tx(name_key, name_val); | |
466 | tx.request_done = true; | |
467 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT); | |
468 | true | |
469 | } | |
470 | _ => { | |
471 | events.push(SMBEvent::MalformedData); | |
472 | false | |
473 | }, | |
474 | } | |
475 | }, | |
476 | SMB2_COMMAND_READ => { | |
477 | match parse_smb2_request_read(r.data) { | |
13b73997 | 478 | Ok((_, rd)) => { |
75d7c9d6 VJ |
479 | SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}", |
480 | rd.guid, rd.rd_len, rd.rd_offset); | |
481 | ||
482 | // store read guid,offset in map | |
483 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); | |
484 | let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset); | |
485 | state.ssn2vecoffset_map.insert(guid_key, guidoff); | |
486 | }, | |
487 | _ => { | |
488 | events.push(SMBEvent::MalformedData); | |
489 | }, | |
490 | } | |
491 | false | |
492 | }, | |
493 | SMB2_COMMAND_CREATE => { | |
494 | match parse_smb2_request_create(r.data) { | |
13b73997 | 495 | Ok((_, cr)) => { |
75d7c9d6 VJ |
496 | let del = cr.create_options & 0x0000_1000 != 0; |
497 | let dir = cr.create_options & 0x0000_0001 != 0; | |
498 | ||
499 | SCLogDebug!("create_options {:08x}", cr.create_options); | |
500 | ||
501 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); | |
502 | state.ssn2vec_map.insert(name_key, cr.data.to_vec()); | |
503 | ||
504 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
505 | let tx = state.new_create_tx(&cr.data.to_vec(), | |
506 | cr.disposition, del, dir, tx_hdr); | |
507 | tx.vercmd.set_smb2_cmd(r.command); | |
508 | SCLogDebug!("TS CREATE TX {} created", tx.id); | |
509 | true | |
510 | }, | |
511 | _ => { | |
512 | events.push(SMBEvent::MalformedData); | |
513 | false | |
514 | }, | |
515 | } | |
516 | }, | |
517 | SMB2_COMMAND_WRITE => { | |
69cf5c9e | 518 | smb2_write_request_record(state, r); |
75d7c9d6 VJ |
519 | true // write handling creates both file tx and generic tx |
520 | }, | |
521 | SMB2_COMMAND_CLOSE => { | |
522 | match parse_smb2_request_close(r.data) { | |
13b73997 | 523 | Ok((_, cd)) => { |
89cb3379 | 524 | let found_ts = match state.get_file_tx_by_fuid(&cd.guid.to_vec(), Direction::ToServer) { |
75d7c9d6 VJ |
525 | Some((tx, files, flags)) => { |
526 | if !tx.request_done { | |
527 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
528 | tdf.file_tracker.close(files, flags); | |
529 | } | |
530 | } | |
531 | tx.request_done = true; | |
532 | tx.response_done = true; | |
533 | tx.set_status(SMB_NTSTATUS_SUCCESS, false); | |
534 | true | |
535 | }, | |
536 | None => { false }, | |
537 | }; | |
89cb3379 | 538 | let found_tc = match state.get_file_tx_by_fuid(&cd.guid.to_vec(), Direction::ToClient) { |
75d7c9d6 VJ |
539 | Some((tx, files, flags)) => { |
540 | if !tx.request_done { | |
541 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
542 | tdf.file_tracker.close(files, flags); | |
543 | } | |
544 | } | |
545 | tx.request_done = true; | |
546 | tx.response_done = true; | |
547 | tx.set_status(SMB_NTSTATUS_SUCCESS, false); | |
548 | true | |
549 | }, | |
550 | None => { false }, | |
551 | }; | |
552 | if !found_ts && !found_tc { | |
553 | SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid); | |
554 | } | |
555 | }, | |
556 | _ => { | |
557 | events.push(SMBEvent::MalformedData); | |
558 | }, | |
559 | } | |
560 | false | |
561 | }, | |
562 | _ => { | |
563 | false | |
564 | }, | |
565 | }; | |
566 | /* if we don't have a tx, create it here (maybe) */ | |
567 | if !have_tx { | |
568 | if smb2_create_new_tx(r.command) { | |
283be3ca | 569 | let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); |
75d7c9d6 VJ |
570 | let tx = state.new_generic_tx(2, r.command, tx_key); |
571 | SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}", | |
283be3ca | 572 | tx.id, r.command, r.session_id, r.tree_id, r.message_id); |
75d7c9d6 VJ |
573 | tx.set_events(events); |
574 | } | |
575 | } | |
576 | } | |
577 | ||
75d7c9d6 VJ |
578 | pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) |
579 | { | |
580 | SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}", | |
581 | &smb2_command_string(r.command), &smb_ntstatus_string(r.nt_status), | |
582 | r.tree_id, r.session_id, r.message_id); | |
583 | ||
75d7c9d6 VJ |
584 | let mut events : Vec<SMBEvent> = Vec::new(); |
585 | ||
586 | let have_tx = match r.command { | |
587 | SMB2_COMMAND_IOCTL => { | |
283be3ca VJ |
588 | smb2_ioctl_response_record(state, r); |
589 | true | |
75d7c9d6 VJ |
590 | }, |
591 | SMB2_COMMAND_SESSION_SETUP => { | |
8bef1208 | 592 | smb2_session_setup_response(state, r); |
75d7c9d6 VJ |
593 | true |
594 | }, | |
595 | SMB2_COMMAND_WRITE => { | |
ecbf10da VJ |
596 | if r.nt_status == SMB_NTSTATUS_SUCCESS { |
597 | match parse_smb2_response_write(r.data) | |
598 | { | |
ef575533 PA |
599 | Ok((_, _wr)) => { |
600 | SCLogDebug!("SMBv2: Write response => {:?}", _wr); | |
ecbf10da VJ |
601 | |
602 | /* search key-guid map */ | |
603 | let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID, | |
604 | r.session_id, r.tree_id, r.message_id); | |
ef575533 | 605 | let _guid_vec = match state.ssn2vec_map.remove(&guid_key) { |
ecbf10da VJ |
606 | Some(p) => p, |
607 | None => { | |
608 | SCLogDebug!("SMBv2 response: GUID NOT FOUND"); | |
609 | Vec::new() | |
610 | }, | |
611 | }; | |
ef575533 | 612 | SCLogDebug!("SMBv2 write response for GUID {:?}", _guid_vec); |
ecbf10da VJ |
613 | } |
614 | _ => { | |
615 | events.push(SMBEvent::MalformedData); | |
616 | }, | |
75d7c9d6 | 617 | } |
75d7c9d6 VJ |
618 | } |
619 | false // the request may have created a generic tx, so handle that here | |
620 | }, | |
621 | SMB2_COMMAND_READ => { | |
ac4e8885 VJ |
622 | if r.nt_status == SMB_NTSTATUS_SUCCESS || |
623 | r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { | |
69cf5c9e | 624 | smb2_read_response_record(state, r); |
75d7c9d6 VJ |
625 | false |
626 | ||
627 | } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE { | |
628 | SCLogDebug!("SMBv2: read response => EOF"); | |
629 | ||
630 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); | |
631 | let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) { | |
632 | Some(o) => o.guid, | |
633 | _ => { | |
ea1e13cb | 634 | SCLogDebug!("SMBv2 READ response: reply to unknown request"); |
75d7c9d6 VJ |
635 | Vec::new() |
636 | }, | |
637 | }; | |
89cb3379 | 638 | let found = match state.get_file_tx_by_fuid(&file_guid, Direction::ToClient) { |
75d7c9d6 VJ |
639 | Some((tx, files, flags)) => { |
640 | if !tx.request_done { | |
641 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
642 | tdf.file_tracker.close(files, flags); | |
643 | } | |
644 | } | |
645 | tx.set_status(r.nt_status, false); | |
646 | tx.request_done = true; | |
647 | false | |
648 | }, | |
649 | None => { false }, | |
650 | }; | |
651 | if !found { | |
652 | SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid); | |
653 | } | |
654 | false | |
655 | } else { | |
ea1e13cb | 656 | SCLogDebug!("SMBv2 READ: status {}", &smb_ntstatus_string(r.nt_status)); |
75d7c9d6 VJ |
657 | false |
658 | } | |
659 | }, | |
660 | SMB2_COMMAND_CREATE => { | |
661 | if r.nt_status == SMB_NTSTATUS_SUCCESS { | |
662 | match parse_smb2_response_create(r.data) { | |
13b73997 | 663 | Ok((_, cr)) => { |
75d7c9d6 VJ |
664 | SCLogDebug!("SMBv2: Create response => {:?}", cr); |
665 | ||
666 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); | |
0e05ef73 VJ |
667 | if let Some(mut p) = state.ssn2vec_map.remove(&guid_key) { |
668 | p.retain(|&i|i != 0x00); | |
669 | state.guid2name_map.insert(cr.guid.to_vec(), p); | |
670 | } else { | |
671 | SCLogDebug!("SMBv2 response: GUID NOT FOUND"); | |
672 | } | |
673 | ||
674 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
675 | if let Some(tx) = state.get_generic_tx(2, r.command, &tx_hdr) { | |
676 | SCLogDebug!("tx {} with {}/{} marked as done", | |
677 | tx.id, r.command, &smb2_command_string(r.command)); | |
678 | tx.set_status(r.nt_status, false); | |
679 | tx.response_done = true; | |
680 | ||
681 | if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data { | |
682 | tdn.create_ts = cr.create_ts.as_unix(); | |
683 | tdn.last_access_ts = cr.last_access_ts.as_unix(); | |
684 | tdn.last_write_ts = cr.last_write_ts.as_unix(); | |
685 | tdn.last_change_ts = cr.last_change_ts.as_unix(); | |
686 | tdn.size = cr.size; | |
fb986abe | 687 | tdn.guid = cr.guid.to_vec(); |
0e05ef73 | 688 | } |
75d7c9d6 VJ |
689 | } |
690 | } | |
691 | _ => { | |
692 | events.push(SMBEvent::MalformedData); | |
693 | }, | |
694 | } | |
0e05ef73 VJ |
695 | true |
696 | } else { | |
697 | false | |
75d7c9d6 | 698 | } |
75d7c9d6 VJ |
699 | }, |
700 | SMB2_COMMAND_TREE_DISCONNECT => { | |
701 | // normally removed when processing request, | |
702 | // but in case we missed that try again here | |
703 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
704 | state.ssn2tree_map.remove(&tree_key); | |
705 | false | |
706 | } | |
707 | SMB2_COMMAND_TREE_CONNECT => { | |
708 | if r.nt_status == SMB_NTSTATUS_SUCCESS { | |
709 | match parse_smb2_response_tree_connect(r.data) { | |
13b73997 | 710 | Ok((_, tr)) => { |
75d7c9d6 VJ |
711 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); |
712 | let mut share_name = Vec::new(); | |
713 | let is_pipe = tr.share_type == 2; | |
714 | let found = match state.get_treeconnect_tx(name_key) { | |
715 | Some(tx) => { | |
716 | if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { | |
c56f5e11 | 717 | tdn.share_type = tr.share_type; |
75d7c9d6 VJ |
718 | tdn.is_pipe = is_pipe; |
719 | tdn.tree_id = r.tree_id as u32; | |
720 | share_name = tdn.share_name.to_vec(); | |
721 | } | |
722 | // update hdr now that we have a tree_id | |
723 | tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); | |
724 | tx.response_done = true; | |
725 | tx.set_status(r.nt_status, false); | |
726 | true | |
727 | }, | |
728 | None => { false }, | |
729 | }; | |
730 | if found { | |
731 | let tree = SMBTree::new(share_name.to_vec(), is_pipe); | |
732 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
733 | state.ssn2tree_map.insert(tree_key, tree); | |
734 | } | |
735 | true | |
736 | } | |
737 | _ => { | |
738 | events.push(SMBEvent::MalformedData); | |
739 | false | |
740 | }, | |
741 | } | |
742 | } else { | |
743 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); | |
744 | let found = match state.get_treeconnect_tx(name_key) { | |
745 | Some(tx) => { | |
746 | tx.response_done = true; | |
747 | tx.set_status(r.nt_status, false); | |
748 | true | |
749 | }, | |
750 | None => { false }, | |
751 | }; | |
752 | found | |
753 | } | |
754 | }, | |
755 | SMB2_COMMAND_NEGOTIATE_PROTOCOL => { | |
6d56edc3 VJ |
756 | let res = if r.nt_status == SMB_NTSTATUS_SUCCESS { |
757 | parse_smb2_response_negotiate_protocol(r.data) | |
758 | } else { | |
759 | parse_smb2_response_negotiate_protocol_error(r.data) | |
760 | }; | |
761 | match res { | |
13b73997 | 762 | Ok((_, rd)) => { |
75d7c9d6 VJ |
763 | SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect)); |
764 | ||
765 | state.dialect = rd.dialect; | |
766 | let found2 = match state.get_negotiate_tx(2) { | |
767 | Some(tx) => { | |
6d56edc3 VJ |
768 | if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { |
769 | tdn.server_guid = rd.server_guid.to_vec(); | |
770 | } | |
75d7c9d6 VJ |
771 | tx.set_status(r.nt_status, false); |
772 | tx.response_done = true; | |
773 | true | |
774 | }, | |
775 | None => { false }, | |
776 | }; | |
777 | // SMB2 response to SMB1 request? | |
778 | let found1 = !found2 && match state.get_negotiate_tx(1) { | |
779 | Some(tx) => { | |
6d56edc3 VJ |
780 | if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { |
781 | tdn.server_guid = rd.server_guid.to_vec(); | |
782 | } | |
75d7c9d6 VJ |
783 | tx.set_status(r.nt_status, false); |
784 | tx.response_done = true; | |
785 | true | |
786 | }, | |
ecbf10da | 787 | None => { false }, |
75d7c9d6 VJ |
788 | }; |
789 | found1 || found2 | |
790 | }, | |
791 | _ => { | |
792 | events.push(SMBEvent::MalformedData); | |
793 | false | |
794 | } | |
795 | } | |
796 | }, | |
797 | _ => { | |
798 | SCLogDebug!("default case: no TX"); | |
799 | false | |
800 | }, | |
801 | }; | |
802 | if !have_tx { | |
283be3ca | 803 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); |
75d7c9d6 VJ |
804 | SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}", |
805 | &smb2_command_string(r.command), | |
283be3ca | 806 | r.session_id, r.tree_id, r.message_id); |
75d7c9d6 VJ |
807 | let _found = match state.get_generic_tx(2, r.command, &tx_hdr) { |
808 | Some(tx) => { | |
809 | SCLogDebug!("tx {} with {}/{} marked as done", | |
810 | tx.id, r.command, &smb2_command_string(r.command)); | |
811 | if r.nt_status != SMB_NTSTATUS_PENDING { | |
812 | tx.response_done = true; | |
813 | } | |
814 | tx.set_status(r.nt_status, false); | |
815 | tx.set_events(events); | |
816 | true | |
817 | }, | |
818 | _ => { | |
ea1e13cb | 819 | SCLogDebug!("no tx found for {:?}", r); |
75d7c9d6 VJ |
820 | false |
821 | }, | |
822 | }; | |
823 | } | |
824 | } |