# SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Philippe Biondi # Copyright (C) Gabriel Potter """ SMB 1.0 (Server Message Block), also known as CIFS. .. note:: You will find more complete documentation for this layer over at `SMB `_ Specs: - [MS-CIFS] (base) - [MS-SMB] (extension of CIFS - SMB v1) """ import struct from scapy.config import conf from scapy.packet import Packet, bind_layers, bind_top_down from scapy.fields import ( ByteEnumField, ByteField, ConditionalField, FieldLenField, FieldListField, FlagsField, IPField, LEFieldLenField, LEIntEnumField, LEIntField, LELongField, LEShortEnumField, LEShortField, MultipleTypeField, PacketField, PacketLenField, PacketListField, ReversePadField, ScalingField, ShortField, StrFixedLenField, StrNullField, StrNullFieldUtf16, UTCTimeField, UUIDField, XLEShortField, XStrLenField, ) from scapy.layers.dns import ( DNSStrField, DNSCompressedPacket, ) from scapy.layers.ntlm import ( _NTLMPayloadPacket, _NTLMPayloadField, _NTLM_ENUM, _NTLM_post_build, ) from scapy.layers.netbios import NBTSession, NBTDatagram from scapy.layers.gssapi import ( GSSAPI_BLOB, ) from scapy.layers.smb2 import ( STATUS_ERREF, SMB2_Compression_Transform_Header, SMB2_Header, SMB2_Transform_Header, ) SMB_COM = { 0x00: "SMB_COM_CREATE_DIRECTORY", 0x01: "SMB_COM_DELETE_DIRECTORY", 0x02: "SMB_COM_OPEN", 0x03: "SMB_COM_CREATE", 0x04: "SMB_COM_CLOSE", 0x05: "SMB_COM_FLUSH", 0x06: "SMB_COM_DELETE", 0x07: "SMB_COM_RENAME", 0x08: "SMB_COM_QUERY_INFORMATION", 0x09: "SMB_COM_SET_INFORMATION", 0x0A: "SMB_COM_READ", 0x0B: "SMB_COM_WRITE", 0x0C: "SMB_COM_LOCK_BYTE_RANGE", 0x0D: "SMB_COM_UNLOCK_BYTE_RANGE", 0x0E: "SMB_COM_CREATE_TEMPORARY", 0x0F: "SMB_COM_CREATE_NEW", 0x10: "SMB_COM_CHECK_DIRECTORY", 0x11: "SMB_COM_PROCESS_EXIT", 0x12: "SMB_COM_SEEK", 0x13: "SMB_COM_LOCK_AND_READ", 0x14: "SMB_COM_WRITE_AND_UNLOCK", 0x1A: "SMB_COM_READ_RAW", 0x1B: "SMB_COM_READ_MPX", 0x1C: "SMB_COM_READ_MPX_SECONDARY", 0x1D: "SMB_COM_WRITE_RAW", 0x1E: "SMB_COM_WRITE_MPX", 0x1F: "SMB_COM_WRITE_MPX_SECONDARY", 0x20: "SMB_COM_WRITE_COMPLETE", 0x21: "SMB_COM_QUERY_SERVER", 0x22: "SMB_COM_SET_INFORMATION2", 0x23: "SMB_COM_QUERY_INFORMATION2", 0x24: "SMB_COM_LOCKING_ANDX", 0x25: "SMB_COM_TRANSACTION", 0x26: "SMB_COM_TRANSACTION_SECONDARY", 0x27: "SMB_COM_IOCTL", 0x28: "SMB_COM_IOCTL_SECONDARY", 0x29: "SMB_COM_COPY", 0x2A: "SMB_COM_MOVE", 0x2B: "SMB_COM_ECHO", 0x2C: "SMB_COM_WRITE_AND_CLOSE", 0x2D: "SMB_COM_OPEN_ANDX", 0x2E: "SMB_COM_READ_ANDX", 0x2F: "SMB_COM_WRITE_ANDX", 0x30: "SMB_COM_NEW_FILE_SIZE", 0x31: "SMB_COM_CLOSE_AND_TREE_DISC", 0x32: "SMB_COM_TRANSACTION2", 0x33: "SMB_COM_TRANSACTION2_SECONDARY", 0x34: "SMB_COM_FIND_CLOSE2", 0x35: "SMB_COM_FIND_NOTIFY_CLOSE", 0x70: "SMB_COM_TREE_CONNECT", 0x71: "SMB_COM_TREE_DISCONNECT", 0x72: "SMB_COM_NEGOTIATE", 0x73: "SMB_COM_SESSION_SETUP_ANDX", 0x74: "SMB_COM_LOGOFF_ANDX", 0x75: "SMB_COM_TREE_CONNECT_ANDX", 0x7E: "SMB_COM_SECURITY_PACKAGE_ANDX", 0x80: "SMB_COM_QUERY_INFORMATION_DISK", 0x81: "SMB_COM_SEARCH", 0x82: "SMB_COM_FIND", 0x83: "SMB_COM_FIND_UNIQUE", 0x84: "SMB_COM_FIND_CLOSE", 0xA0: "SMB_COM_NT_TRANSACT", 0xA1: "SMB_COM_NT_TRANSACT_SECONDARY", 0xA2: "SMB_COM_NT_CREATE_ANDX", 0xA4: "SMB_COM_NT_CANCEL", 0xA5: "SMB_COM_NT_RENAME", 0xC0: "SMB_COM_OPEN_PRINT_FILE", 0xC1: "SMB_COM_WRITE_PRINT_FILE", 0xC2: "SMB_COM_CLOSE_PRINT_FILE", 0xC3: "SMB_COM_GET_PRINT_QUEUE", 0xD8: "SMB_COM_READ_BULK", 0xD9: "SMB_COM_WRITE_BULK", 0xDA: "SMB_COM_WRITE_BULK_DATA", 0xFE: "SMB_COM_INVALID", 0xFF: "SMB_COM_NO_ANDX_COMMAND", } class SMB_Header(Packet): name = "SMB 1 Protocol Request Header" fields_desc = [ StrFixedLenField("Start", b"\xffSMB", 4), ByteEnumField("Command", 0x72, SMB_COM), LEIntEnumField("Status", 0, STATUS_ERREF), FlagsField( "Flags", 0x18, 8, [ "LOCK_AND_READ_OK", "BUF_AVAIL", "res", "CASE_INSENSITIVE", "CANONICALIZED_PATHS", "OPLOCK", "OPBATCH", "REPLY", ], ), FlagsField( "Flags2", 0x0000, -16, [ "LONG_NAMES", "EAS", "SMB_SECURITY_SIGNATURE", "COMPRESSED", "SMB_SECURITY_SIGNATURE_REQUIRED", "res", "IS_LONG_NAME", "res", "res", "res", "REPARSE_PATH", "EXTENDED_SECURITY", "DFS", "PAGING_IO", "NT_STATUS", "UNICODE", ], ), LEShortField("PIDHigh", 0x0000), StrFixedLenField("SecuritySignature", b"", length=8), LEShortField("Reserved", 0x0), LEShortField("TID", 0), LEShortField("PIDLow", 0), LEShortField("UID", 0), LEShortField("MID", 0), ] def guess_payload_class(self, payload): # type: (bytes) -> Packet if not payload: return super(SMB_Header, self).guess_payload_class(payload) WordCount = ord(payload[:1]) if self.Command == 0x72: if self.Flags.REPLY: if self.Flags2.EXTENDED_SECURITY: return SMBNegotiate_Response_Extended_Security else: return SMBNegotiate_Response_Security else: return SMBNegotiate_Request elif self.Command == 0x73: if WordCount == 0: return SMBSession_Null if self.Flags.REPLY: if WordCount == 0x04: return SMBSession_Setup_AndX_Response_Extended_Security elif WordCount == 0x03: return SMBSession_Setup_AndX_Response if self.Flags2.EXTENDED_SECURITY: return SMBSession_Setup_AndX_Response_Extended_Security else: return SMBSession_Setup_AndX_Response else: if WordCount == 0x0C: return SMBSession_Setup_AndX_Request_Extended_Security elif WordCount == 0x0D: return SMBSession_Setup_AndX_Request if self.Flags2.EXTENDED_SECURITY: return SMBSession_Setup_AndX_Request_Extended_Security else: return SMBSession_Setup_AndX_Request elif self.Command == 0x25: if self.Flags.REPLY: if WordCount == 0x11: return SMBMailslot_Write else: return SMBTransaction_Response else: if WordCount == 0x11: return SMBMailslot_Write else: return SMBTransaction_Request return super(SMB_Header, self).guess_payload_class(payload) def answers(self, pkt): return SMB_Header in pkt # SMB Negotiate Request class SMB_Dialect(Packet): name = "SMB Dialect" fields_desc = [ ByteField("BufferFormat", 0x02), StrNullField("DialectString", "NT LM 0.12"), ] def default_payload_class(self, payload): return conf.padding_layer class SMBNegotiate_Request(Packet): name = "SMB Negotiate Request" fields_desc = [ ByteField("WordCount", 0), LEFieldLenField("ByteCount", None, length_of="Dialects"), PacketListField( "Dialects", [SMB_Dialect()], SMB_Dialect, length_from=lambda pkt: pkt.ByteCount, ), ] bind_layers(SMB_Header, SMBNegotiate_Request, Command=0x72) # SMBNegociate Protocol Response def _SMBStrNullField(name, default): """ Returns a StrNullField that is either normal or UTF-16 depending on the SMB headers. """ def _isUTF16(pkt): while not hasattr(pkt, "Flags2") and pkt.underlayer: pkt = pkt.underlayer return hasattr(pkt, "Flags2") and pkt.Flags2.UNICODE return MultipleTypeField( [(StrNullFieldUtf16(name, default), _isUTF16)], StrNullField(name, default), ) def _len(pkt, name): """ Returns the length of a field, works with Unicode strings. """ fld, v = pkt.getfield_and_val(name) return len(fld.addfield(pkt, v, b"")) class _SMBNegotiate_Response(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt and len(_pkt) >= 2: # Yes this is inspired by # https://github.com/wireshark/wireshark/blob/925e01b23fd5aad2fa929fafd894128a88832e74/epan/dissectors/packet-smb.c#L2902 wc = struct.unpack(" bytes return ( _NTLM_post_build( self, pkt, 32 + 31 + len(self.Setup) * 2 + len(self.Name) + 1, { "Parameter": 19, "Data": 23, }, config=_SMB_CONFIG, ) + pay ) def mysummary(self): if getattr(self, "Data", None) is not None: return self.sprintf("Tran %Name% ") + self.Data.mysummary() return self.sprintf("Tran %Name%") bind_top_down(SMB_Header, SMBTransaction_Request, Command=0x25) class SMBMailslot_Write(SMBTransaction_Request): WordCount = 0x11 # [MS-CIFS] sect 2.2.4.33.2 class SMBTransaction_Response(_NTLMPayloadPacket): name = "SMB COM Transaction Response" _NTLM_PAYLOAD_FIELD_NAME = "Buffer" fields_desc = [ FieldLenField( "WordCount", None, length_of="SetupCount", adjust=lambda pkt, x: x + 0x0A, fmt="B", ), FieldLenField( "TotalParamCount", None, length_of="Buffer", fmt=" bytes return ( _NTLM_post_build( self, pkt, 32 + 22 + len(self.Setup) * 2, { "Parameter": 7, "Data": 13, }, config=_SMB_CONFIG, ) + pay ) bind_top_down(SMB_Header, SMBTransaction_Response, Command=0x25, Flags=0x80) # [MS-ADTS] sect 6.3.1.4 _NETLOGON_opcodes = { 0x7: "LOGON_PRIMARY_QUERY", 0x12: "LOGON_SAM_LOGON_REQUEST", 0x13: "LOGON_SAM_LOGON_RESPONSE", 0x15: "LOGON_SAM_USER_UNKNOWN", 0x17: "LOGON_SAM_LOGON_RESPONSE_EX", 0x19: "LOGON_SAM_USER_UNKNOWN_EX", } _NV_VERSION = { 0x00000001: "V1", 0x00000002: "V5", 0x00000004: "V5EX", 0x00000008: "V5EX_WITH_IP", 0x00000010: "V5EX_WITH_CLOSEST_SITE", 0x01000000: "AVOID_NT4EMUL", 0x10000000: "PDC", 0x20000000: "IP", 0x40000000: "LOCAL", 0x80000000: "GC", } class NETLOGON(Packet): @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: if _pkt[0] == 0x07: # LOGON_PRIMARY_QUERY return NETLOGON_LOGON_QUERY elif _pkt[0] == 0x12: # LOGON_SAM_LOGON_REQUEST return NETLOGON_SAM_LOGON_REQUEST elif _pkt[0] == 0x13: # LOGON_SAM_USER_RESPONSE try: i = _pkt.index(b"\xff\xff\xff\xff") NtVersion = NETLOGON_SAM_LOGON_RESPONSE_NT40.fields_desc[ -3 ].getfield(None, _pkt[i - 4 : i])[1] if NtVersion.V1 and not NtVersion.V5: return NETLOGON_SAM_LOGON_RESPONSE_NT40 except Exception: pass return NETLOGON_SAM_LOGON_RESPONSE elif _pkt[0] == 0x15: # LOGON_SAM_USER_UNKNOWN return NETLOGON_SAM_LOGON_RESPONSE elif _pkt[0] == 0x17: # LOGON_SAM_LOGON_RESPONSE_EX return NETLOGON_SAM_LOGON_RESPONSE_EX elif _pkt[0] == 0x19: # LOGON_SAM_USER_UNKNOWN_EX return NETLOGON_SAM_LOGON_RESPONSE return cls class NETLOGON_LOGON_QUERY(NETLOGON): fields_desc = [ LEShortEnumField("OpCode", 0x7, _NETLOGON_opcodes), StrNullField("ComputerName", ""), StrNullField("MailslotName", ""), StrNullFieldUtf16("UnicodeComputerName", ""), FlagsField("NtVersion", 0xB, -32, _NV_VERSION), XLEShortField("LmNtToken", 0xFFFF), XLEShortField("Lm20Token", 0xFFFF), ] # [MS-ADTS] sect 6.3.1.6 class NETLOGON_SAM_LOGON_REQUEST(NETLOGON): fields_desc = [ LEShortEnumField("OpCode", 0x12, _NETLOGON_opcodes), LEShortField("RequestCount", 0), StrNullFieldUtf16("UnicodeComputerName", ""), StrNullFieldUtf16("UnicodeUserName", ""), StrNullField("MailslotName", "\\MAILSLOT\\NET\\GETDC701253F9"), LEIntField("AllowableAccountControlBits", 0), FieldLenField("DomainSidSize", None, fmt="=2008R2 0x00008000: "DS_9", # >=2012 0x00010000: "DS_10", # >=2016 0x00020000: "DS_11", # >=2019 0x00040000: "DS_12", # >=2025 0x20000000: "DNS_CONTROLLER", 0x40000000: "DNS_DOMAIN", 0x80000000: "DNS_FOREST", } # [MS-ADTS] sect 6.3.1.8 class NETLOGON_SAM_LOGON_RESPONSE(NETLOGON, DNSCompressedPacket): fields_desc = [ LEShortEnumField("OpCode", 0x17, _NETLOGON_opcodes), StrNullFieldUtf16("UnicodeLogonServer", ""), StrNullFieldUtf16("UnicodeUserName", ""), StrNullFieldUtf16("UnicodeDomainName", ""), UUIDField("DomainGuid", None, uuid_fmt=UUIDField.FORMAT_LE), UUIDField("NullGuid", None, uuid_fmt=UUIDField.FORMAT_LE), DNSStrField("DnsForestName", ""), DNSStrField("DnsDomainName", ""), DNSStrField("DnsHostName", ""), IPField("DcIpAddress", "0.0.0.0"), FlagsField("Flags", 0, -32, _NETLOGON_FLAGS), FlagsField("NtVersion", 0x1, -32, _NV_VERSION), XLEShortField("LmNtToken", 0xFFFF), XLEShortField("Lm20Token", 0xFFFF), ] def get_full(self): return self.original # [MS-ADTS] sect 6.3.1.9 class DcSockAddr(Packet): fields_desc = [ LEShortField("sin_family", 2), LEShortField("sin_port", 0), IPField("sin_addr", None), LELongField("sin_zero", 0), ] def default_payload_class(self, payload): return conf.padding_layer class NETLOGON_SAM_LOGON_RESPONSE_EX(NETLOGON, DNSCompressedPacket): fields_desc = [ LEShortEnumField("OpCode", 0x17, _NETLOGON_opcodes), LEShortField("Sbz", 0), FlagsField("Flags", 0, -32, _NETLOGON_FLAGS), UUIDField("DomainGuid", None, uuid_fmt=UUIDField.FORMAT_LE), DNSStrField("DnsForestName", ""), DNSStrField("DnsDomainName", ""), DNSStrField("DnsHostName", ""), DNSStrField("NetbiosDomainName", ""), DNSStrField("NetbiosComputerName", ""), DNSStrField("UserName", ""), DNSStrField("DcSiteName", "Default-First-Site-Name"), DNSStrField("ClientSiteName", "Default-First-Site-Name"), ConditionalField( ByteField("DcSockAddrSize", 0x10), lambda pkt: pkt.NtVersion.V5EX_WITH_IP, ), ConditionalField( PacketField("DcSockAddr", DcSockAddr(), DcSockAddr), lambda pkt: pkt.NtVersion.V5EX_WITH_IP, ), ConditionalField( DNSStrField("NextClosestSiteName", ""), lambda pkt: pkt.NtVersion.V5EX_WITH_CLOSEST_SITE, ), FlagsField("NtVersion", 0xB, -32, _NV_VERSION), XLEShortField("LmNtToken", 0xFFFF), XLEShortField("Lm20Token", 0xFFFF), ] def pre_dissect(self, s): try: i = s.index(b"\xff\xff\xff\xff") self.fields["NtVersion"] = self.fields_desc[-3].getfield( self, s[i - 4 : i] )[1] except Exception: self.NtVersion = 0xB return s def get_full(self): return self.original # [MS-BRWS] sect 2.2 class BRWS(Packet): fields_desc = [ ByteEnumField( "OpCode", 0x00, { 0x01: "HostAnnouncement", 0x02: "AnnouncementRequest", 0x08: "RequestElection", 0x09: "GetBackupListRequest", 0x0A: "GetBackupListResponse", 0x0B: "BecomeBackup", 0x0C: "DomainAnnouncement", 0x0D: "MasterAnnouncement", 0x0E: "ResetStateRequest", 0x0F: "LocalMasterAnnouncement", }, ), ] def mysummary(self): return self.sprintf("%OpCode%") registered_opcodes = {} @classmethod def register_variant(cls): cls.registered_opcodes[cls.OpCode.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: return cls.registered_opcodes.get(_pkt[0], cls) return cls def default_payload_class(self, payload): return conf.padding_layer # [MS-BRWS] sect 2.2.1 class BRWS_HostAnnouncement(BRWS): OpCode = 0x01 fields_desc = [ BRWS, ByteField("UpdateCount", 0), LEIntField("Periodicity", 128000), StrFixedLenField("ServerName", b"", length=16), ByteField("OSVersionMajor", 6), ByteField("OSVersionMinor", 1), LEIntField("ServerType", 4611), ByteField("BrowserConfigVersionMajor", 21), ByteField("BrowserConfigVersionMinor", 1), XLEShortField("Signature", 0xAA55), StrNullField("Comment", ""), ] def mysummary(self): return self.sprintf("%OpCode% for %ServerName%") # [MS-BRWS] sect 2.2.6 class BRWS_BecomeBackup(BRWS): OpCode = 0x0B fields_desc = [ BRWS, StrNullField("BrowserToPromote", b""), ] def mysummary(self): return self.sprintf("%OpCode% from %BrowserToPromote%") # [MS-BRWS] sect 2.2.10 class BRWS_LocalMasterAnnouncement(BRWS_HostAnnouncement): OpCode = 0x0F # SMB dispatcher class _SMBGeneric(Packet): name = "SMB Generic dispatcher" fields_desc = [StrFixedLenField("Start", b"\xffSMB", 4)] @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Depending on the first 4 bytes of the packet, dispatch to the correct version of Header (either SMB or SMB2) """ if _pkt and len(_pkt) >= 4: if _pkt[:4] == b"\xffSMB": return SMB_Header if _pkt[:4] == b"\xfeSMB": return SMB2_Header if _pkt[:4] == b"\xfdSMB": return SMB2_Transform_Header if _pkt[:4] == b"\xfcSMB": return SMB2_Compression_Transform_Header return cls bind_layers(NBTSession, _SMBGeneric) bind_layers(NBTDatagram, _SMBGeneric)