1424 lines
52 KiB
Python
Executable File
1424 lines
52 KiB
Python
Executable File
|
|
import os, sys, struct, types, logging, binascii, time
|
|
from io import StringIO
|
|
from .smb_constants import *
|
|
|
|
|
|
# Set to True if you want to enable support for extended security. Required for Windows Vista and later
|
|
SUPPORT_EXTENDED_SECURITY = True
|
|
|
|
# Set to True if you want to enable SMB2 protocol.
|
|
SUPPORT_SMB2 = True
|
|
|
|
# Supported dialects
|
|
DIALECTS = [ ]
|
|
for i, ( name, dialect ) in enumerate([ ( 'NT_LAN_MANAGER_DIALECT', b'NT LM 0.12' ), ]):
|
|
DIALECTS.append(dialect)
|
|
globals()[name] = i
|
|
|
|
DIALECTS2 = [ ]
|
|
for i, ( name, dialect ) in enumerate([ ( 'SMB2_DIALECT', b'SMB 2.002' ) ]):
|
|
DIALECTS2.append(dialect)
|
|
globals()[name] = i + len(DIALECTS)
|
|
|
|
|
|
class UnsupportedFeature(Exception):
|
|
"""
|
|
Raised when an supported feature is present/required in the protocol but is not
|
|
currently supported by pysmb
|
|
"""
|
|
pass
|
|
|
|
|
|
class ProtocolError(Exception):
|
|
|
|
def __init__(self, message, data_buf = None, smb_message = None):
|
|
self.message = message
|
|
self.data_buf = data_buf
|
|
self.smb_message = smb_message
|
|
|
|
def __str__(self):
|
|
b = StringIO()
|
|
b.write(self.message + os.linesep)
|
|
if self.smb_message:
|
|
b.write('=' * 20 + ' SMB Message ' + '=' * 20 + os.linesep)
|
|
b.write(str(self.smb_message))
|
|
|
|
if self.data_buf:
|
|
b.write('=' * 20 + ' SMB Data Packet (hex) ' + '=' * 20 + os.linesep)
|
|
b.write(str(binascii.hexlify(self.data_buf)))
|
|
b.write(os.linesep)
|
|
|
|
return b.getvalue()
|
|
|
|
class SMB2ProtocolHeaderError(ProtocolError):
|
|
|
|
def __init__(self):
|
|
ProtocolError.__init__(self, "Packet header belongs to SMB2")
|
|
|
|
class OperationFailure(Exception):
|
|
|
|
def __init__(self, message, smb_messages):
|
|
self.message = message
|
|
self.smb_messages = smb_messages
|
|
|
|
def __str__(self):
|
|
b = StringIO()
|
|
b.write(self.message + os.linesep)
|
|
|
|
for idx, m in enumerate(self.smb_messages):
|
|
b.write('=' * 20 + ' SMB Message %d ' % idx + '=' * 20 + os.linesep)
|
|
b.write('SMB Header:' + os.linesep)
|
|
b.write('-----------' + os.linesep)
|
|
b.write(str(m))
|
|
b.write('SMB Data Packet (hex):' + os.linesep)
|
|
b.write('----------------------' + os.linesep)
|
|
b.write(str(binascii.hexlify(m.raw_data)))
|
|
b.write(os.linesep)
|
|
|
|
return b.getvalue()
|
|
|
|
|
|
class SMBError:
|
|
|
|
def __init__(self):
|
|
self.reset()
|
|
|
|
def reset(self):
|
|
self.internal_value = 0
|
|
self.is_ntstatus = True
|
|
|
|
def __str__(self):
|
|
if self.is_ntstatus:
|
|
return 'NTSTATUS=0x%08X' % self.internal_value
|
|
else:
|
|
return 'ErrorClass=0x%02X ErrorCode=0x%04X' % ( self.internal_value >> 24, self.internal_value & 0xFFFF )
|
|
|
|
@property
|
|
def hasError(self):
|
|
return self.internal_value != 0
|
|
|
|
|
|
class SMBMessage:
|
|
|
|
HEADER_STRUCT_FORMAT = "<4sBIBHHQxxHHHHB"
|
|
HEADER_STRUCT_SIZE = struct.calcsize(HEADER_STRUCT_FORMAT)
|
|
|
|
log = logging.getLogger('SMB.SMBMessage')
|
|
protocol = 1
|
|
|
|
def __init__(self, payload = None):
|
|
self.reset()
|
|
if payload:
|
|
self.payload = payload
|
|
self.payload.initMessage(self)
|
|
|
|
def __str__(self):
|
|
b = StringIO()
|
|
b.write('Command: 0x%02X (%s) %s' % ( self.command, SMB_COMMAND_NAMES.get(self.command, '<unknown>'), os.linesep ))
|
|
b.write('Status: %s %s' % ( str(self.status), os.linesep ))
|
|
b.write('Flags: 0x%02X %s' % ( self.flags, os.linesep ))
|
|
b.write('Flags2: 0x%04X %s' % ( self.flags2, os.linesep ))
|
|
b.write('PID: %d %s' % ( self.pid, os.linesep ))
|
|
b.write('UID: %d %s' % ( self.uid, os.linesep ))
|
|
b.write('MID: %d %s' % ( self.mid, os.linesep ))
|
|
b.write('TID: %d %s' % ( self.tid, os.linesep ))
|
|
b.write('Security: 0x%016X %s' % ( self.security, os.linesep ))
|
|
b.write('Parameters: %d bytes %s%s %s' % ( len(self.parameters_data), os.linesep, str(binascii.hexlify(self.parameters_data)), os.linesep ))
|
|
b.write('Data: %d bytes %s%s %s' % ( len(self.data), os.linesep, str(binascii.hexlify(self.data)), os.linesep ))
|
|
return b.getvalue()
|
|
|
|
def reset(self):
|
|
self.raw_data = b''
|
|
self.command = 0
|
|
self.status = SMBError()
|
|
self.flags = 0
|
|
self.flags2 = 0
|
|
self.pid = 0
|
|
self.tid = 0
|
|
self.uid = 0
|
|
self.mid = 0
|
|
self.security = 0
|
|
self.parameters_data = b''
|
|
self.data = b''
|
|
self.payload = None
|
|
|
|
@property
|
|
def isAsync(self):
|
|
return bool(self.flags & SMB2_FLAGS_ASYNC_COMMAND)
|
|
|
|
@property
|
|
def isReply(self):
|
|
return bool(self.flags & SMB_FLAGS_REPLY)
|
|
|
|
@property
|
|
def hasExtendedSecurity(self):
|
|
return bool(self.flags2 & SMB_FLAGS2_EXTENDED_SECURITY)
|
|
|
|
def encode(self):
|
|
"""
|
|
Encode this SMB message into a series of bytes suitable to be embedded with a NetBIOS session message.
|
|
AssertionError will be raised if this SMB message has not been initialized with a Payload instance
|
|
|
|
@return: a string containing the encoded SMB message
|
|
"""
|
|
assert self.payload
|
|
|
|
self.pid = os.getpid()
|
|
self.payload.prepare(self)
|
|
|
|
parameters_len = len(self.parameters_data)
|
|
assert parameters_len % 2 == 0
|
|
|
|
headers_data = struct.pack(self.HEADER_STRUCT_FORMAT,
|
|
b'\xFFSMB', self.command, self.status.internal_value, self.flags,
|
|
self.flags2, (self.pid >> 16) & 0xFFFF, self.security, self.tid,
|
|
self.pid & 0xFFFF, self.uid, self.mid, int(parameters_len / 2))
|
|
return headers_data + self.parameters_data + struct.pack('<H', len(self.data)) + self.data
|
|
|
|
def decode(self, buf):
|
|
"""
|
|
Decodes the SMB message in buf.
|
|
All fields of the SMBMessage object will be reset to default values before decoding.
|
|
On errors, do not assume that the fields will be reinstated back to what they are before
|
|
this method is invoked.
|
|
|
|
@param buf: data containing one complete SMB message
|
|
@type buf: string
|
|
@return: a positive integer indicating the number of bytes used in buf to decode this SMB message
|
|
@raise ProtocolError: raised when decoding fails
|
|
"""
|
|
buf_len = len(buf)
|
|
if buf_len < self.HEADER_STRUCT_SIZE:
|
|
# We need at least 32 bytes (header) + 1 byte (parameter count)
|
|
raise ProtocolError('Not enough data to decode SMB header', buf)
|
|
|
|
self.reset()
|
|
|
|
protocol, self.command, status, self.flags, \
|
|
self.flags2, pid_high, self.security, self.tid, \
|
|
pid_low, self.uid, self.mid, params_count = struct.unpack(self.HEADER_STRUCT_FORMAT, buf[:self.HEADER_STRUCT_SIZE])
|
|
|
|
if protocol == b'\xFESMB':
|
|
raise SMB2ProtocolHeaderError()
|
|
if protocol != b'\xFFSMB':
|
|
raise ProtocolError('Invalid 4-byte protocol field', buf)
|
|
|
|
self.pid = (pid_high << 16) | pid_low
|
|
self.status.internal_value = status
|
|
self.status.is_ntstatus = bool(self.flags2 & SMB_FLAGS2_NT_STATUS)
|
|
|
|
offset = self.HEADER_STRUCT_SIZE
|
|
if buf_len < params_count * 2 + 2:
|
|
# Not enough data in buf to decode up to body length
|
|
raise ProtocolError('Not enough data. Parameters list decoding failed', buf)
|
|
|
|
datalen_offset = offset + params_count*2
|
|
body_len = struct.unpack('<H', buf[datalen_offset:datalen_offset+2])[0]
|
|
if body_len > 0 and buf_len < (datalen_offset + 2 + body_len):
|
|
# Not enough data in buf to decode body
|
|
raise ProtocolError('Not enough data. Body decoding failed', buf)
|
|
|
|
self.parameters_data = buf[offset:datalen_offset]
|
|
|
|
if body_len > 0:
|
|
self.data = buf[datalen_offset+2:datalen_offset+2+body_len]
|
|
|
|
self.raw_data = buf
|
|
self._decodePayload()
|
|
|
|
return self.HEADER_STRUCT_SIZE + params_count * 2 + 2 + body_len
|
|
|
|
def _decodePayload(self):
|
|
if self.command == SMB_COM_READ_ANDX:
|
|
self.payload = ComReadAndxResponse()
|
|
elif self.command == SMB_COM_WRITE_ANDX:
|
|
self.payload = ComWriteAndxResponse()
|
|
elif self.command == SMB_COM_TRANSACTION:
|
|
self.payload = ComTransactionResponse()
|
|
elif self.command == SMB_COM_TRANSACTION2:
|
|
self.payload = ComTransaction2Response()
|
|
elif self.command == SMB_COM_OPEN_ANDX:
|
|
self.payload = ComOpenAndxResponse()
|
|
elif self.command == SMB_COM_NT_CREATE_ANDX:
|
|
self.payload = ComNTCreateAndxResponse()
|
|
elif self.command == SMB_COM_TREE_CONNECT_ANDX:
|
|
self.payload = ComTreeConnectAndxResponse()
|
|
elif self.command == SMB_COM_ECHO:
|
|
self.payload = ComEchoResponse()
|
|
elif self.command == SMB_COM_SESSION_SETUP_ANDX:
|
|
self.payload = ComSessionSetupAndxResponse()
|
|
elif self.command == SMB_COM_NEGOTIATE:
|
|
self.payload = ComNegotiateResponse()
|
|
|
|
if self.payload:
|
|
self.payload.decode(self)
|
|
|
|
|
|
class Payload:
|
|
|
|
DEFAULT_ANDX_PARAM_HEADER = b'\xFF\x00\x00\x00'
|
|
DEFAULT_ANDX_PARAM_SIZE = 4
|
|
|
|
def initMessage(self, message):
|
|
# SMB_FLAGS2_UNICODE must always be enabled. Without this, almost all the Payload subclasses will need to be
|
|
# rewritten to check for OEM/Unicode strings which will be tedious. Fortunately, almost all tested CIFS services
|
|
# support SMB_FLAGS2_UNICODE by default.
|
|
assert message.payload == self
|
|
message.flags = SMB_FLAGS_CASE_INSENSITIVE | SMB_FLAGS_CANONICALIZED_PATHS
|
|
message.flags2 = SMB_FLAGS2_UNICODE | SMB_FLAGS2_NT_STATUS | SMB_FLAGS2_IS_LONG_NAME | SMB_FLAGS2_LONG_NAMES
|
|
|
|
if SUPPORT_EXTENDED_SECURITY:
|
|
message.flags2 |= SMB_FLAGS2_EXTENDED_SECURITY
|
|
|
|
def prepare(self, message):
|
|
raise NotImplementedError
|
|
|
|
def decode(self, message):
|
|
raise NotImplementedError
|
|
|
|
|
|
class ComNegotiateRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.52.1
|
|
- [MS-SMB]: 2.2.4.5.1
|
|
"""
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_NEGOTIATE
|
|
|
|
def prepare(self, message):
|
|
assert message.payload == self
|
|
message.parameters_data = b''
|
|
if SUPPORT_SMB2:
|
|
message.data = b''.join([b'\x02'+s+b'\x00' for s in DIALECTS + DIALECTS2])
|
|
else:
|
|
message.data = b''.join([b'\x02'+s+b'\x00' for s in DIALECTS])
|
|
|
|
|
|
class ComNegotiateResponse(Payload):
|
|
"""
|
|
Contains information on the SMB_COM_NEGOTIATE response from server
|
|
|
|
After calling the decode method, each instance will contain the following attributes,
|
|
- security_mode (integer)
|
|
- max_mpx_count (integer)
|
|
- max_number_vcs (integer)
|
|
- max_buffer_size (long)
|
|
- max_raw_size (long)
|
|
- session_key (long)
|
|
- capabilities (long)
|
|
- system_time (long)
|
|
- server_time_zone (integer)
|
|
- challenge_length (integer)
|
|
|
|
If the underlying SMB message's flag2 does not have SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
|
|
then the instance will have the following additional attributes,
|
|
- challenge (string)
|
|
- domain (unicode)
|
|
|
|
If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
|
|
then the instance will have the following additional attributes,
|
|
- server_guid (string)
|
|
- security_blob (string)
|
|
|
|
References:
|
|
===========
|
|
- [MS-SMB]: 2.2.4.5.2.1
|
|
- [MS-CIFS]: 2.2.4.52.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HBHHIIIIQHB'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_NEGOTIATE
|
|
|
|
if not message.isReply:
|
|
raise ProtocolError('Not a SMB_COM_NEGOTIATE reply', message.raw_data, message)
|
|
|
|
self.security_mode, self.max_mpx_count, self.max_number_vcs, self.max_buffer_size, \
|
|
self.max_raw_size, self.session_key, self.capabilities, self.system_time, self.server_time_zone, \
|
|
self.challenge_length = ( 0, ) * 10
|
|
|
|
data_len = len(message.parameters_data)
|
|
if data_len < 2:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE dialect_index field', message.raw_data, message)
|
|
|
|
self.dialect_index = struct.unpack('<H', message.parameters_data[:2])[0]
|
|
if self.dialect_index == NT_LAN_MANAGER_DIALECT:
|
|
if data_len != (0x11 * 2):
|
|
raise ProtocolError('NT LAN Manager dialect selected in SMB_COM_NEGOTIATE but parameters bytes count (%d) does not meet specs' % data_len,
|
|
message.raw_data, message)
|
|
else:
|
|
_, self.security_mode, self.max_mpx_count, self.max_number_vcs, self.max_buffer_size, \
|
|
self.max_raw_size, self.session_key, self.capabilities, self.system_time, self.server_time_zone, \
|
|
self.challenge_length = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
elif self.dialect_index == 0xFFFF:
|
|
raise ProtocolError('Server does not support any of the pysmb dialects. Please email pysmb to add in support for your OS',
|
|
message.raw_data, message)
|
|
else:
|
|
raise ProtocolError('Unknown dialect index (0x%04X)' % self.dialect_index, message.raw_data, message)
|
|
|
|
data_len = len(message.data)
|
|
if not message.hasExtendedSecurity:
|
|
self.challenge, self.domain = '', ''
|
|
if self.challenge_length > 0:
|
|
if data_len >= self.challenge_length:
|
|
self.challenge = message.data[:self.challenge_length]
|
|
|
|
s = b''
|
|
offset = self.challenge_length
|
|
while offset < data_len:
|
|
_s = message.data[offset:offset+2]
|
|
if _s == b'\0\0':
|
|
self.domain = s.decode('UTF-16LE')
|
|
break
|
|
else:
|
|
s += _s
|
|
offset += 2
|
|
else:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE (without security extensions) Challenge field', message.raw_data, message)
|
|
else:
|
|
if data_len < 16:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_NEGOTIATE (with security extensions) ServerGUID field', message.raw_data, message)
|
|
|
|
self.server_guid = message.data[:16]
|
|
self.security_blob = message.data[16:]
|
|
|
|
@property
|
|
def supportsExtendedSecurity(self):
|
|
return bool(self.capabilities & CAP_EXTENDED_SECURITY)
|
|
|
|
|
|
class ComSessionSetupAndxRequest__WithSecurityExtension(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-SMB]: 2.2.4.6.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HHHIHII'
|
|
|
|
def __init__(self, session_key, security_blob):
|
|
self.session_key = session_key
|
|
self.security_blob = security_blob
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_SESSION_SETUP_ANDX
|
|
|
|
def prepare(self, message):
|
|
assert message.hasExtendedSecurity
|
|
|
|
message.flags2 |= SMB_FLAGS2_UNICODE
|
|
|
|
cap = CAP_UNICODE | CAP_STATUS32 | CAP_EXTENDED_SECURITY | CAP_NT_SMBS
|
|
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
16644, 10, 1, self.session_key, len(self.security_blob), 0, cap)
|
|
|
|
message.data = self.security_blob
|
|
if (SMBMessage.HEADER_STRUCT_SIZE + len(message.parameters_data) + len(message.data)) % 2 != 0:
|
|
message.data = message.data + b'\0'
|
|
message.data = message.data + b'\0' * 4
|
|
|
|
|
|
class ComSessionSetupAndxRequest__NoSecurityExtension(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.53.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HHHIHHII'
|
|
|
|
def __init__(self, session_key, username, password, is_unicode, domain):
|
|
self.username = username
|
|
self.session_key = session_key
|
|
self.password = password
|
|
self.is_unicode = is_unicode
|
|
self.domain = domain
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_SESSION_SETUP_ANDX
|
|
|
|
def prepare(self, message):
|
|
if self.is_unicode:
|
|
message.flags2 |= SMB_FLAGS2_UNICODE
|
|
else:
|
|
message.flags2 &= (~SMB_FLAGS2_UNICODE & 0xFFFF)
|
|
|
|
password_len = len(self.password)
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
16644, 10, 0, self.session_key,
|
|
(not self.is_unicode and password_len) or 0,
|
|
(self.is_unicode and password_len) or 0,
|
|
0,
|
|
CAP_UNICODE | CAP_LARGE_FILES | CAP_STATUS32)
|
|
|
|
est_offset = SMBMessage.HEADER_STRUCT_SIZE + len(message.parameters_data) # To check if data until SMB paramaters are aligned to a 16-bit boundary
|
|
|
|
message.data = self.password
|
|
if (est_offset + len(message.data)) % 2 != 0 and message.flags2 & SMB_FLAGS2_UNICODE:
|
|
message.data = message.data + b'\0'
|
|
|
|
if message.flags2 & SMB_FLAGS2_UNICODE:
|
|
message.data = message.data + self.username.encode('UTF-16LE') + b'\0'
|
|
else:
|
|
message.data = message.data + str(self.username).encode('UTF-8') + b'\0'
|
|
|
|
if (est_offset + len(message.data)) % 2 != 0 and message.flags2 & SMB_FLAGS2_UNICODE:
|
|
message.data = message.data + b'\0'
|
|
|
|
if message.flags2 & SMB_FLAGS2_UNICODE:
|
|
message.data = message.data + self.domain.encode('UTF-16LE') + b'\0\0' + 'pysmb'.encode('UTF-16LE') + b'\0\0'
|
|
else:
|
|
message.data = message.data + self.domain.encode('UTF-8') + b'\0pysmb\0'
|
|
|
|
|
|
class ComSessionSetupAndxResponse(Payload):
|
|
"""
|
|
Contains information on the SMB_COM_SESSION_SETUP_ANDX response from server
|
|
|
|
If the underlying SMB message's flags2 does not have SMB_FLAGS2_EXTENDED_SECURITY bit enabled,
|
|
then the instance will have the following attributes,
|
|
- action
|
|
|
|
If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled
|
|
and the message status is STATUS_MORE_PROCESSING_REQUIRED or equals to 0x00 (no error),
|
|
then the instance will have the following attributes,
|
|
- action
|
|
- securityblob
|
|
|
|
If the underlying SMB message's flags2 has SMB_FLAGS2_EXTENDED_SECURITY bit enabled but
|
|
the message status is not STATUS_MORE_PROCESSING_REQUIRED
|
|
|
|
References:
|
|
===========
|
|
- [MS-SMB]: 2.2.4.6.2
|
|
- [MS-CIFS]: 2.2.4.53.2
|
|
"""
|
|
|
|
NOSECURE_PARAMETER_STRUCT_FORMAT = '<BBHH'
|
|
NOSECURE_PARAMETER_STRUCT_SIZE = struct.calcsize(NOSECURE_PARAMETER_STRUCT_FORMAT)
|
|
|
|
SECURE_PARAMETER_STRUCT_FORMAT = '<BBHHH'
|
|
SECURE_PARAMETER_STRUCT_SIZE = struct.calcsize(SECURE_PARAMETER_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_SESSION_SETUP_ANDX
|
|
if not message.hasExtendedSecurity:
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.NOSECURE_PARAMETER_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (no security extensions) parameters', message.raw_data, message)
|
|
|
|
_, _, _, self.action = struct.unpack(self.NOSECURE_PARAMETER_STRUCT_FORMAT, message.parameters_data[:self.NOSECURE_PARAMETER_STRUCT_SIZE])
|
|
else:
|
|
if not message.status.hasError or message.status.internal_value == 0xc0000016: # STATUS_MORE_PROCESSING_REQUIRED
|
|
if len(message.parameters_data) < self.SECURE_PARAMETER_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (with security extensions) parameters', message.raw_data, message)
|
|
|
|
_, _, _, self.action, blob_length = struct.unpack(self.SECURE_PARAMETER_STRUCT_FORMAT, message.parameters_data[:self.SECURE_PARAMETER_STRUCT_SIZE])
|
|
if len(message.data) < blob_length:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_SESSION_SETUP_ANDX (with security extensions) security blob', message.raw_data, message)
|
|
|
|
self.security_blob = message.data[:blob_length]
|
|
|
|
|
|
class ComTreeConnectAndxRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.55.1
|
|
- [MS-SMB]: 2.2.4.7.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, path, service, password = ''):
|
|
self.path = path
|
|
self.service = service
|
|
self.password = password + '\0'
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_TREE_CONNECT_ANDX
|
|
|
|
def prepare(self, message):
|
|
password_len = len(self.password)
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
0x08 | \
|
|
((message.hasExtendedSecurity and 0x0004) or 0x00) | \
|
|
((message.tid and message.tid != 0xffff and 0x0001) or 0x00), # Disconnect tid, if message.tid must be non-zero
|
|
password_len)
|
|
|
|
padding = b''
|
|
if password_len % 2 == 0:
|
|
padding = b'\0'
|
|
|
|
# Note that service field is never encoded in UTF-16LE. [MS-CIFS]: 2.2.1.1
|
|
message.data = self.password.encode('UTF-8') + padding + self.path.encode('UTF-16LE') + b'\0\0' + self.service.encode('UTF-8') + b'\0'
|
|
|
|
|
|
class ComTreeConnectAndxResponse(Payload):
|
|
"""
|
|
Contains information about the SMB_COM_TREE_CONNECT_ANDX response from the server.
|
|
|
|
If the message has no errors, each instance contains the following attributes:
|
|
- optional_support
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.55.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<BBHH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_TREE_CONNECT_ANDX
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_TREE_CONNECT_ANDX parameters', message.raw_data, message)
|
|
|
|
_, _, _, self.optional_support = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
|
|
class ComNTCreateAndxRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.64.1
|
|
- [MS-SMB]: 2.2.4.9.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<BHIIIQIIIIIB'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, filename, flags = 0, root_fid = 0, access_mask = 0, allocation_size = 0, ext_attr = 0,
|
|
share_access = 0, create_disp = 0, create_options = 0, impersonation = 0, security_flags = 0):
|
|
self.filename = (filename + '\0').encode('UTF-16LE')
|
|
self.flags = flags
|
|
self.root_fid = root_fid
|
|
self.access_mask = access_mask
|
|
self.allocation_size = allocation_size
|
|
self.ext_attr = ext_attr
|
|
self.share_access = share_access
|
|
self.create_disp = create_disp
|
|
self.create_options = create_options
|
|
self.impersonation = impersonation
|
|
self.security_flags = security_flags
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_NT_CREATE_ANDX
|
|
|
|
def prepare(self, message):
|
|
filename_len = len(self.filename)
|
|
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
0x00, # reserved
|
|
filename_len, # NameLength
|
|
self.flags, # Flags
|
|
self.root_fid, # RootDirectoryFID
|
|
self.access_mask, # DesiredAccess
|
|
self.allocation_size, # AllocationSize
|
|
self.ext_attr, # ExtFileAttributes
|
|
self.share_access, # ShareAccess
|
|
self.create_disp, # CreateDisposition
|
|
self.create_options, # CreateOptions
|
|
self.impersonation, # ImpersonationLevel
|
|
self.security_flags) # SecurityFlags
|
|
|
|
padding = b''
|
|
if (message.HEADER_STRUCT_SIZE + len(message.parameters_data)) % 2 != 0:
|
|
padding = b'\0'
|
|
|
|
message.data = padding + self.filename
|
|
|
|
|
|
class ComNTCreateAndxResponse(Payload):
|
|
"""
|
|
Contains (partial) information about the SMB_COM_NT_CREATE_ANDX response from the server.
|
|
|
|
Each instance contains the following attributes after decoding:
|
|
- oplock_level
|
|
- fid
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.64.2
|
|
"""
|
|
PAYLOAD_STRUCT_FORMAT = '<BBHBH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_NT_CREATE_ANDX
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_NT_CREATE_ANDX parameters', message.raw_data, message)
|
|
|
|
_, _, _, self.oplock_level, self.fid = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
|
|
class ComTransactionRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.33.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HHHHBBHIHHHHHH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, max_params_count, max_data_count, max_setup_count,
|
|
total_params_count = 0, total_data_count = 0,
|
|
params_bytes = b'', data_bytes = b'', setup_bytes = b'',
|
|
flags = 0, timeout = 0, name = "\\PIPE\\"):
|
|
self.total_params_count = total_params_count or len(params_bytes)
|
|
self.total_data_count = total_data_count or len(data_bytes)
|
|
self.max_params_count = max_params_count
|
|
self.max_data_count = max_data_count
|
|
self.max_setup_count = max_setup_count
|
|
self.flags = flags
|
|
self.timeout = timeout
|
|
self.params_bytes = params_bytes
|
|
self.data_bytes = data_bytes
|
|
self.setup_bytes = setup_bytes
|
|
self.name = name
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_TRANSACTION
|
|
|
|
def prepare(self, message):
|
|
name = (self.name + '\0').encode('UTF-16LE')
|
|
name_len = len(name)
|
|
setup_bytes_len = len(self.setup_bytes)
|
|
params_bytes_len = len(self.params_bytes)
|
|
data_bytes_len = len(self.data_bytes)
|
|
|
|
padding0 = b''
|
|
offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
if offset % 2 != 0:
|
|
padding0 = b'\0'
|
|
offset += 1
|
|
|
|
offset += name_len # For the name field
|
|
padding1 = b''
|
|
if offset % 4 != 0:
|
|
padding1 = b'\0'*(4-offset%4)
|
|
offset += (4-offset%4)
|
|
|
|
if params_bytes_len > 0:
|
|
params_bytes_offset = offset
|
|
offset += params_bytes_len
|
|
else:
|
|
params_bytes_offset = 0
|
|
|
|
padding2 = b''
|
|
if offset % 4 != 0:
|
|
padding2 = b'\0'*(4-offset%4)
|
|
offset += (4-offset%4)
|
|
|
|
if data_bytes_len > 0:
|
|
data_bytes_offset = offset
|
|
else:
|
|
data_bytes_offset = 0
|
|
|
|
message.parameters_data = \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
self.total_params_count,
|
|
self.total_data_count,
|
|
self.max_params_count,
|
|
self.max_data_count,
|
|
self.max_setup_count,
|
|
0x00, # Reserved1. Must be 0x00
|
|
self.flags,
|
|
self.timeout,
|
|
0x0000, # Reserved2. Must be 0x0000
|
|
params_bytes_len,
|
|
params_bytes_offset,
|
|
data_bytes_len,
|
|
data_bytes_offset,
|
|
int(setup_bytes_len / 2)) + \
|
|
self.setup_bytes
|
|
|
|
message.data = padding0 + name + padding1 + self.params_bytes + padding2 + self.data_bytes
|
|
|
|
|
|
class ComTransactionResponse(Payload):
|
|
"""
|
|
Contains information about a SMB_COM_TRANSACTION response from the server
|
|
|
|
After decoding, each instance contains the following attributes:
|
|
- total_params_count (integer)
|
|
- total_data_count (integer)
|
|
- setup_bytes (string)
|
|
- data_bytes (string)
|
|
- params_bytes (string)
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.33.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HHHHHHHHHH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_TRANSACTION
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)
|
|
|
|
self.total_params_count, self.total_data_count, _, \
|
|
params_bytes_len, params_bytes_offset, params_bytes_displ, \
|
|
data_bytes_len, data_bytes_offset, data_bytes_displ, \
|
|
setup_count = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
if setup_count > 0:
|
|
setup_bytes_len = setup_count * 2
|
|
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE + setup_bytes_len:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)
|
|
|
|
self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_bytes_len]
|
|
else:
|
|
self.setup_bytes = ''
|
|
|
|
offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count * 2 + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
|
|
if params_bytes_len > 0:
|
|
self.params_bytes = message.data[params_bytes_offset-offset:params_bytes_offset-offset+params_bytes_len]
|
|
else:
|
|
self.params_bytes = ''
|
|
|
|
if data_bytes_len > 0:
|
|
self.data_bytes = message.data[data_bytes_offset-offset:data_bytes_offset-offset+data_bytes_len]
|
|
else:
|
|
self.data_bytes = ''
|
|
|
|
|
|
class ComTransaction2Request(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.46.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = 'HHHHBBHIHHHHHH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, max_params_count, max_data_count, max_setup_count,
|
|
total_params_count = 0, total_data_count = 0,
|
|
params_bytes = b'', data_bytes = b'', setup_bytes = b'',
|
|
flags = 0, timeout = 0):
|
|
self.total_params_count = total_params_count or len(params_bytes)
|
|
self.total_data_count = total_data_count or len(data_bytes)
|
|
self.max_params_count = max_params_count
|
|
self.max_data_count = max_data_count
|
|
self.max_setup_count = max_setup_count
|
|
self.flags = flags
|
|
self.timeout = timeout
|
|
self.params_bytes = params_bytes
|
|
self.data_bytes = data_bytes
|
|
self.setup_bytes = setup_bytes
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_TRANSACTION2
|
|
|
|
def prepare(self, message):
|
|
setup_bytes_len = len(self.setup_bytes)
|
|
params_bytes_len = len(self.params_bytes)
|
|
data_bytes_len = len(self.data_bytes)
|
|
name = b'\0\0'
|
|
|
|
padding0 = b''
|
|
offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
if offset % 2 != 0:
|
|
padding0 = b'\0'
|
|
offset += 1
|
|
|
|
offset += 2 # For the name field
|
|
padding1 = b''
|
|
if offset % 4 != 0:
|
|
padding1 = b'\0'*(4-offset%4)
|
|
|
|
if params_bytes_len > 0:
|
|
params_bytes_offset = offset
|
|
offset += params_bytes_len
|
|
else:
|
|
params_bytes_offset = 0
|
|
|
|
padding2 = b''
|
|
if offset % 4 != 0:
|
|
padding2 = b'\0'*(4-offset%4)
|
|
|
|
if data_bytes_len > 0:
|
|
data_bytes_offset = offset
|
|
else:
|
|
data_bytes_offset = 0
|
|
|
|
message.parameters_data = \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
self.total_params_count,
|
|
self.total_data_count,
|
|
self.max_params_count,
|
|
self.max_data_count,
|
|
self.max_setup_count,
|
|
0x00, # Reserved1. Must be 0x00
|
|
self.flags,
|
|
self.timeout,
|
|
0x0000, # Reserved2. Must be 0x0000
|
|
params_bytes_len,
|
|
params_bytes_offset,
|
|
data_bytes_len,
|
|
data_bytes_offset,
|
|
int(setup_bytes_len / 2)) + \
|
|
self.setup_bytes
|
|
|
|
message.data = padding0 + name + padding1 + self.params_bytes + padding2 + self.data_bytes
|
|
|
|
|
|
class ComTransaction2Response(Payload):
|
|
"""
|
|
Contains information about a SMB_COM_TRANSACTION2 response from the server
|
|
|
|
After decoding, each instance contains the following attributes:
|
|
- total_params_count (integer)
|
|
- total_data_count (integer)
|
|
- setup_bytes (string)
|
|
- data_bytes (string)
|
|
- params_bytes (string)
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.46.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HHHHHHHHHBB'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_TRANSACTION2
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION2 parameters', message.raw_data, message)
|
|
|
|
self.total_params_count, self.total_data_count, _, \
|
|
params_bytes_len, params_bytes_offset, params_bytes_displ, \
|
|
data_bytes_len, data_bytes_offset, data_bytes_displ, \
|
|
setup_count, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
if setup_count > 0:
|
|
setup_bytes_len = setup_count * 2
|
|
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE + setup_bytes_len:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_TRANSACTION parameters', message.raw_data, message)
|
|
|
|
self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_bytes_len]
|
|
else:
|
|
self.setup_bytes = ''
|
|
|
|
offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count * 2 + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
|
|
if params_bytes_len > 0:
|
|
self.params_bytes = message.data[params_bytes_offset-offset:params_bytes_offset-offset+params_bytes_len]
|
|
else:
|
|
self.params_bytes = ''
|
|
|
|
if data_bytes_len > 0:
|
|
self.data_bytes = message.data[data_bytes_offset-offset:data_bytes_offset-offset+data_bytes_len]
|
|
else:
|
|
self.data_bytes = ''
|
|
|
|
|
|
class ComCloseRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.5.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HI'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, fid, last_modified_time = 0xFFFFFFFF):
|
|
self.fid = fid
|
|
self.last_modified_time = last_modified_time
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_CLOSE
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = struct.pack(self.PAYLOAD_STRUCT_FORMAT, self.fid, self.last_modified_time)
|
|
message.data = b''
|
|
|
|
|
|
class ComOpenAndxRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.41.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HHHHIHIII'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, filename, access_mode, open_mode, flags = 0x0000, search_attributes = 0, file_attributes = 0, create_time = 0, timeout = 0):
|
|
"""
|
|
@param create_time: Epoch time value to indicate the time of creation for this file. If zero, we will automatically assign the current time
|
|
@type create_time: int
|
|
@param timeout: Number of milliseconds to wait for blocked open request before failing
|
|
@type timeout: int
|
|
"""
|
|
self.filename = filename
|
|
self.access_mode = access_mode
|
|
self.open_mode = open_mode
|
|
self.flags = flags
|
|
self.search_attributes = search_attributes
|
|
self.file_attributes = file_attributes
|
|
self.create_time = create_time or int(time.time())
|
|
self.timeout = timeout
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_OPEN_ANDX
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
self.flags,
|
|
self.access_mode,
|
|
self.search_attributes,
|
|
self.file_attributes,
|
|
self.create_time,
|
|
self.open_mode,
|
|
0, # AllocationSize
|
|
0, # Timeout (in milli-secs)
|
|
0) # Reserved
|
|
|
|
message.data = b'\0' + self.filename.encode('UTF-16LE') + b'\0\0'
|
|
|
|
|
|
class ComOpenAndxResponse(Payload):
|
|
"""
|
|
Contains information about a SMB_COM_OPEN_ANDX response from the server
|
|
|
|
After decoding, each instance will contain the following attributes:
|
|
- fid (integer)
|
|
- file_attributes (integer)
|
|
- last_write_time (long)
|
|
- access_rights (integer)
|
|
- resource_type (integer)
|
|
- open_results (integer)
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.41.2
|
|
- [MS-SMB]: 2.2.4.1.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<BBHHHIIHHHHHHH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_OPEN_ANDX
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_OPEN_ANDX parameters', message.raw_data, message)
|
|
|
|
_, _, _, self.fid, self.file_attributes, self.last_write_time, _, \
|
|
self.access_rights, self.resource_type, _, self.open_results, _, _, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
|
|
message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
|
|
class ComWriteAndxRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.43.1
|
|
- [MS-SMB]: 2.2.4.3.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HIIHHHHHI'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, fid, data_bytes, offset, write_mode = 0, timeout = 0):
|
|
"""
|
|
@param timeout: Number of milliseconds to wait for blocked write request before failing. Must be zero for writing to regular file
|
|
@type timeout: int
|
|
"""
|
|
self.fid = fid
|
|
self.offset = offset
|
|
self.data_bytes = data_bytes
|
|
self.timeout = timeout
|
|
self.write_mode = write_mode
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_WRITE_ANDX
|
|
|
|
def prepare(self, message):
|
|
# constant 1 is to account for the pad byte in the message.data
|
|
# constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
data_offset = message.HEADER_STRUCT_SIZE + self.DEFAULT_ANDX_PARAM_SIZE + self.PAYLOAD_STRUCT_SIZE + 1 + 2
|
|
data_len = len(self.data_bytes)
|
|
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
self.fid,
|
|
self.offset & 0xFFFFFFFF,
|
|
self.timeout,
|
|
self.write_mode,
|
|
data_len, # Remaining
|
|
0x0000, # Reserved
|
|
len(self.data_bytes), # DataLength
|
|
data_offset, # DataOffset
|
|
self.offset >> 32) # OffsetHigh field defined in [MS-SMB]: 2.2.4.3.1
|
|
|
|
message.data = b'\0' + self.data_bytes
|
|
|
|
|
|
class ComWriteAndxResponse(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.43.2
|
|
- [MS-SMB]: 2.2.4.3.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<BBHHHHH' # We follow the SMB_COM_WRITEX_ANDX server extensions in [MS-SMB]: 2.2.4.3.2
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_WRITE_ANDX
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_WRITE_ANDX parameters', message.raw_data, message)
|
|
|
|
_, _, _, count, self.available, high_count, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT, message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
self.count = (count & 0xFFFF) | (high_count << 16)
|
|
|
|
|
|
class ComReadAndxRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.42.1
|
|
- [MS-SMB]: 2.2.4.2.1
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<HIHHIHI'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, fid, offset, max_return_bytes_count, min_return_bytes_count, timeout = 0, remaining = 0):
|
|
"""
|
|
@param timeout: If reading from a regular file, this parameter must be 0.
|
|
@type timeout: int
|
|
"""
|
|
self.fid = fid
|
|
self.remaining = remaining
|
|
self.max_return_bytes_count = max_return_bytes_count
|
|
self.min_return_bytes_count = min_return_bytes_count
|
|
self.offset = offset
|
|
self.timeout = timeout
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_READ_ANDX
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = \
|
|
self.DEFAULT_ANDX_PARAM_HEADER + \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
self.fid,
|
|
self.offset & 0xFFFFFFFF,
|
|
self.max_return_bytes_count,
|
|
self.min_return_bytes_count,
|
|
self.timeout or (self.max_return_bytes_count >> 32), # Note that in [MS-SMB]: 2.2.4.2.1, this field can also act as MaxCountHigh field
|
|
self.remaining, # In [MS-CIFS]: 2.2.4.42.1, this field must be set to 0x0000
|
|
self.offset >> 32)
|
|
|
|
message.data = b''
|
|
|
|
|
|
class ComReadAndxResponse(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.42.2
|
|
- [MS-SMB]: 2.2.4.2.2
|
|
"""
|
|
|
|
PAYLOAD_STRUCT_FORMAT = '<BBHHHHHHHHHHH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_READ_ANDX
|
|
|
|
if not message.status.hasError:
|
|
if len(message.parameters_data) < self.PAYLOAD_STRUCT_SIZE:
|
|
raise ProtocolError('Not enough data to decode SMB_COM_READ_ANDX parameters', message.raw_data, message)
|
|
|
|
_, _, _, _, _, _, self.data_length, data_offset, _, _, _, _, _ = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
|
|
message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
offset = data_offset - message.HEADER_STRUCT_SIZE - self.PAYLOAD_STRUCT_SIZE - 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
self.data = message.data[offset:offset+self.data_length]
|
|
assert len(self.data) == self.data_length
|
|
|
|
|
|
class ComDeleteRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.7.1
|
|
"""
|
|
|
|
def __init__(self, filename_pattern, search_attributes = 0):
|
|
self.filename_pattern = filename_pattern
|
|
self.search_attributes = search_attributes
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_DELETE
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = struct.pack('<H', self.search_attributes)
|
|
message.data = b'\x04' + self.filename_pattern.encode('UTF-16LE') + b'\0\0'
|
|
|
|
|
|
class ComCreateDirectoryRequest(Payload):
|
|
"""
|
|
Although this command has been marked deprecated in [MS-CIFS], we continue to use it for its simplicity
|
|
as compared to its replacement TRANS2_CREATE_DIRECTORY sub-command [MS-CIFS]: 2.2.6.14
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.1.1
|
|
"""
|
|
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_CREATE_DIRECTORY
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = b''
|
|
message.data = b'\x04' + self.path.encode('UTF-16LE') + b'\0\0'
|
|
|
|
|
|
class ComDeleteDirectoryRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.2.1
|
|
"""
|
|
|
|
def __init__(self, path):
|
|
self.path = path
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_DELETE_DIRECTORY
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = b''
|
|
message.data = b'\x04' + self.path.encode('UTF-16LE') + b'\0\0'
|
|
|
|
|
|
class ComRenameRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.8.1
|
|
"""
|
|
|
|
def __init__(self, old_path, new_path, search_attributes = 0):
|
|
self.old_path = old_path
|
|
self.new_path = new_path
|
|
self.search_attributes = search_attributes
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_RENAME
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = struct.pack('<H', self.search_attributes)
|
|
message.data = b'\x04' + self.old_path.encode('UTF-16LE') + b'\x00\x00\x04\x00' + self.new_path.encode('UTF-16LE') + b'\x00\x00'
|
|
|
|
|
|
class ComEchoRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.39.1
|
|
"""
|
|
|
|
def __init__(self, echo_data = b'', echo_count = 1):
|
|
self.echo_count = echo_count
|
|
self.echo_data = echo_data
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_ECHO
|
|
message.tid = 0xFFFF
|
|
|
|
def prepare(self, message):
|
|
message.parameters_data = struct.pack('<H', self.echo_count)
|
|
message.data = self.echo_data
|
|
|
|
|
|
class ComEchoResponse(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.39.2
|
|
"""
|
|
|
|
def decode(self, message):
|
|
self.sequence_number = struct.unpack('<H', message.parameters_data[:2])[0]
|
|
self.data = message.data
|
|
|
|
|
|
class ComNTTransactRequest(Payload):
|
|
"""
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.62.1
|
|
"""
|
|
PAYLOAD_STRUCT_FORMAT = '<BHIIIIIIIIBH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def __init__(self, function, max_params_count, max_data_count, max_setup_count,
|
|
total_params_count = 0, total_data_count = 0,
|
|
params_bytes = b'', setup_bytes = b'', data_bytes = b''):
|
|
self.function = function
|
|
self.total_params_count = total_params_count or len(params_bytes)
|
|
self.total_data_count = total_data_count or len(data_bytes)
|
|
self.max_params_count = max_params_count
|
|
self.max_data_count = max_data_count
|
|
self.max_setup_count = max_setup_count
|
|
self.params_bytes = params_bytes
|
|
self.setup_bytes = setup_bytes
|
|
self.data_bytes = data_bytes
|
|
|
|
def initMessage(self, message):
|
|
Payload.initMessage(self, message)
|
|
message.command = SMB_COM_NT_TRANSACT
|
|
|
|
def prepare(self, message):
|
|
setup_bytes_len = len(self.setup_bytes)
|
|
params_bytes_len = len(self.params_bytes)
|
|
data_bytes_len = len(self.data_bytes)
|
|
|
|
padding0 = b''
|
|
offset = message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_bytes_len + 2 # constant 2 is for the ByteCount field in the SMB header (i.e. field which indicates number of data bytes after the SMB parameters)
|
|
if offset % 4 != 0:
|
|
padding0 = b'\0'*(4-offset%4)
|
|
offset += (4-offset%4)
|
|
|
|
if params_bytes_len > 0:
|
|
params_bytes_offset = offset
|
|
else:
|
|
params_bytes_offset = 0
|
|
|
|
offset += params_bytes_len
|
|
padding1 = b''
|
|
if offset % 4 != 0:
|
|
padding1 = b'\0'*(4-offset%4)
|
|
offset += (4-offset%4)
|
|
|
|
if data_bytes_len > 0:
|
|
data_bytes_offset = offset
|
|
else:
|
|
data_bytes_offset = 0
|
|
|
|
message.parameters_data = \
|
|
struct.pack(self.PAYLOAD_STRUCT_FORMAT,
|
|
self.max_setup_count,
|
|
0x00, # Reserved1. Must be 0x00
|
|
self.total_params_count,
|
|
self.total_data_count,
|
|
self.max_params_count,
|
|
self.max_data_count,
|
|
params_bytes_len,
|
|
params_bytes_offset,
|
|
data_bytes_len,
|
|
data_bytes_offset,
|
|
int(setup_bytes_len / 2),
|
|
self.function) + \
|
|
self.setup_bytes
|
|
|
|
message.data = padding0 + self.params_bytes + padding1 + self.data_bytes
|
|
|
|
|
|
class ComNTTransactResponse(Payload):
|
|
"""
|
|
Contains information about a SMB_COM_NT_TRANSACT response from the server
|
|
|
|
After decoding, each instance contains the following attributes:
|
|
- total_params_count (integer)
|
|
- total_data_count (integer)
|
|
- setup_bytes (string)
|
|
- data_bytes (string)
|
|
- params_bytes (string)
|
|
|
|
References:
|
|
===========
|
|
- [MS-CIFS]: 2.2.4.62.2
|
|
"""
|
|
PAYLOAD_STRUCT_FORMAT = '<3sIIIIIIIIBH'
|
|
PAYLOAD_STRUCT_SIZE = struct.calcsize(PAYLOAD_STRUCT_FORMAT)
|
|
|
|
def decode(self, message):
|
|
assert message.command == SMB_COM_NT_TRANSACT
|
|
|
|
if not message.status.hasError:
|
|
_, self.total_params_count, self.total_data_count, \
|
|
params_count, params_offset, params_displ, \
|
|
data_count, data_offset, data_displ, setup_count = struct.unpack(self.PAYLOAD_STRUCT_FORMAT,
|
|
message.parameters_data[:self.PAYLOAD_STRUCT_SIZE])
|
|
|
|
self.setup_bytes = message.parameters_data[self.PAYLOAD_STRUCT_SIZE:self.PAYLOAD_STRUCT_SIZE+setup_count*2]
|
|
|
|
if params_count > 0:
|
|
params_offset -= message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count*2 + 2
|
|
self.params_bytes = message.data[params_offset:params_offset+params_count]
|
|
else:
|
|
self.params_bytes = b''
|
|
|
|
if data_count > 0:
|
|
data_offset -= message.HEADER_STRUCT_SIZE + self.PAYLOAD_STRUCT_SIZE + setup_count*2 + 2
|
|
self.data_bytes = message.data[data_offset:data_offset+data_count]
|
|
else:
|
|
self.data_bytes = b''
|