mirror of https://github.com/OISF/suricata
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
881 lines
27 KiB
Rust
881 lines
27 KiB
Rust
/* Copyright (C) 2017 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 crate::common::nom7::take_until_and_consume;
|
|
use crate::smb::error::SmbError;
|
|
use crate::smb::smb::*;
|
|
use crate::smb::smb_records::*;
|
|
use nom7::bytes::streaming::{tag, take};
|
|
use nom7::combinator::{complete, cond, peek, rest, verify};
|
|
use nom7::error::{make_error, ErrorKind};
|
|
use nom7::Err;
|
|
use nom7::multi::many1;
|
|
use nom7::number::streaming::{le_u8, le_u16, le_u32, le_u64};
|
|
use nom7::IResult;
|
|
|
|
pub const SMB1_HEADER_SIZE: usize = 32;
|
|
|
|
// SMB_FLAGS_REPLY in Microsoft docs.
|
|
const SMB1_FLAGS_RESPONSE: u8 = 0x80;
|
|
|
|
fn smb_get_unicode_string_with_offset(i: &[u8], offset: usize) -> IResult<&[u8], Vec<u8>, SmbError>
|
|
{
|
|
let (i, _) = cond(offset % 2 == 1, take(1_usize))(i)?;
|
|
smb_get_unicode_string(i)
|
|
}
|
|
|
|
/// take a string, unicode or ascii based on record
|
|
pub fn smb1_get_string<'a>(i: &'a[u8], r: &SmbRecord, offset: usize) -> IResult<&'a[u8], Vec<u8>, SmbError> {
|
|
if r.has_unicode_support() {
|
|
smb_get_unicode_string_with_offset(i, offset)
|
|
} else {
|
|
smb_get_ascii_string(i)
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbParamBlockAndXHeader {
|
|
pub wct: u8,
|
|
pub andx_command: u8,
|
|
pub andx_offset: u16,
|
|
}
|
|
|
|
pub fn smb1_parse_andx_header(i: &[u8]) -> IResult<&[u8], SmbParamBlockAndXHeader> {
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, andx_command) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, andx_offset) = le_u16(i)?;
|
|
let hdr = SmbParamBlockAndXHeader {
|
|
wct,
|
|
andx_command,
|
|
andx_offset,
|
|
};
|
|
Ok((i, hdr))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Smb1WriteRequestRecord<'a> {
|
|
pub offset: u64,
|
|
pub len: u32,
|
|
pub fid: &'a[u8],
|
|
pub data: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb1_write_request_record(i: &[u8]) -> IResult<&[u8], Smb1WriteRequestRecord> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let (i, _count) = le_u16(i)?;
|
|
let (i, offset) = le_u32(i)?;
|
|
let (i, _remaining) = le_u16(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let (i, _buffer_format) = le_u8(i)?;
|
|
let (i, data_len) = le_u16(i)?;
|
|
let (i, file_data) = take(data_len)(i)?;
|
|
let record = Smb1WriteRequestRecord {
|
|
offset: offset as u64,
|
|
len: data_len as u32,
|
|
fid,
|
|
data:file_data,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
pub fn parse_smb1_write_andx_request_record(i : &[u8], andx_offset: usize) -> IResult<&[u8], Smb1WriteRequestRecord> {
|
|
let origin_i = i;
|
|
let ax = andx_offset as u16;
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _andx_command) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, _andx_offset) = le_u16(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let (i, offset) = le_u32(i)?;
|
|
let (i, _) = take(4_usize)(i)?; // reserved
|
|
let (i, _write_mode) = le_u16(i)?;
|
|
let (i, _remaining) = le_u16(i)?;
|
|
let (i, data_len_high) = le_u16(i)?;
|
|
let (i, data_len_low) = le_u16(i)?;
|
|
let data_len = ((data_len_high as u32) << 16)|(data_len_low as u32);
|
|
let (i, data_offset) = le_u16(i)?;
|
|
if data_offset < 0x3c || data_offset < ax{
|
|
return Err(Err::Error(make_error(i, ErrorKind::LengthValue)));
|
|
}
|
|
let (i, high_offset) = cond(wct == 14, le_u32)(i)?;
|
|
let (_i, _bcc) = le_u16(i)?;
|
|
let (i, _padding_data) = take(data_offset-ax)(origin_i)?;
|
|
let (i, file_data) = take(std::cmp::min(data_len, i.len() as u32))(i)?;
|
|
|
|
let record = Smb1WriteRequestRecord {
|
|
offset: ((high_offset.unwrap_or(0) as u64) << 32) | offset as u64,
|
|
len: data_len,
|
|
fid,
|
|
data: file_data,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
pub fn parse_smb1_write_and_close_request_record(i: &[u8]) -> IResult<&[u8], Smb1WriteRequestRecord> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let (i, count) = le_u16(i)?;
|
|
let (i, offset) = le_u32(i)?;
|
|
let (i, _last_write) = take(4_usize)(i)?;
|
|
let (i, bcc) = le_u16(i)?;
|
|
let (i, _padding) = cond(bcc > count, |b| take(bcc - count)(b))(i)?;
|
|
let (i, file_data) = take(count)(i)?;
|
|
let record = Smb1WriteRequestRecord {
|
|
offset: offset as u64,
|
|
len: count as u32,
|
|
fid,
|
|
data: file_data,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Smb1NegotiateProtocolResponseRecord<'a> {
|
|
pub dialect_idx: u16,
|
|
pub server_guid: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb1_negotiate_protocol_response_record_error(i: &[u8])
|
|
-> IResult<&[u8], Smb1NegotiateProtocolResponseRecord> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let record = Smb1NegotiateProtocolResponseRecord {
|
|
dialect_idx: 0,
|
|
server_guid: &[],
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
pub fn parse_smb1_negotiate_protocol_response_record_ok(i: &[u8])
|
|
-> IResult<&[u8], Smb1NegotiateProtocolResponseRecord> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, dialect_idx) = le_u16(i)?;
|
|
let (i, _sec_mode) = le_u8(i)?;
|
|
let (i, _) = take(16_usize)(i)?;
|
|
let (i, _caps) = le_u32(i)?;
|
|
let (i, _sys_time) = le_u64(i)?;
|
|
let (i, _server_tz) = le_u16(i)?;
|
|
let (i, _challenge_len) = le_u8(i)?;
|
|
let (i, bcc) = le_u16(i)?;
|
|
let (i, server_guid) = cond(bcc >= 16, take(16_usize))(i)?;
|
|
let record = Smb1NegotiateProtocolResponseRecord {
|
|
dialect_idx,
|
|
server_guid: server_guid.unwrap_or(&[]),
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
pub fn parse_smb1_negotiate_protocol_response_record(i: &[u8])
|
|
-> IResult<&[u8], Smb1NegotiateProtocolResponseRecord> {
|
|
let (i, wct) = peek(le_u8)(i)?;
|
|
match wct {
|
|
0 => parse_smb1_negotiate_protocol_response_record_error(i),
|
|
_ => parse_smb1_negotiate_protocol_response_record_ok(i),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Smb1NegotiateProtocolRecord<'a> {
|
|
pub dialects: Vec<&'a [u8]>,
|
|
}
|
|
|
|
pub fn parse_smb1_negotiate_protocol_record(i: &[u8])
|
|
-> IResult<&[u8], Smb1NegotiateProtocolRecord> {
|
|
let (i, _wtc) = le_u8(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
// dialects is a list of [1 byte buffer format][string][0 terminator]
|
|
let (i, dialects) = many1(complete(take_until_and_consume(b"\0")))(i)?;
|
|
let record = Smb1NegotiateProtocolRecord { dialects };
|
|
Ok((i, record))
|
|
}
|
|
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Smb1ResponseRecordTreeConnectAndX<'a> {
|
|
pub service: &'a[u8],
|
|
pub nativefs: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_connect_tree_andx_response_record(i: &[u8])
|
|
-> IResult<&[u8], Smb1ResponseRecordTreeConnectAndX> {
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _andx_command) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, _andx_offset) = le_u16(i)?;
|
|
let (i, _) = cond(wct >= 3, take(2_usize))(i)?; // optional support
|
|
let (i, _) = cond(wct == 7, take(8_usize))(i)?; // access masks
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let (i, service) = take_until_and_consume(b"\x00")(i)?;
|
|
let (i, nativefs) = take_until_and_consume(b"\x00")(i)?;
|
|
let record = Smb1ResponseRecordTreeConnectAndX {
|
|
service,
|
|
nativefs
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecordTreeConnectAndX<'a> {
|
|
pub path: Vec<u8>,
|
|
pub service: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_connect_tree_andx_record<'a>(i: &'a[u8], r: &SmbRecord)
|
|
-> IResult<&'a[u8], SmbRecordTreeConnectAndX<'a>, SmbError> {
|
|
let (i, _skip1) = take(7_usize)(i)?;
|
|
let (i, pwlen) = le_u16(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let (i, _pw) = take(pwlen)(i)?;
|
|
let (i, path) = smb1_get_string(i, r, 11 + pwlen as usize)?;
|
|
let (i, service) = take_until_and_consume(b"\x00")(i)?;
|
|
let record = SmbRecordTreeConnectAndX {
|
|
path,
|
|
service
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecordTransRequest<'a> {
|
|
pub params: SmbRecordTransRequestParams,
|
|
pub pipe: Option<SmbPipeProtocolRecord<'a>>,
|
|
pub txname: Vec<u8>,
|
|
pub data: SmbRecordTransRequestData<'a>,
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbPipeProtocolRecord<'a> {
|
|
pub function: u16,
|
|
pub fid: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_trans_request_record_pipe(i: &[u8])
|
|
-> IResult<&[u8], SmbPipeProtocolRecord, SmbError> {
|
|
let (i, fun) = le_u16(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let record = SmbPipeProtocolRecord {
|
|
function: fun,
|
|
fid
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecordTransRequestParams<> {
|
|
pub max_data_cnt: u16,
|
|
param_cnt: u16,
|
|
param_offset: u16,
|
|
data_cnt: u16,
|
|
data_offset: u16,
|
|
bcc: u16,
|
|
}
|
|
|
|
pub fn parse_smb_trans_request_record_params(i: &[u8])
|
|
-> IResult<&[u8], (SmbRecordTransRequestParams, Option<SmbPipeProtocolRecord>), SmbError>
|
|
{
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _total_param_cnt) = le_u16(i)?;
|
|
let (i, _total_data_count) = le_u16(i)?;
|
|
let (i, _max_param_cnt) = le_u16(i)?;
|
|
let (i, max_data_cnt) = le_u16(i)?;
|
|
let (i, _max_setup_cnt) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, _) = take(2_usize)(i)?; // flags
|
|
let (i, _timeout) = le_u32(i)?;
|
|
let (i, _) = take(2_usize)(i)?; // reserved
|
|
let (i, param_cnt) = le_u16(i)?;
|
|
let (i, param_offset) = le_u16(i)?;
|
|
let (i, data_cnt) = le_u16(i)?;
|
|
let (i, data_offset) = le_u16(i)?;
|
|
let (i, setup_cnt) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, pipe) = cond(wct == 16 && setup_cnt == 2 && data_cnt > 0, parse_smb_trans_request_record_pipe)(i)?;
|
|
let (i, bcc) = le_u16(i)?;
|
|
let params = SmbRecordTransRequestParams {
|
|
max_data_cnt,
|
|
param_cnt,
|
|
param_offset,
|
|
data_cnt,
|
|
data_offset,
|
|
bcc
|
|
};
|
|
Ok((i, (params, pipe)))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecordTransRequestData<'a> {
|
|
pub data: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_trans_request_record_data(i: &[u8],
|
|
pad1: usize, param_cnt: u16, pad2: usize, data_len: u16)
|
|
-> IResult<&[u8], SmbRecordTransRequestData, SmbError>
|
|
{
|
|
let (i, _) = take(pad1)(i)?;
|
|
let (i, _) = take(param_cnt)(i)?;
|
|
let (i, _) = take(pad2)(i)?;
|
|
let (i, data) = take(data_len)(i)?;
|
|
let req = SmbRecordTransRequestData { data };
|
|
Ok((i, req))
|
|
}
|
|
|
|
pub fn parse_smb_trans_request_record<'a>(i: &'a[u8], r: &SmbRecord)
|
|
-> IResult<&'a[u8], SmbRecordTransRequest<'a>, SmbError>
|
|
{
|
|
let (rem, (params, pipe)) = parse_smb_trans_request_record_params(i)?;
|
|
let mut offset = 32 + (i.len() - rem.len()); // init with SMB header
|
|
SCLogDebug!("params {:?}: offset {}", params, offset);
|
|
|
|
let (rem2, n) = smb1_get_string(rem, r, offset)?;
|
|
offset += rem.len() - rem2.len();
|
|
SCLogDebug!("n {:?}: offset {}", n, offset);
|
|
|
|
// spec says pad to 4 bytes, but traffic shows this doesn't
|
|
// always happen.
|
|
let pad1 = if offset == params.param_offset as usize ||
|
|
offset == params.data_offset as usize {
|
|
0
|
|
} else {
|
|
offset % 4
|
|
};
|
|
SCLogDebug!("pad1 {}", pad1);
|
|
offset += pad1;
|
|
offset += params.param_cnt as usize;
|
|
|
|
let recdata = if params.data_cnt > 0 {
|
|
// ignore padding rule if we're already at the correct
|
|
// offset.
|
|
let pad2 = if offset == params.data_offset as usize {
|
|
0
|
|
} else {
|
|
offset % 4
|
|
};
|
|
SCLogDebug!("pad2 {}", pad2);
|
|
|
|
let d = match parse_smb_trans_request_record_data(rem2,
|
|
pad1, params.param_cnt, pad2, params.data_cnt) {
|
|
Ok((_, rd)) => rd,
|
|
Err(e) => { return Err(e); }
|
|
};
|
|
SCLogDebug!("d {:?}", d);
|
|
d
|
|
} else {
|
|
SmbRecordTransRequestData { data: &[], } // no data
|
|
};
|
|
|
|
let res = SmbRecordTransRequest {
|
|
params, pipe, txname: n, data: recdata,
|
|
};
|
|
Ok((rem, res))
|
|
}
|
|
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecordTransResponse<'a> {
|
|
pub data_cnt: u16,
|
|
pub bcc: u16,
|
|
pub data: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_trans_response_error_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, bcc) = le_u16(i)?;
|
|
let resp = SmbRecordTransResponse {
|
|
data_cnt: 0,
|
|
bcc,
|
|
data: &[],
|
|
};
|
|
Ok((i, resp))
|
|
}
|
|
|
|
pub fn parse_smb_trans_response_regular_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse> {
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _total_param_cnt) = le_u16(i)?;
|
|
let (i, _total_data_count) = le_u16(i)?;
|
|
let (i, _) = take(2_usize)(i)?; // reserved
|
|
let (i, _param_cnt) = le_u16(i)?;
|
|
let (i, _param_offset) = le_u16(i)?;
|
|
let (i, _param_displacement) = le_u16(i)?;
|
|
let (i, data_cnt) = le_u16(i)?;
|
|
let (i, data_offset) = le_u16(i)?;
|
|
let (i, _data_displacement) = le_u16(i)?;
|
|
let (i, _setup_cnt) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, bcc) = le_u16(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // padding
|
|
let (i, _padding_evasion) = cond(
|
|
data_offset > 36+2*(wct as u16),
|
|
|b| take(data_offset - (36+2*(wct as u16)))(b)
|
|
)(i)?;
|
|
let (i, data) = take(data_cnt)(i)?;
|
|
let resp = SmbRecordTransResponse {
|
|
data_cnt,
|
|
bcc,
|
|
data
|
|
};
|
|
Ok((i, resp))
|
|
}
|
|
|
|
pub fn parse_smb_trans_response_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse> {
|
|
let (i, wct) = peek(le_u8)(i)?;
|
|
match wct {
|
|
0 => parse_smb_trans_response_error_record(i),
|
|
_ => parse_smb_trans_response_regular_record(i),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecordSetupAndX<'a> {
|
|
pub sec_blob: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbRecordSetupAndX> {
|
|
let (i, _skip1) = take(15_usize)(i)?;
|
|
let (i, sec_blob_len) = le_u16(i)?;
|
|
let (i, _skip2) = take(8_usize)(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let (i, sec_blob) = take(sec_blob_len)(i)?;
|
|
let record = SmbRecordSetupAndX { sec_blob };
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbResponseRecordSetupAndX<'a> {
|
|
pub sec_blob: &'a[u8],
|
|
}
|
|
|
|
fn response_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> {
|
|
let (i, _skip1) = take(7_usize)(i)?;
|
|
let (i, sec_blob_len) = le_u16(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let (i, sec_blob) = take(sec_blob_len)(i)?;
|
|
let record = SmbResponseRecordSetupAndX { sec_blob };
|
|
Ok((i, record))
|
|
}
|
|
|
|
fn response_setup_andx_wct3_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> {
|
|
let (i, _skip1) = take(7_usize)(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let record = SmbResponseRecordSetupAndX {
|
|
sec_blob: &[],
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
fn response_setup_andx_error_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let record = SmbResponseRecordSetupAndX {
|
|
sec_blob: &[],
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
pub fn parse_smb_response_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> {
|
|
let (i, wct) = peek(le_u8)(i)?;
|
|
match wct {
|
|
0 => response_setup_andx_error_record(i),
|
|
3 => response_setup_andx_wct3_record(i),
|
|
_ => response_setup_andx_record(i),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRequestReadAndXRecord<'a> {
|
|
pub fid: &'a[u8],
|
|
pub size: u64,
|
|
pub offset: u64,
|
|
}
|
|
|
|
pub fn parse_smb_read_andx_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestReadAndXRecord> {
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _andx_command) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, _andx_offset) = le_u16(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let (i, offset) = le_u32(i)?;
|
|
let (i, max_count_low) = le_u16(i)?;
|
|
let (i, _) = take(2_usize)(i)?;
|
|
let (i, max_count_high) = le_u32(i)?;
|
|
let (i, _) = take(2_usize)(i)?;
|
|
let (i, high_offset) = cond(wct == 12,le_u32)(i)?; // only from wct ==12?
|
|
let record = SmbRequestReadAndXRecord {
|
|
fid,
|
|
size: (((max_count_high as u64) << 16)|max_count_low as u64),
|
|
offset: high_offset.map(|ho| (ho as u64) << 32 | offset as u64).unwrap_or(0),
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbResponseReadAndXRecord<'a> {
|
|
pub len: u32,
|
|
pub data: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_read_andx_response_record(i: &[u8]) -> IResult<&[u8], SmbResponseReadAndXRecord> {
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _andx_command) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, _andx_offset) = le_u16(i)?;
|
|
let (i, _) = take(6_usize)(i)?;
|
|
let (i, data_len_low) = le_u16(i)?;
|
|
let (i, data_offset) = le_u16(i)?;
|
|
let (i, data_len_high) = le_u32(i)?;
|
|
let (i, _) = take(6_usize)(i)?; // reserved
|
|
let (i, bcc) = le_u16(i)?;
|
|
let (i, _padding) = cond(
|
|
bcc > data_len_low,
|
|
|b| take(bcc - data_len_low)(b)
|
|
)(i)?; // TODO figure out how this works with data_len_high
|
|
let (i, _padding_evasion) = cond(
|
|
data_offset > 36+2*(wct as u16),
|
|
|b| take(data_offset - (36+2*(wct as u16)))(b)
|
|
)(i)?;
|
|
let (i, file_data) = rest(i)?;
|
|
|
|
let record = SmbResponseReadAndXRecord {
|
|
len: ((data_len_high << 16)|data_len_low as u32),
|
|
data: file_data,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRequestRenameRecord {
|
|
pub oldname: Vec<u8>,
|
|
pub newname: Vec<u8>,
|
|
}
|
|
|
|
pub fn parse_smb_rename_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestRenameRecord, SmbError> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, _search_attr) = le_u16(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let (i, _oldtype) = le_u8(i)?;
|
|
let (i, oldname) = smb_get_unicode_string(i)?;
|
|
let (i, _newtype) = le_u8(i)?;
|
|
let (i, newname) = smb_get_unicode_string_with_offset(i, 1)?; // HACK if we assume oldname is a series of utf16 chars offset would be 1
|
|
let record = SmbRequestRenameRecord {
|
|
oldname,
|
|
newname
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRequestCreateAndXRecord<> {
|
|
pub disposition: u32,
|
|
pub create_options: u32,
|
|
pub file_name: Vec<u8>,
|
|
}
|
|
|
|
pub fn parse_smb_create_andx_request_record<'a>(i: &'a[u8], r: &SmbRecord)
|
|
-> IResult<&'a[u8], SmbRequestCreateAndXRecord<>, SmbError>
|
|
{
|
|
let (i, _skip1) = take(6_usize)(i)?;
|
|
let (i, file_name_len) = le_u16(i)?;
|
|
let (i, _skip3) = take(28_usize)(i)?;
|
|
let (i, disposition) = le_u32(i)?;
|
|
let (i, create_options) = le_u32(i)?;
|
|
let (i, _skip2) = take(5_usize)(i)?;
|
|
let (i, bcc) = le_u16(i)?;
|
|
let (i, file_name) = cond(
|
|
bcc >= file_name_len,
|
|
|b| smb1_get_string(b, r, (bcc - file_name_len) as usize)
|
|
)(i)?;
|
|
let (i, _skip3) = rest(i)?;
|
|
let record = SmbRequestCreateAndXRecord {
|
|
disposition,
|
|
create_options,
|
|
file_name: file_name.unwrap_or_default(),
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Trans2RecordParamSetFileInfoDisposition<> {
|
|
pub delete: bool,
|
|
}
|
|
|
|
pub fn parse_trans2_request_data_set_file_info_disposition(i: &[u8])
|
|
-> IResult<&[u8], Trans2RecordParamSetFileInfoDisposition> {
|
|
let (i, delete) = le_u8(i)?;
|
|
let record = Trans2RecordParamSetFileInfoDisposition {
|
|
delete: delete & 1 == 1,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Trans2RecordParamSetFileInfo<'a> {
|
|
pub fid: &'a[u8],
|
|
pub loi: u16,
|
|
}
|
|
|
|
pub fn parse_trans2_request_params_set_file_info(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetFileInfo> {
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let (i, loi) = le_u16(i)?;
|
|
let record = Trans2RecordParamSetFileInfo { fid, loi };
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Trans2RecordParamSetFileInfoRename<'a> {
|
|
pub replace: bool,
|
|
pub newname: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_trans2_request_data_set_file_info_rename(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetFileInfoRename> {
|
|
let (i, replace) = le_u8(i)?;
|
|
let (i, _reserved) = take(3_usize)(i)?;
|
|
let (i, _root_dir) = take(4_usize)(i)?;
|
|
let (i, newname_len) = le_u32(i)?;
|
|
let (i, newname) = take(newname_len)(i)?;
|
|
let record = Trans2RecordParamSetFileInfoRename {
|
|
replace: replace==1,
|
|
newname,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Trans2RecordParamSetPathInfo<> {
|
|
pub loi: u16,
|
|
pub oldname: Vec<u8>,
|
|
}
|
|
|
|
pub fn parse_trans2_request_params_set_path_info(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetPathInfo, SmbError> {
|
|
let (i, loi) = le_u16(i)?;
|
|
let (i, _reserved) = take(4_usize)(i)?;
|
|
let (i, oldname) = smb_get_unicode_string(i)?;
|
|
let record = Trans2RecordParamSetPathInfo { loi, oldname };
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct Trans2RecordParamSetPathInfoRename<'a> {
|
|
pub replace: bool,
|
|
pub newname: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_trans2_request_data_set_path_info_rename(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetPathInfoRename> {
|
|
let (i, replace) = le_u8(i)?;
|
|
let (i, _reserved) = take(3_usize)(i)?;
|
|
let (i, _root_dir) = take(4_usize)(i)?;
|
|
let (i, newname_len) = le_u32(i)?;
|
|
let (i, newname) = take(newname_len)(i)?;
|
|
let record = Trans2RecordParamSetPathInfoRename {
|
|
replace: replace==1,
|
|
newname
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRequestTrans2Record<'a> {
|
|
pub subcmd: u16,
|
|
pub setup_blob: &'a[u8],
|
|
pub data_blob: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb_trans2_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestTrans2Record> {
|
|
let (i, _wct) = le_u8(i)?;
|
|
let (i, _total_param_cnt) = le_u16(i)?;
|
|
let (i, _total_data_cnt) = le_u16(i)?;
|
|
let (i, _max_param_cnt) = le_u16(i)?;
|
|
let (i, _max_data_cnt) = le_u16(i)?;
|
|
let (i, _max_setup_cnt) = le_u8(i)?;
|
|
let (i, _reserved1) = take(1_usize)(i)?;
|
|
let (i, _flags) = le_u16(i)?;
|
|
let (i, _timeout) = le_u32(i)?;
|
|
let (i, _reserved2) = take(2_usize)(i)?;
|
|
let (i, param_cnt) = le_u16(i)?;
|
|
let (i, param_offset) = verify(le_u16, |&v| v <= (u16::MAX - param_cnt))(i)?;
|
|
let (i, data_cnt) = le_u16(i)?;
|
|
let (i, data_offset) = le_u16(i)?;
|
|
let (i, _setup_cnt) = le_u8(i)?;
|
|
let (i, _reserved3) = take(1_usize)(i)?;
|
|
let (i, subcmd) = le_u16(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
//TODO test and use param_offset
|
|
let (i, _padding) = take(3_usize)(i)?;
|
|
let (i, setup_blob) = take(param_cnt)(i)?;
|
|
let (i, _padding2) = cond(
|
|
data_offset > param_offset + param_cnt,
|
|
|b| take(data_offset - param_offset - param_cnt)(b)
|
|
)(i)?;
|
|
let (i, data_blob) = take(data_cnt)(i)?;
|
|
|
|
let record = SmbRequestTrans2Record {
|
|
subcmd,
|
|
setup_blob,
|
|
data_blob
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbResponseCreateAndXRecord<'a> {
|
|
pub fid: &'a[u8],
|
|
pub create_ts: SMBFiletime,
|
|
pub last_access_ts: SMBFiletime,
|
|
pub last_write_ts: SMBFiletime,
|
|
pub last_change_ts: SMBFiletime,
|
|
pub file_size: u64,
|
|
}
|
|
|
|
pub fn parse_smb_create_andx_response_record(i: &[u8]) -> IResult<&[u8], SmbResponseCreateAndXRecord> {
|
|
let (i, wct) = le_u8(i)?;
|
|
let (i, _andx_command) = le_u8(i)?;
|
|
let (i, _) = take(1_usize)(i)?; // reserved
|
|
let (i, _andx_offset) = le_u16(i)?;
|
|
let (i, _oplock_level) = le_u8(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let (i, _create_action) = le_u32(i)?;
|
|
let (i, create_ts) = le_u64(i)?;
|
|
let (i, last_access_ts) = le_u64(i)?;
|
|
let (i, last_write_ts) = le_u64(i)?;
|
|
let (i, last_change_ts) = le_u64(i)?;
|
|
let (i, _) = take(4_usize)(i)?;
|
|
let (i, file_size) = le_u64(i)?;
|
|
let (i, _eof) = le_u64(i)?;
|
|
let (i, _file_type) = le_u16(i)?;
|
|
let (i, _ipc_state) = le_u16(i)?;
|
|
let (i, _is_dir) = le_u8(i)?;
|
|
let (i, _) = cond(wct == 42, take(32_usize))(i)?;
|
|
let (i, _bcc) = le_u16(i)?;
|
|
let record = SmbResponseCreateAndXRecord {
|
|
fid,
|
|
create_ts: SMBFiletime::new(create_ts),
|
|
last_access_ts: SMBFiletime::new(last_access_ts),
|
|
last_write_ts: SMBFiletime::new(last_write_ts),
|
|
last_change_ts: SMBFiletime::new(last_change_ts),
|
|
file_size,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRequestCloseRecord<'a> {
|
|
pub fid: &'a[u8],
|
|
}
|
|
|
|
pub fn parse_smb1_close_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestCloseRecord> {
|
|
let (i, _) = take(1_usize)(i)?;
|
|
let (i, fid) = take(2_usize)(i)?;
|
|
let record = SmbRequestCloseRecord {
|
|
fid,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbVersion<> {
|
|
pub version: u8,
|
|
}
|
|
|
|
pub fn parse_smb_version(i: &[u8]) -> IResult<&[u8], SmbVersion> {
|
|
let (i, version) = le_u8(i)?;
|
|
let (i, _) = tag(b"SMB")(i)?;
|
|
let version = SmbVersion { version };
|
|
Ok((i, version))
|
|
}
|
|
|
|
#[derive(Debug,PartialEq, Eq)]
|
|
pub struct SmbRecord<'a> {
|
|
pub command: u8,
|
|
pub is_dos_error: bool,
|
|
pub nt_status: u32,
|
|
pub flags: u8,
|
|
pub flags2: u16,
|
|
|
|
pub tree_id: u16,
|
|
pub user_id: u16,
|
|
pub multiplex_id: u16,
|
|
|
|
pub process_id: u32,
|
|
pub ssn_id: u32,
|
|
|
|
pub data: &'a[u8],
|
|
}
|
|
|
|
impl SmbRecord<'_> {
|
|
pub fn has_unicode_support(&self) -> bool {
|
|
self.flags2 & 0x8000_u16 != 0
|
|
}
|
|
|
|
/// Return true if record is a request.
|
|
pub fn is_request(&self) -> bool {
|
|
self.flags & SMB1_FLAGS_RESPONSE == 0
|
|
}
|
|
|
|
/// Return true if record is a reply.
|
|
pub fn is_response(&self) -> bool {
|
|
self.flags & SMB1_FLAGS_RESPONSE != 0
|
|
}
|
|
}
|
|
|
|
pub fn parse_smb_record(i: &[u8]) -> IResult<&[u8], SmbRecord> {
|
|
let (i, _) = tag(b"\xffSMB")(i)?;
|
|
let (i, command) = le_u8(i)?;
|
|
let (i, nt_status) = le_u32(i)?;
|
|
let (i, flags) = le_u8(i)?;
|
|
let (i, flags2) = le_u16(i)?;
|
|
let (i, process_id_high) = le_u16(i)?;
|
|
let (i, _signature) = take(8_usize)(i)?;
|
|
let (i, _reserved) = take(2_usize)(i)?;
|
|
let (i, tree_id) = le_u16(i)?;
|
|
let (i, process_id) = le_u16(i)?;
|
|
let (i, user_id) = le_u16(i)?;
|
|
let (i, multiplex_id) = le_u16(i)?;
|
|
let (i, data) = rest(i)?;
|
|
|
|
let record = SmbRecord {
|
|
command,
|
|
nt_status,
|
|
flags,
|
|
flags2,
|
|
is_dos_error: (flags2 & 0x4000_u16 == 0),// && nt_status != 0),
|
|
tree_id,
|
|
user_id,
|
|
multiplex_id,
|
|
|
|
process_id: (process_id_high as u32) << 16 | process_id as u32,
|
|
//ssn_id: (((process_id as u32)<< 16)|(user_id as u32)),
|
|
ssn_id: user_id as u32,
|
|
data,
|
|
};
|
|
Ok((i, record))
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_smb1_write_andx_request_record_origin() {
|
|
let data = hex::decode("0eff000000014000000000ff00000008001400000014003f000000000014004142434445464748494a4b4c4d4e4f5051520a0a").unwrap();
|
|
let result = parse_smb1_write_andx_request_record(&data, SMB1_HEADER_SIZE);
|
|
assert!(result.is_ok());
|
|
let record = result.unwrap().1;
|
|
assert_eq!(record.offset, 0);
|
|
assert_eq!(record.len, 20);
|
|
assert_eq!(record.fid, &[0x01, 0x40]);
|
|
assert_eq!(record.data.len(), 20);
|
|
assert_eq!(record.data, b"ABCDEFGHIJKLMNOPQR\n\n");
|
|
}
|