Source code for smb.smb_structs


import os, sys, struct, types, logging, binascii, time
from copy import copy
from io import StringIO
from .smb_constants import *
from .strategy import DataFaultToleranceStrategy

# 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)


[docs] class UnsupportedFeature(Exception): """ Raised when an supported feature is present/required in the protocol but is not currently supported by pysmb """ pass
[docs] 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")
[docs] class OperationFailure(Exception): def __init__(self, message, smb_messages): self.message = message self.smb_messages = [copy(msg) for msg in smb_messages] self.args = (message, self.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 = DataFaultToleranceStrategy.data_bytes_decode(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''