1use crate::{Result, SmbError};
5
6fn utf16le(s: &str) -> Vec<u8> {
7 s.encode_utf16().flat_map(u16::to_le_bytes).collect()
8}
9fn u16(b: &[u8], o: usize) -> u16 {
10 u16::from_le_bytes([b[o], b[o + 1]])
11}
12fn u32(b: &[u8], o: usize) -> u32 {
13 u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]])
14}
15
16pub fn negotiate(client_guid: &[u8; 16]) -> Vec<u8> {
20 let dialects: [u16; 2] = [0x0202, 0x0210];
28 let mut b = Vec::new();
29 b.extend_from_slice(&36u16.to_le_bytes()); b.extend_from_slice(&(dialects.len() as u16).to_le_bytes()); b.extend_from_slice(&0x0001u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(client_guid);
35 b.extend_from_slice(&0u64.to_le_bytes()); for dialect in dialects {
37 b.extend_from_slice(&dialect.to_le_bytes());
38 }
39 b
40}
41
42pub fn session_setup(token: &[u8]) -> Vec<u8> {
46 let mut b = Vec::new();
47 b.extend_from_slice(&25u16.to_le_bytes()); b.push(0); b.push(0x01); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); let sec_off = 64u16 + 24; b.extend_from_slice(&sec_off.to_le_bytes()); b.extend_from_slice(&(token.len() as u16).to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(token);
57 b
58}
59
60pub fn session_setup_token(msg: &[u8]) -> Result<Vec<u8>> {
62 let body = msg.get(64..).ok_or(SmbError::Truncated)?;
64 let off = u16(body, 4) as usize; let len = u16(body, 6) as usize;
66 msg.get(off..off + len)
67 .map(|s| s.to_vec())
68 .ok_or(SmbError::Truncated)
69}
70
71pub fn tree_connect(path: &str) -> Vec<u8> {
74 let name = utf16le(path);
75 let mut b = Vec::new();
76 b.extend_from_slice(&9u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); let path_off = 64u16 + 8;
79 b.extend_from_slice(&path_off.to_le_bytes()); b.extend_from_slice(&(name.len() as u16).to_le_bytes()); b.extend_from_slice(&name);
82 b
83}
84
85pub fn create_pipe(name: &str) -> Vec<u8> {
89 let n = utf16le(name);
90 let mut b = Vec::new();
91 b.extend_from_slice(&57u16.to_le_bytes()); b.push(0); b.push(0); b.extend_from_slice(&2u32.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(&0x0012_019Fu32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0x0000_0007u32.to_le_bytes()); b.extend_from_slice(&0x0000_0001u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); let name_off = 64u16 + 56;
103 b.extend_from_slice(&name_off.to_le_bytes()); b.extend_from_slice(&(n.len() as u16).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&n);
108 b
109}
110
111pub fn create_file(path: &str, access: u32, share: u32, disposition: u32, options: u32) -> Vec<u8> {
114 let n = utf16le(path);
115 let mut b = Vec::new();
116 b.extend_from_slice(&57u16.to_le_bytes()); b.push(0); b.push(0); b.extend_from_slice(&2u32.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(&0u64.to_le_bytes()); b.extend_from_slice(&access.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&share.to_le_bytes()); b.extend_from_slice(&disposition.to_le_bytes()); b.extend_from_slice(&options.to_le_bytes()); let name_off = 64u16 + 56;
134 b.extend_from_slice(&name_off.to_le_bytes()); b.extend_from_slice(&(n.len() as u16).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); if n.is_empty() {
139 b.push(0); } else {
141 b.extend_from_slice(&n);
142 }
143 b
144}
145
146pub fn read_req(file_id: &[u8; 16], offset: u64, length: u32) -> Vec<u8> {
148 let mut b = Vec::new();
149 b.extend_from_slice(&49u16.to_le_bytes()); b.push(0); b.push(0); b.extend_from_slice(&length.to_le_bytes()); b.extend_from_slice(&offset.to_le_bytes()); b.extend_from_slice(file_id);
155 b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.push(0); b
162}
163
164pub fn read_output(msg: &[u8]) -> Result<Vec<u8>> {
166 let body = msg.get(64..).ok_or(SmbError::Truncated)?;
167 let data_off = *body.get(2).ok_or(SmbError::Truncated)? as usize; let data_len = u32(body, 4) as usize;
169 msg.get(data_off..data_off + data_len)
170 .map(|s| s.to_vec())
171 .ok_or(SmbError::Truncated)
172}
173
174pub fn write_req(file_id: &[u8; 16], offset: u64, data: &[u8]) -> Vec<u8> {
176 let mut b = Vec::new();
177 b.extend_from_slice(&49u16.to_le_bytes()); b.extend_from_slice(&(64u16 + 48).to_le_bytes()); b.extend_from_slice(&(data.len() as u32).to_le_bytes()); b.extend_from_slice(&offset.to_le_bytes()); b.extend_from_slice(file_id);
182 b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(data);
188 b
189}
190
191pub fn close_req(file_id: &[u8; 16]) -> Vec<u8> {
193 let mut b = Vec::new();
194 b.extend_from_slice(&24u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(file_id);
198 b
199}
200
201pub fn create_file_id(msg: &[u8]) -> Result<[u8; 16]> {
203 msg.get(128..144)
205 .map(|s| s.try_into().unwrap())
206 .ok_or(SmbError::Truncated)
207}
208
209#[derive(Clone, Debug, PartialEq, Eq)]
211pub struct DirEntry {
212 pub name: String,
213 pub is_dir: bool,
214 pub size: u64,
215}
216
217pub const FILE_DIRECTORY_INFORMATION: u8 = 0x01;
225
226pub fn query_directory_output(msg: &[u8]) -> Result<Vec<u8>> {
235 let body = msg.get(64..).ok_or(SmbError::Truncated)?;
236 if body.len() < 8 {
237 return Err(SmbError::Truncated);
238 }
239 let off = u16(body, 2) as usize; let len = u32(body, 4) as usize; msg.get(off..off.checked_add(len).ok_or(SmbError::Truncated)?)
242 .map(|s| s.to_vec())
243 .ok_or(SmbError::Truncated)
244}
245
246pub fn query_directory_req(file_id: &[u8; 16], pattern: &str, output_len: u32) -> Vec<u8> {
251 let n = utf16le(pattern);
252 let mut b = Vec::new();
253 b.extend_from_slice(&33u16.to_le_bytes()); b.push(FILE_DIRECTORY_INFORMATION); b.push(0); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(file_id);
258 let name_off = 64u16 + 32; b.extend_from_slice(&name_off.to_le_bytes()); b.extend_from_slice(&(n.len() as u16).to_le_bytes()); b.extend_from_slice(&output_len.to_le_bytes()); if n.is_empty() {
263 b.push(0); } else {
265 b.extend_from_slice(&n);
266 }
267 b
268}
269
270pub fn parse_directory_info(msg: &[u8]) -> Result<Vec<DirEntry>> {
275 const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
276 let body = msg.get(64..).ok_or(SmbError::Truncated)?;
277 if body.len() < 8 {
281 return Ok(Vec::new());
282 }
283 let out_off = u16(body, 2) as usize; let out_len = u32(body, 4) as usize;
285 let buf = msg
286 .get(out_off..out_off.checked_add(out_len).ok_or(SmbError::Truncated)?)
287 .ok_or(SmbError::Truncated)?;
288
289 let mut entries = Vec::new();
290 let mut pos = 0usize;
291 for _ in 0..100_000 {
293 let rec = match buf.get(pos..) {
294 Some(r) if r.len() >= 64 => r,
295 _ => break,
296 };
297 let next = u32(rec, 0) as usize; let attrs = u32(rec, 56); let name_len = u32(rec, 60) as usize; if let Some(name_bytes) = rec.get(64..64usize.saturating_add(name_len)) {
302 let units: Vec<u16> = name_bytes
303 .chunks_exact(2)
304 .map(|c| u16::from_le_bytes([c[0], c[1]]))
305 .collect();
306 let name = String::from_utf16_lossy(&units);
307 if name != "." && name != ".." && !name.is_empty() {
308 entries.push(DirEntry {
309 name,
310 is_dir: attrs & FILE_ATTRIBUTE_DIRECTORY != 0,
311 size: u64::from_le_bytes(
312 rec.get(40..48)
313 .and_then(|s| s.try_into().ok())
314 .unwrap_or([0; 8]),
315 ),
316 });
317 }
318 } else {
319 break; }
321 if next == 0 {
322 break; }
324 pos = match pos.checked_add(next) {
326 Some(p) if p > pos => p,
327 _ => break,
328 };
329 }
330 Ok(entries)
331}
332
333pub const FSCTL_PIPE_TRANSCEIVE: u32 = 0x0011_C017;
336
337pub fn ioctl_transceive(file_id: &[u8; 16], input: &[u8]) -> Vec<u8> {
339 let mut b = Vec::new();
340 b.extend_from_slice(&57u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&FSCTL_PIPE_TRANSCEIVE.to_le_bytes()); b.extend_from_slice(file_id);
344 let input_off = 64u32 + 56;
345 b.extend_from_slice(&input_off.to_le_bytes()); b.extend_from_slice(&(input.len() as u32).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&input_off.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0x0001_0000u32.to_le_bytes()); b.extend_from_slice(&0x0000_0001u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(input);
354 b
355}
356
357pub fn ioctl_output(msg: &[u8]) -> Result<Vec<u8>> {
359 let body = msg.get(64..).ok_or(SmbError::Truncated)?;
362 let out_off = u32(body, 32) as usize; let out_len = u32(body, 36) as usize;
364 msg.get(out_off..out_off + out_len)
365 .map(|s| s.to_vec())
366 .ok_or(SmbError::Truncated)
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn negotiate_offers_dialect_210() {
375 let b = negotiate(&[0; 16]);
376 assert_eq!(u16(&b, 0), 36); assert_eq!(u16(&b, 2), 2); assert_eq!(u16(&b, 36), 0x0202);
380 assert_eq!(u16(&b, 38), 0x0210);
381 }
382
383 #[test]
384 fn create_pipe_name_offset_correct() {
385 let b = create_pipe("samr");
386 assert_eq!(u16(&b, 0), 57);
387 assert_eq!(u16(&b, 44), 64 + 56); assert_eq!(u16(&b, 46), 8); }
390
391 #[test]
392 fn ioctl_uses_transceive_ctlcode() {
393 let b = ioctl_transceive(&[0; 16], &[1, 2, 3]);
394 assert_eq!(u32(&b, 4), FSCTL_PIPE_TRANSCEIVE);
395 assert_eq!(u32(&b, 28), 3); }
397
398 #[test]
399 fn query_directory_req_shape() {
400 let b = query_directory_req(&[0; 16], "*", 0x1_0000);
401 assert_eq!(u16(&b, 0), 33); assert_eq!(b[2], 0x01); assert_eq!(u16(&b, 24), 64 + 32); assert_eq!(u16(&b, 26), 2); assert_eq!(u32(&b, 28), 0x1_0000); }
407
408 #[test]
412 fn parse_directory_info_reads_entries_and_filters_dot() {
413 fn rec(out: &mut Vec<u8>, next: u32, attrs: u32, size: u64, name: &str) {
414 let units: Vec<u16> = name.encode_utf16().collect();
415 let name_bytes: Vec<u8> = units.iter().flat_map(|u| u.to_le_bytes()).collect();
416 let start = out.len();
417 out.extend_from_slice(&next.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out.extend_from_slice(&[0u8; 32]); out.extend_from_slice(&size.to_le_bytes()); out.extend_from_slice(&0u64.to_le_bytes()); out.extend_from_slice(&attrs.to_le_bytes()); out.extend_from_slice(&(name_bytes.len() as u32).to_le_bytes()); out.extend_from_slice(&name_bytes); if next != 0 {
426 while out.len() - start < next as usize {
428 out.push(0);
429 }
430 }
431 }
432 let mut buf = Vec::new();
433 rec(&mut buf, 72, 0x10, 0, "."); rec(&mut buf, 72, 0x10, 0, ".."); rec(&mut buf, 80, 0x10, 0, "Policies"); rec(&mut buf, 0, 0x20, 1234, "GptTmpl.inf"); let out_off = 64u16 + 8;
441 let mut msg = vec![0u8; 64];
442 msg.extend_from_slice(&9u16.to_le_bytes()); msg.extend_from_slice(&out_off.to_le_bytes()); msg.extend_from_slice(&(buf.len() as u32).to_le_bytes()); msg.extend_from_slice(&buf);
446
447 let entries = parse_directory_info(&msg).unwrap();
448 assert_eq!(entries.len(), 2);
449 assert_eq!(entries[0].name, "Policies");
450 assert!(entries[0].is_dir);
451 assert_eq!(entries[1].name, "GptTmpl.inf");
452 assert!(!entries[1].is_dir);
453 assert_eq!(entries[1].size, 1234);
454 }
455
456 #[test]
457 fn parse_directory_info_survives_hostile_input() {
458 for cut in 0..80 {
460 let _ = parse_directory_info(&vec![0u8; cut]);
461 }
462 let mut buf = vec![0u8; 64];
465 buf[60..64].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes()); let out_off = 64u16 + 8;
467 let mut msg = vec![0u8; 64];
468 msg.extend_from_slice(&9u16.to_le_bytes());
469 msg.extend_from_slice(&out_off.to_le_bytes());
470 msg.extend_from_slice(&(buf.len() as u32).to_le_bytes());
471 msg.extend_from_slice(&buf);
472 let entries = parse_directory_info(&msg).unwrap();
473 assert!(entries.is_empty()); }
475
476 #[test]
477 fn create_file_carries_access_and_options() {
478 let b = create_file("Windows\\Temp\\x.out", 0x0013_0081, 0x7, 1, 0x1060);
479 assert_eq!(u16(&b, 0), 57); assert_eq!(u32(&b, 24), 0x0013_0081); assert_eq!(u32(&b, 32), 0x7); assert_eq!(u32(&b, 36), 1); assert_eq!(u32(&b, 40), 0x1060); assert_eq!(u16(&b, 44), 64 + 56); assert_eq!(
486 u16(&b, 46),
487 "Windows\\Temp\\x.out".chars().count() as u16 * 2
488 );
489 }
490
491 #[test]
492 fn read_req_offset_and_length() {
493 let b = read_req(&[0xAB; 16], 0x1_0000, 0x4000);
494 assert_eq!(u16(&b, 0), 49); assert_eq!(u32(&b, 4), 0x4000); assert_eq!(u32(&b, 8), 0x1_0000); assert_eq!(&b[16..32], &[0xAB; 16]); }
499
500 #[test]
501 fn close_req_shape() {
502 let b = close_req(&[0xCD; 16]);
503 assert_eq!(u16(&b, 0), 24); assert_eq!(&b[8..24], &[0xCD; 16]); }
506}