1pub const NTLMSSP_SIG: &[u8; 8] = b"NTLMSSP\0";
10
11pub const MSG_TYPE1: u32 = 1;
13pub const MSG_TYPE2: u32 = 2;
15pub const MSG_TYPE3: u32 = 3;
17
18pub const NEGOTIATE_UNICODE: u32 = 0x0000_0001;
20pub const REQUEST_TARGET: u32 = 0x0000_0004;
22pub const NEGOTIATE_NTLM: u32 = 0x0000_0200;
24pub const NEGOTIATE_ANONYMOUS: u32 = 0x0000_0800;
26pub const NEGOTIATE_DOMAIN_SUPPLIED: u32 = 0x0000_1000;
28pub const NEGOTIATE_ALWAYS_SIGN: u32 = 0x0000_8000;
30pub const NEGOTIATE_EXTENDED_SESSIONSECURITY: u32 = 0x0008_0000;
32pub const NEGOTIATE_TARGET_INFO: u32 = 0x0080_0000;
34pub const NEGOTIATE_128: u32 = 0x2000_0000;
36pub const NEGOTIATE_VERSION: u32 = 0x0200_0000;
38pub const TARGET_TYPE_DOMAIN: u32 = 0x0001_0000;
40pub const NEGOTIATE_SIGN: u32 = 0x0000_0010;
42
43pub const NEGOTIATE_SEAL: u32 = 0x0000_0020;
45
46pub const NEGOTIATE_KEY_EXCH: u32 = 0x4000_0000;
49
50#[derive(Debug, Default)]
52pub struct Type3 {
53 pub user: String,
55 pub domain: String,
57 pub workstation: String,
59 pub lm_response: Vec<u8>,
61 pub ntlm_response: Vec<u8>,
63 pub encrypted_session_key: Vec<u8>,
65 pub flags: u32,
67}
68
69#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
70fn rd_u32(b: &[u8], off: usize) -> u32 {
71 b.get(off..off + 4)
72 .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
73 .unwrap_or(0)
74}
75
76#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
77fn rd_u16(b: &[u8], off: usize) -> u16 {
78 b.get(off..off + 2)
79 .map(|s| u16::from_le_bytes(s.try_into().unwrap()))
80 .unwrap_or(0)
81}
82
83fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
85 haystack.windows(needle.len()).position(|w| w == needle)
86}
87
88pub fn unwrap_blob(blob: &[u8]) -> Option<&[u8]> {
91 find(blob, NTLMSSP_SIG).map(|pos| &blob[pos..])
92}
93
94#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
97pub fn is_spnego(blob: &[u8]) -> bool {
98 matches!(blob.first(), Some(0x60) | Some(0xA1)) || (blob.len() > 1 && blob[0] == 0x06)
99}
100
101#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
103pub fn msg_type(blob: &[u8]) -> Option<u32> {
104 if blob.len() < 12 || &blob[..8] != NTLMSSP_SIG {
105 return None;
106 }
107 Some(rd_u32(blob, 8))
108}
109
110#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
117pub fn build_type2(challenge: &[u8; 8], domain: &str, hostname: &str) -> Vec<u8> {
118 let mut ti = Vec::new();
120 {
121 let put_av = |ti: &mut Vec<u8>, typ: u16, val: &[u16]| {
122 ti.extend_from_slice(&typ.to_le_bytes());
123 ti.extend_from_slice(&((val.len() * 2) as u16).to_le_bytes());
124 for u in val {
125 ti.extend_from_slice(&u.to_le_bytes());
126 }
127 };
128 put_av(&mut ti, 0x0001, &domain.encode_utf16().collect::<Vec<_>>()); put_av(&mut ti, 0x0002, &hostname.encode_utf16().collect::<Vec<_>>()); put_av(&mut ti, 0x0003, &domain.encode_utf16().collect::<Vec<_>>()); put_av(&mut ti, 0x0004, &hostname.encode_utf16().collect::<Vec<_>>()); let now = smb_server_proto::types::FileTime::now().0;
134 let ts: [u16; 4] = [
135 (now & 0xffff) as u16,
136 ((now >> 16) & 0xffff) as u16,
137 ((now >> 32) & 0xffff) as u16,
138 ((now >> 48) & 0xffff) as u16,
139 ];
140 put_av(&mut ti, 0x0007, &ts);
141 ti.extend_from_slice(&[0u8; 4]);
143 }
144
145 let dom_utf16: Vec<u8> = domain.encode_utf16().flat_map(|u| u.to_le_bytes()).collect();
146
147 let mut flags = NEGOTIATE_UNICODE
148 | REQUEST_TARGET
149 | NEGOTIATE_NTLM
150 | NEGOTIATE_EXTENDED_SESSIONSECURITY
151 | NEGOTIATE_TARGET_INFO
152 | TARGET_TYPE_DOMAIN
153 | NEGOTIATE_128;
154 if let Ok(extra) = std::env::var("RUSTSMB_T2_FLAGS") {
156 for part in extra.split(',') {
157 let part = part.trim();
158 if let Some(neg) = part.strip_prefix('-') {
159 if let Ok(v) = u32::from_str_radix(neg.trim_start_matches("0x"), 16) {
160 flags &= !v;
161 }
162 } else if let Ok(v) = u32::from_str_radix(part.trim_start_matches("0x"), 16) {
163 flags |= v;
164 }
165 }
166 }
167 flags |= NEGOTIATE_VERSION;
168
169 const PAYLOAD_BASE: usize = 56; let mut m = Vec::with_capacity(PAYLOAD_BASE + dom_utf16.len() + ti.len());
172 m.extend_from_slice(NTLMSSP_SIG);
173 m.extend_from_slice(&MSG_TYPE2.to_le_bytes());
174 m.extend_from_slice(&(dom_utf16.len() as u16).to_le_bytes()); m.extend_from_slice(&(dom_utf16.len() as u16).to_le_bytes()); m.extend_from_slice(&(PAYLOAD_BASE as u32).to_le_bytes()); m.extend_from_slice(&flags.to_le_bytes());
178 m.extend_from_slice(challenge);
179 m.extend_from_slice(&[0u8; 8]); m.extend_from_slice(&(ti.len() as u16).to_le_bytes());
181 m.extend_from_slice(&(ti.len() as u16).to_le_bytes());
182 m.extend_from_slice(&((PAYLOAD_BASE + dom_utf16.len()) as u32).to_le_bytes());
183 m.extend_from_slice(&[0x0a, 0x00, 0xf9, 0x38, 0x00, 0x00, 0x00, 0x0f]);
185 m.extend_from_slice(&dom_utf16);
186 m.extend_from_slice(&ti);
187 m
188}
189
190#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
192pub fn parse_type3_session_key(blob: &[u8]) -> Vec<u8> {
193 let len = rd_u16(blob, 52) as usize;
194 let off = rd_u32(blob, 56) as usize;
195 if len == 0 || off + len > blob.len() {
196 return Vec::new();
197 }
198 blob[off..off + len].to_vec()
199}
200
201#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
204pub fn parse_type3(blob: &[u8]) -> Option<Type3> {
205 if blob.len() < 32 || msg_type(blob) != Some(MSG_TYPE3) {
206 return None;
207 }
208 let field = |len_off: usize, off_off: usize| -> Vec<u8> {
209 let len = rd_u16(blob, len_off) as usize;
210 let off = rd_u32(blob, off_off) as usize;
211 if len == 0 || off + len > blob.len() {
212 return Vec::new();
213 }
214 blob[off..off + len].to_vec()
215 };
216 let utf16 = |b: &[u8]| -> String {
217 let units: Vec<u16> = b
218 .chunks_exact(2)
219 .map(|c| c[0] as u16 | ((c[1] as u16) << 8))
220 .collect();
221 String::from_utf16_lossy(&units)
222 };
223 Some(Type3 {
224 lm_response: field(12, 16),
225 ntlm_response: field(20, 24),
226 domain: utf16(&field(28, 32)),
227 user: utf16(&field(36, 40)),
228 workstation: utf16(&field(44, 48)),
229 encrypted_session_key: parse_type3_session_key(blob),
230 flags: rd_u32(blob, 60),
231 })
232}
233
234#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))]
238fn der_len(len: usize) -> Vec<u8> {
239 if len < 0x80 {
240 vec![len as u8]
241 } else if len < 0x100 {
242 vec![0x81, len as u8]
243 } else {
244 vec![0x82, (len >> 8) as u8, (len & 0xff) as u8]
245 }
246}
247
248fn der_tlv(tag: u8, content: &[u8]) -> Vec<u8> {
249 let mut out = vec![tag];
250 out.extend_from_slice(&der_len(content.len()));
251 out.extend_from_slice(content);
252 out
253}
254
255#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub fn wrap_negtoken_targ(token: &[u8]) -> Vec<u8> {
259 let result = der_tlv(0xA0, &der_tlv(0x0A, &[0x01]));
260 let oid_bytes = [0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a]; let mech = der_tlv(0xA1, &der_tlv(0x06, &oid_bytes));
262 let resp = der_tlv(0xA2, &der_tlv(0x04, token));
263 let mut seq_content = result;
264 seq_content.extend_from_slice(&mech);
265 seq_content.extend_from_slice(&resp);
266 der_tlv(0xA1, &der_tlv(0x30, &seq_content))
267}
268
269#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub fn wrap_accept_complete() -> Vec<u8> {
272 let result = der_tlv(0xA0, &der_tlv(0x0A, &[0x00]));
273 der_tlv(0xA1, &der_tlv(0x30, &result))
274}
275
276#[cfg_attr(dylint_lib = "no_magic_numbers", allow(no_magic_numbers))] pub fn wrap_accept_complete_with_mic(mic: &[u8]) -> Vec<u8> {
284 let result = der_tlv(0xA0, &der_tlv(0x0A, &[0x00]));
285 let mic_field = der_tlv(0xA3, &der_tlv(0x04, mic));
286 let mut seq = result;
287 seq.extend_from_slice(&mic_field);
288 der_tlv(0xA1, &der_tlv(0x30, &seq))
289}