# SPDX-License-Identifier: GPL-2.0-only # This file is part of Scapy # See https://scapy.net/ for more information # Copyright (C) Gabriel Potter """ [MS-PAC] https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-pac/166d8064-c863-41e1-9c23-edaaa5f36962 Up to date with version: 23.0 """ import struct from scapy.config import conf from scapy.error import log_runtime from scapy.fields import ( ConditionalField, FieldLenField, FieldListField, FlagsField, LEIntEnumField, LEIntField, LELongField, LEShortField, MultipleTypeField, PacketField, PacketListField, StrField, StrFieldUtf16, StrFixedLenField, StrLenFieldUtf16, UTCTimeField, UUIDField, XStrField, XStrLenField, ) from scapy.packet import Packet from scapy.layers.kerberos import ( _AUTHORIZATIONDATA_VALUES, _KRB_S_TYPES, ) from scapy.layers.dcerpc import ( NDRByteField, NDRConfFieldListField, NDRConfPacketListField, NDRConfStrLenField, NDRConfVarStrLenFieldUtf16, NDRConfVarStrNullFieldUtf16, NDRConformantString, NDRFieldListField, NDRFullEmbPointerField, NDRInt3264EnumField, NDRIntField, NDRLongField, NDRPacket, NDRPacketField, NDRSerialization1Header, NDRSerializeType1PacketLenField, NDRShortField, NDRSignedLongField, NDRUnionField, _NDRConfField, ndr_deserialize1, ndr_serialize1, ) from scapy.layers.ntlm import ( _NTLMPayloadField, _NTLMPayloadPacket, ) from scapy.layers.smb2 import WINNT_SID # sect 2.4 class PAC_INFO_BUFFER(Packet): fields_desc = [ LEIntEnumField( "ulType", 0x00000001, { 0x00000001: "Logon information", 0x00000002: "Credentials information", 0x00000006: "Server Signature", 0x00000007: "KDC Signature", 0x0000000A: "Client name and ticket information", 0x0000000B: "Constrained delegation information", 0x0000000C: "UPN and DNS information", 0x0000000D: "Client claims information", 0x0000000E: "Device information", 0x0000000F: "Device claims information", 0x00000010: "Ticket Signature", 0x00000011: "PAC Attributes", 0x00000012: "PAC Requestor", 0x00000013: "Extended KDC Signature", }, ), LEIntField("cbBufferSize", None), LELongField("Offset", None), ] def default_payload_class(self, payload): return conf.padding_layer _PACTYPES = {} # sect 2.5 - NDR PACKETS class RPC_UNICODE_STRING(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRShortField("Length", None, size_of="Buffer", adjust=lambda _, x: (x * 2)), NDRShortField( "MaximumLength", None, size_of="Buffer", adjust=lambda _, x: (x * 2) ), NDRFullEmbPointerField( NDRConfVarStrLenFieldUtf16( "Buffer", "", size_is=lambda pkt: (pkt.MaximumLength // 2), length_is=lambda pkt: (pkt.Length // 2), ), ), ] class FILETIME(NDRPacket): ALIGNMENT = (4, 4) fields_desc = [NDRIntField("dwLowDateTime", 0), NDRIntField("dwHighDateTime", 0)] class GROUP_MEMBERSHIP(NDRPacket): ALIGNMENT = (4, 4) fields_desc = [NDRIntField("RelativeId", 0), NDRIntField("Attributes", 0)] class CYPHER_BLOCK(NDRPacket): fields_desc = [StrFixedLenField("data", "", length=8)] class USER_SESSION_KEY(NDRPacket): fields_desc = [PacketListField("data", [], CYPHER_BLOCK, count_from=lambda _: 2)] class RPC_SID_IDENTIFIER_AUTHORITY(NDRPacket): fields_desc = [StrFixedLenField("Value", "", length=6)] class SID(NDRPacket): ALIGNMENT = (4, 8) DEPORTED_CONFORMANTS = ["SubAuthority"] fields_desc = [ NDRByteField("Revision", 0), NDRByteField("SubAuthorityCount", None, size_of="SubAuthority"), NDRPacketField( "IdentifierAuthority", RPC_SID_IDENTIFIER_AUTHORITY(), RPC_SID_IDENTIFIER_AUTHORITY, ), NDRConfFieldListField( "SubAuthority", [], NDRIntField("", 0), size_is=lambda pkt: pkt.SubAuthorityCount, conformant_in_struct=True, ), ] def summary(self): return WINNT_SID.summary(self) class KERB_SID_AND_ATTRIBUTES(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRPacketField("Sid", SID(), SID)), NDRIntField("Attributes", 0), ] class KERB_VALIDATION_INFO(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRPacketField("LogonTime", FILETIME(), FILETIME), NDRPacketField("LogoffTime", FILETIME(), FILETIME), NDRPacketField("KickOffTime", FILETIME(), FILETIME), NDRPacketField("PasswordLastSet", FILETIME(), FILETIME), NDRPacketField("PasswordCanChange", FILETIME(), FILETIME), NDRPacketField("PasswordMustChange", FILETIME(), FILETIME), NDRPacketField("EffectiveName", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("FullName", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("LogonScript", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("ProfilePath", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("HomeDirectory", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("HomeDirectoryDrive", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRShortField("LogonCount", 0), NDRShortField("BadPasswordCount", 0), NDRIntField("UserId", 0), NDRIntField("PrimaryGroupId", 0), NDRIntField("GroupCount", None, size_of="GroupIds"), NDRFullEmbPointerField( NDRConfPacketListField( "GroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.GroupCount, ), ), NDRIntField("UserFlags", 0), NDRPacketField("UserSessionKey", USER_SESSION_KEY(), USER_SESSION_KEY), NDRPacketField("LogonServer", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRPacketField("LogonDomainName", RPC_UNICODE_STRING(), RPC_UNICODE_STRING), NDRFullEmbPointerField(NDRPacketField("LogonDomainId", SID(), SID)), NDRFieldListField("Reserved1", [], NDRIntField("", 0), length_is=lambda _: 2), NDRIntField("UserAccountControl", 0), NDRFieldListField("Reserved3", [], NDRIntField("", 0), length_is=lambda _: 7), NDRIntField("SidCount", None, size_of="ExtraSids"), NDRFullEmbPointerField( NDRConfPacketListField( "ExtraSids", [KERB_SID_AND_ATTRIBUTES()], KERB_SID_AND_ATTRIBUTES, size_is=lambda pkt: pkt.SidCount, ), ), NDRFullEmbPointerField( NDRPacketField("ResourceGroupDomainSid", SID(), SID), ), NDRIntField("ResourceGroupCount", None, size_of="ResourceGroupIds"), NDRFullEmbPointerField( NDRConfPacketListField( "ResourceGroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.ResourceGroupCount, ), ), ] _PACTYPES[1] = KERB_VALIDATION_INFO # sect 2.6 class PAC_CREDENTIAL_INFO(Packet): fields_desc = [ LEIntField("Version", 0), LEIntEnumField( "EncryptionType", 1, { 0x00000001: "DES-CBC-CRC", 0x00000003: "DES-CBC-MD5", 0x00000011: "AES128_CTS_HMAC_SHA1_96", 0x00000012: "AES256_CTS_HMAC_SHA1_96", 0x00000017: "RC4-HMAC", }, ), XStrField("SerializedData", b""), ] _PACTYPES[2] = PAC_CREDENTIAL_INFO # sect 2.7 class PAC_CLIENT_INFO(Packet): fields_desc = [ UTCTimeField( "ClientId", None, fmt=" bytes offset = 12 fields = { "Upn": 0, "DnsDomainName": 4, } if self.Flags.S: offset = 20 fields["SamName"] = 12 fields["Sid"] = 16 return ( _pac_post_build( self, pkt, offset, fields, ) + pay ) _PACTYPES[0xC] = UPN_DNS_INFO # sect 2.11 - NDR PACKETS try: from enum import IntEnum except ImportError: IntEnum = object class CLAIM_TYPE(IntEnum): CLAIM_TYPE_INT64 = 1 CLAIM_TYPE_UINT64 = 2 CLAIM_TYPE_STRING = 3 CLAIM_TYPE_BOOLEAN = 6 class CLAIMS_SOURCE_TYPE(IntEnum): CLAIMS_SOURCE_TYPE_AD = 1 CLAIMS_SOURCE_TYPE_CERTIFICATE = 2 class CLAIMS_COMPRESSION_FORMAT(IntEnum): COMPRESSION_FORMAT_NONE = 0 COMPRESSION_FORMAT_LZNT1 = 2 COMPRESSION_FORMAT_XPRESS = 3 COMPRESSION_FORMAT_XPRESS_HUFF = 4 class CLAIM_ENTRY_sub0(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="Int64Values"), NDRFullEmbPointerField( NDRConfFieldListField( "Int64Values", [], NDRSignedLongField, size_is=lambda pkt: pkt.ValueCount, ), ), ] class CLAIM_ENTRY_sub1(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="Uint64Values"), NDRFullEmbPointerField( NDRConfFieldListField( "Uint64Values", [], NDRLongField, size_is=lambda pkt: pkt.ValueCount ), ), ] class CLAIM_ENTRY_sub2(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="StringValues"), NDRFullEmbPointerField( NDRConfFieldListField( "StringValues", [], NDRFullEmbPointerField( NDRConfVarStrNullFieldUtf16("StringVal", ""), ), size_is=lambda pkt: pkt.ValueCount, ), ), ] class CLAIM_ENTRY_sub3(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ValueCount", None, size_of="BooleanValues"), NDRFullEmbPointerField( NDRConfFieldListField( "BooleanValues", [], NDRLongField, size_is=lambda pkt: pkt.ValueCount ), ), ] class CLAIM_ENTRY(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRConfVarStrNullFieldUtf16("Id", "")), NDRInt3264EnumField("Type", 0, CLAIM_TYPE), NDRUnionField( [ ( NDRPacketField("Values", CLAIM_ENTRY_sub0(), CLAIM_ENTRY_sub0), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_INT64 ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_INT64), ), ), ( NDRPacketField("Values", CLAIM_ENTRY_sub1(), CLAIM_ENTRY_sub1), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_UINT64 ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_UINT64), ), ), ( NDRPacketField("Values", CLAIM_ENTRY_sub2(), CLAIM_ENTRY_sub2), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_STRING ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_STRING), ), ), ( NDRPacketField("Values", CLAIM_ENTRY_sub3(), CLAIM_ENTRY_sub3), ( ( lambda pkt: getattr(pkt, "Type", None) == CLAIM_TYPE.CLAIM_TYPE_BOOLEAN ), (lambda _, val: val.tag == CLAIM_TYPE.CLAIM_TYPE_BOOLEAN), ), ), ], StrFixedLenField("Values", "", length=0), align=(2, 8), switch_fmt=("H", "I"), ), ] class CLAIMS_ARRAY(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRInt3264EnumField("usClaimsSourceType", 0, CLAIMS_SOURCE_TYPE), NDRIntField("ulClaimsCount", None, size_of="ClaimEntries"), NDRFullEmbPointerField( NDRConfPacketListField( "ClaimEntries", [CLAIM_ENTRY()], CLAIM_ENTRY, size_is=lambda pkt: pkt.ulClaimsCount, ), ), ] class CLAIMS_SET(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ulClaimsArrayCount", None, size_of="ClaimsArrays"), NDRFullEmbPointerField( NDRConfPacketListField( "ClaimsArrays", [CLAIMS_ARRAY()], CLAIMS_ARRAY, size_is=lambda pkt: pkt.ulClaimsArrayCount, ), ), NDRShortField("usReservedType", 0), NDRIntField("ulReservedFieldSize", None, size_of="ReservedField"), NDRFullEmbPointerField( NDRConfStrLenField( "ReservedField", "", size_is=lambda pkt: pkt.ulReservedFieldSize ), ), ] class _CLAIMSClaimSet(_NDRConfField, NDRSerializeType1PacketLenField): CONFORMANT_STRING = True LENGTH_FROM = True def m2i(self, pkt, s): if pkt.usCompressionFormat == CLAIMS_COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE: return ndr_deserialize1(s, CLAIMS_SET, ptr_pack=True) else: # TODO: There are 3 funky compression formats... see sect 2.2.18.4 return NDRConformantString(value=s) def i2m(self, pkt, val): val = val[0] if pkt.usCompressionFormat == CLAIMS_COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE: return ndr_serialize1(val, ptr_pack=True) else: # funky return bytes(val) def valueof(self, pkt, x): if pkt.usCompressionFormat == CLAIMS_COMPRESSION_FORMAT.COMPRESSION_FORMAT_NONE: return self._subval(x)[0] else: return x class CLAIMS_SET_METADATA(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("ulClaimsSetSize", None, size_of="ClaimsSet"), NDRFullEmbPointerField( _CLAIMSClaimSet( "ClaimsSet", None, None, size_is=lambda pkt: pkt.ulClaimsSetSize ), ), NDRInt3264EnumField( "usCompressionFormat", 0, CLAIMS_COMPRESSION_FORMAT, ), # this size_of is technically wrong. we just assume it's uncompressed... NDRIntField("ulUncompressedClaimsSetSize", None, size_of="ClaimsSet"), NDRShortField("usReservedType", 0), NDRIntField("ulReservedFieldSize", None, size_of="ReservedField"), NDRFullEmbPointerField( NDRConfStrLenField( "ReservedField", "", size_is=lambda pkt: pkt.ulReservedFieldSize ), ), ] class PAC_CLIENT_CLAIMS_INFO(NDRPacket): fields_desc = [NDRPacketField("Claims", CLAIMS_SET_METADATA(), CLAIMS_SET_METADATA)] if IntEnum != object: # If not available, ignore. I can't be bothered _PACTYPES[0xD] = PAC_CLIENT_CLAIMS_INFO # sect 2.12 - NDR PACKETS class DOMAIN_GROUP_MEMBERSHIP(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRFullEmbPointerField(NDRPacketField("DomainId", SID(), SID)), NDRIntField("GroupCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "GroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.GroupCount, ), ), ] class PAC_DEVICE_INFO(NDRPacket): ALIGNMENT = (4, 8) fields_desc = [ NDRIntField("UserId", 0), NDRIntField("PrimaryGroupId", 0), NDRFullEmbPointerField( NDRPacketField("AccountDomainId", SID(), SID), ), NDRIntField("AccountGroupCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "AccountGroupIds", [GROUP_MEMBERSHIP()], GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.AccountGroupCount, ), ), NDRIntField("SidCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "ExtraSids", [KERB_SID_AND_ATTRIBUTES()], KERB_SID_AND_ATTRIBUTES, size_is=lambda pkt: pkt.SidCount, ), ), NDRIntField("DomainGroupCount", 0), NDRFullEmbPointerField( NDRConfPacketListField( "DomainGroup", [DOMAIN_GROUP_MEMBERSHIP()], DOMAIN_GROUP_MEMBERSHIP, size_is=lambda pkt: pkt.DomainGroupCount, ), ), ] _PACTYPES[0xE] = PAC_DEVICE_INFO # sect 2.14 - PAC_ATTRIBUTES_INFO class PAC_ATTRIBUTES_INFO(Packet): fields_desc = [ LEIntField("FlagsLength", 2), FieldListField( "Flags", ["PAC_WAS_REQUESTED"], FlagsField( "", 0, -32, { 0x00000001: "PAC_WAS_REQUESTED", 0x00000002: "PAC_WAS_GIVEN_IMPLICITLY", }, ), count_from=lambda pkt: (pkt.FlagsLength + 7) // 8, ), ] _PACTYPES[0x11] = PAC_ATTRIBUTES_INFO # sect 2.15 - PAC_REQUESTOR_SID class PAC_REQUESTOR_SID(Packet): fields_desc = [ PacketField("Sid", WINNT_SID(), WINNT_SID), ] _PACTYPES[0x12] = PAC_REQUESTOR_SID # sect 2.16 - PAC_REQUESTOR_GUID class PAC_REQUESTOR_GUID(Packet): fields_desc = [ UUIDField("Guid", None), ] _PACTYPES[0x14] = PAC_REQUESTOR_GUID # sect 2.3 class _PACTYPEBuffers(PacketListField): def addfield(self, pkt, s, val): # we use this field to set Offset and cbBufferSize res = b"" if len(val) != len(pkt.Payloads): log_runtime.warning("Size of 'Buffers' does not match size of 'Payloads' !") return super(_PACTYPEBuffers, self).addfield(pkt, s, val) offset = 16 * len(pkt.Payloads) + 8 for i, v in enumerate(val): x = self.i2m(pkt, v) pay = pkt.Payloads[i] if isinstance(pay, NDRPacket) or isinstance(pay, NDRSerialization1Header): lgth = len(ndr_serialize1(pay, ptr_pack=True)) else: lgth = len(pay) if v.cbBufferSize is None: x = x[:4] + struct.pack("