1# Copyright (c) 2013-2018 CORE Security Technologies 2# 3# This software is provided under under a slightly modified version 4# of the Apache Software License. See the accompanying LICENSE file 5# for more information. 6# 7# Structures and types used in LDAP 8# Contains the Structures for the NT Security Descriptor (non-RPC format) and 9# all ACL related structures 10# 11# Author: 12# Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 13# 14# 15from struct import unpack, pack 16from impacket.structure import Structure 17from impacket.uuid import string_to_bin 18 19# Global constant if the library should recalculate ACE sizes in objects that are decoded/re-encoded. 20# This defaults to True, but this causes the ACLs to not match on a binary level 21# since Active Directory for some reason sometimes adds null bytes to the end of ACEs. 22# This is valid according to the spec (see 2.4.4), but since impacket encodes them more efficiently 23# this should be turned off if running unit tests. 24RECALC_ACE_SIZE = True 25 26# LDAP SID structure - based on SAMR_RPC_SID, except the SubAuthority is LE here 27class LDAP_SID_IDENTIFIER_AUTHORITY(Structure): 28 structure = ( 29 ('Value','6s'), 30 ) 31 32class LDAP_SID(Structure): 33 structure = ( 34 ('Revision','<B'), 35 ('SubAuthorityCount','<B'), 36 ('IdentifierAuthority',':',LDAP_SID_IDENTIFIER_AUTHORITY), 37 ('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'), 38 ('SubAuthority',':'), 39 ) 40 41 def formatCanonical(self): 42 ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5])) 43 for i in range(self['SubAuthorityCount']): 44 ans += '-%d' % ( unpack('<L',self['SubAuthority'][i*4:i*4+4])[0]) 45 return ans 46 47 def fromCanonical(self, canonical): 48 items = canonical.split('-') 49 self['Revision'] = int(items[1]) 50 self['IdentifierAuthority'] = LDAP_SID_IDENTIFIER_AUTHORITY() 51 self['IdentifierAuthority']['Value'] = '\x00\x00\x00\x00\x00' + pack('B',int(items[2])) 52 self['SubAuthorityCount'] = len(items) - 3 53 self['SubAuthority'] = '' 54 for i in range(self['SubAuthorityCount']): 55 self['SubAuthority'] += pack('<L', int(items[i+3])) 56 57""" 58Self-relative security descriptor as described in 2.4.6 59https://msdn.microsoft.com/en-us/library/cc230366.aspx 60""" 61class SR_SECURITY_DESCRIPTOR(Structure): 62 structure = ( 63 ('Revision','c'), 64 ('Sbz1','c'), 65 ('Control','<H'), 66 ('OffsetOwner','<L'), 67 ('OffsetGroup','<L'), 68 ('OffsetSacl','<L'), 69 ('OffsetDacl','<L'), 70 ('Sacl',':'), 71 ('Dacl',':'), 72 ('OwnerSid',':'), 73 ('GroupSid',':'), 74 ) 75 76 def fromString(self, data): 77 Structure.fromString(self, data) 78 # All these fields are optional, if the offset is 0 they are empty 79 # there are also flags indicating if they are present 80 # TODO: parse those if it adds value 81 if self['OffsetOwner'] != 0: 82 self['OwnerSid'] = LDAP_SID(data=data[self['OffsetOwner']:]) 83 else: 84 self['OwnerSid'] = '' 85 86 if self['OffsetGroup'] != 0: 87 self['GroupSid'] = LDAP_SID(data=data[self['OffsetGroup']:]) 88 else: 89 self['GroupSid'] = '' 90 91 if self['OffsetSacl'] != 0: 92 self['Sacl'] = ACL(data=data[self['OffsetSacl']:]) 93 else: 94 self['Sacl'] = '' 95 96 if self['OffsetDacl'] != 0: 97 self['Dacl'] = ACL(data=data[self['OffsetDacl']:]) 98 else: 99 self['Sacl'] = '' 100 101 def getData(self): 102 headerlen = 20 103 # Reconstruct the security descriptor 104 # flags are currently not set automatically 105 # TODO: do this? 106 datalen = 0 107 if self['Sacl'] != '': 108 self['OffsetSacl'] = headerlen + datalen 109 datalen += len(self['Sacl'].getData()) 110 else: 111 self['OffsetSacl'] = 0 112 113 if self['Dacl'] != '': 114 self['OffsetDacl'] = headerlen + datalen 115 datalen += len(self['Dacl'].getData()) 116 else: 117 self['OffsetDacl'] = 0 118 119 if self['OwnerSid'] != '': 120 self['OffsetOwner'] = headerlen + datalen 121 datalen += len(self['OwnerSid'].getData()) 122 else: 123 self['OffsetOwner'] = 0 124 125 if self['GroupSid'] != '': 126 self['OffsetGroup'] = headerlen + datalen 127 datalen += len(self['GroupSid'].getData()) 128 else: 129 self['OffsetGroup'] = 0 130 return Structure.getData(self) 131 132""" 133ACE as described in 2.4.4 134https://msdn.microsoft.com/en-us/library/cc230295.aspx 135""" 136class ACE(Structure): 137 # Flag constants 138 CONTAINER_INHERIT_ACE = 0x01 139 FAILED_ACCESS_ACE_FLAG = 0x80 140 INHERIT_ONLY_ACE = 0x08 141 INHERITED_ACE = 0x10 142 NO_PROPAGATE_INHERIT_ACE = 0x04 143 OBJECT_INHERIT_ACE = 0x01 144 SUCCESSFUL_ACCESS_ACE_FLAG = 0x40 145 146 structure = ( 147 # 148 # ACE_HEADER as described in 2.4.4.1 149 # https://msdn.microsoft.com/en-us/library/cc230296.aspx 150 # 151 ('AceType','B'), 152 ('AceFlags','B'), 153 ('AceSize','<H'), 154 # Virtual field to calculate data length from AceSize 155 ('AceLen', '_-Ace', 'self["AceSize"]-4'), 156 # 157 # ACE body, is parsed depending on the type 158 # 159 ('Ace',':') 160 ) 161 162 def fromString(self, data): 163 # This will parse the header 164 Structure.fromString(self, data) 165 # Now we parse the ACE body according to its type 166 self['TypeName'] = ACE_TYPE_MAP[self['AceType']].__name__ 167 self['Ace'] = ACE_TYPE_MAP[self['AceType']](data=self['Ace']) 168 169 def getData(self): 170 if RECALC_ACE_SIZE or 'AceSize' not in self.fields: 171 self['AceSize'] = len(self['Ace'].getData())+4 # Header size (4 bytes) is included 172 if self['AceSize'] % 4 != 0: 173 # Make sure the alignment is correct 174 self['AceSize'] += self['AceSize'] % 4 175 data = Structure.getData(self) 176 # For some reason ACEs are sometimes longer than they need to be 177 # we fill this space up with null bytes to make sure the object 178 # we create is identical to the original object 179 if len(data) < self['AceSize']: 180 data += '\x00' * (self['AceSize'] - len(data)) 181 return data 182 183 def hasFlag(self, flag): 184 return self['AceFlags'] & flag == flag 185 186""" 187ACCESS_MASK as described in 2.4.3 188https://msdn.microsoft.com/en-us/library/cc230294.aspx 189""" 190class ACCESS_MASK(Structure): 191 # Flag constants 192 GENERIC_READ = 0x80000000L 193 GENERIC_WRITE = 0x04000000L 194 GENERIC_EXECUTE = 0x20000000L 195 GENERIC_ALL = 0x10000000L 196 MAXIMUM_ALLOWED = 0x02000000L 197 ACCESS_SYSTEM_SECURITY = 0x01000000L 198 SYNCHRONIZE = 0x00100000L 199 WRITE_OWNER = 0x00080000L 200 WRITE_DACL = 0x00040000L 201 READ_CONTROL = 0x00020000L 202 DELETE = 0x00010000L 203 204 structure = ( 205 ('Mask', '<L'), 206 ) 207 208 def hasPriv(self, priv): 209 return self['Mask'] & priv == priv 210 211 def setPriv(self, priv): 212 self['Mask'] |= priv 213 214 def removePriv(self, priv): 215 self['Mask'] ^= priv 216 217""" 218ACCESS_ALLOWED_ACE as described in 2.4.4.2 219https://msdn.microsoft.com/en-us/library/cc230286.aspx 220""" 221class ACCESS_ALLOWED_ACE(Structure): 222 ACE_TYPE = 0x00 223 structure = ( 224 ('Mask', ':', ACCESS_MASK), 225 ('Sid', ':', LDAP_SID) 226 ) 227 228""" 229ACCESS_ALLOWED_OBJECT_ACE as described in 2.4.4.3 230https://msdn.microsoft.com/en-us/library/cc230289.aspx 231""" 232class ACCESS_ALLOWED_OBJECT_ACE(Structure): 233 ACE_TYPE = 0x05 234 235 # Flag contstants 236 ACE_OBJECT_TYPE_PRESENT = 0x01 237 ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x02 238 239 # ACE type specific mask constants 240 # Note that while not documented, these also seem valid 241 # for ACCESS_ALLOWED_ACE types 242 ADS_RIGHT_DS_CONTROL_ACCESS = 0x00000100 243 ADS_RIGHT_DS_CREATE_CHILD = 0x00000001 244 ADS_RIGHT_DS_DELETE_CHILD = 0x00000002 245 ADS_RIGHT_DS_READ_PROP = 0x00000010 246 ADS_RIGHT_DS_WRITE_PROP = 0x00000020 247 ADS_RIGHT_DS_SELF = 0x00000008 248 249 250 structure = ( 251 ('Mask', ':', ACCESS_MASK), 252 ('Flags', '<L'), 253 # Optional field 254 ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'), 255 ('ObjectType', ':=""'), 256 # Optional field 257 ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'), 258 ('InheritedObjectType', ':=""'), 259 ('Sid', ':', LDAP_SID) 260 ) 261 262 @staticmethod 263 def checkInheritedObjectType(flags): 264 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT: 265 return 16 266 return 0 267 268 @staticmethod 269 def checkObjectType(flags): 270 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT: 271 return 16 272 return 0 273 274 def getData(self): 275 # Set the correct flags 276 if self['ObjectType'] != '': 277 self['Flags'] |= self.ACE_OBJECT_TYPE_PRESENT 278 if self['InheritedObjectType'] != '': 279 self['Flags'] |= self.ACE_INHERITED_OBJECT_TYPE_PRESENT 280 return Structure.getData(self) 281 282 def hasFlag(self, flag): 283 return self['Flags'] & flag == flag 284 285""" 286ACCESS_DENIED_ACE as described in 2.4.4.4 287https://msdn.microsoft.com/en-us/library/cc230291.aspx 288Structure is identical to ACCESS_ALLOWED_ACE 289""" 290class ACCESS_DENIED_ACE(ACCESS_ALLOWED_ACE): 291 ACE_TYPE = 0x01 292 293""" 294ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.5 295https://msdn.microsoft.com/en-us/library/gg750297.aspx 296Structure is identical to ACCESS_ALLOWED_OBJECT_ACE 297""" 298class ACCESS_DENIED_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE): 299 ACE_TYPE = 0x06 300 301""" 302ACCESS_ALLOWED_CALLBACK_ACE as described in 2.4.4.6 303https://msdn.microsoft.com/en-us/library/cc230287.aspx 304""" 305class ACCESS_ALLOWED_CALLBACK_ACE(Structure): 306 ACE_TYPE = 0x09 307 structure = ( 308 ('Mask', ':', ACCESS_MASK), 309 ('Sid', ':', LDAP_SID), 310 ('ApplicationData', ':') 311 ) 312 313""" 314ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.7 315https://msdn.microsoft.com/en-us/library/cc230292.aspx 316Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 317""" 318class ACCESS_DENIED_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 319 ACE_TYPE = 0x0A 320 321""" 322ACCESS_ALLOWED_CALLBACK_OBJECT_ACE as described in 2.4.4.8 323https://msdn.microsoft.com/en-us/library/cc230288.aspx 324""" 325class ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE): 326 ACE_TYPE = 0x0B 327 structure = ( 328 ('Mask', ':', ACCESS_MASK), 329 ('Flags', '<L'), 330 # Optional field 331 ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'), 332 ('ObjectType', ':=""'), 333 # Optional field 334 ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'), 335 ('InheritedObjectType', ':=""'), 336 ('Sid', ':', LDAP_SID), 337 ('ApplicationData', ':') 338 ) 339 340""" 341ACCESS_DENIED_CALLBACK_OBJECT_ACE as described in 2.4.4.7 342https://msdn.microsoft.com/en-us/library/cc230292.aspx 343Structure is identical to ACCESS_ALLOWED_OBJECT_OBJECT_ACE 344""" 345class ACCESS_DENIED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 346 ACE_TYPE = 0x0C 347 348""" 349SYSTEM_AUDIT_ACE as described in 2.4.4.10 350https://msdn.microsoft.com/en-us/library/cc230376.aspx 351Structure is identical to ACCESS_ALLOWED_ACE 352""" 353class SYSTEM_AUDIT_ACE(ACCESS_ALLOWED_ACE): 354 ACE_TYPE = 0x02 355 356 357""" 358SYSTEM_AUDIT_OBJECT_ACE as described in 2.4.4.11 359https://msdn.microsoft.com/en-us/library/gg750298.aspx 360Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE 361""" 362class SYSTEM_AUDIT_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 363 ACE_TYPE = 0x07 364 365 366""" 367SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.12 368https://msdn.microsoft.com/en-us/library/cc230377.aspx 369Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 370""" 371class SYSTEM_AUDIT_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 372 ACE_TYPE = 0x0D 373 374""" 375SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.13 376https://msdn.microsoft.com/en-us/library/cc230379.aspx 377Structure is identical to ACCESS_ALLOWED_ACE, but with custom masks and meanings. 378Lets keep it separate for now 379""" 380class SYSTEM_MANDATORY_LABEL_ACE(Structure): 381 ACE_TYPE = 0x11 382 structure = ( 383 ('Mask', ':', ACCESS_MASK), 384 ('Sid', ':', LDAP_SID) 385 ) 386 387""" 388SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.14 389https://msdn.microsoft.com/en-us/library/cc230378.aspx 390Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE 391""" 392class SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 393 ACE_TYPE = 0x0F 394 395""" 396SYSTEM_RESOURCE_ATTRIBUTE_ACE as described in 2.4.4.15 397https://msdn.microsoft.com/en-us/library/hh877837.aspx 398Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 399The application data however is encoded in CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 400format as described in section 2.4.10.1 401Todo: implement this substructure if needed 402""" 403class SYSTEM_RESOURCE_ATTRIBUTE_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 404 ACE_TYPE = 0x12 405 406 407""" 408SYSTEM_SCOPED_POLICY_ID_ACE as described in 2.4.4.16 409https://msdn.microsoft.com/en-us/library/hh877846.aspx 410Structure is identical to ACCESS_ALLOWED_ACE 411The Sid data MUST match a CAPID of a CentralAccessPolicy 412contained in the CentralAccessPoliciesList 413Todo: implement this substructure if needed 414Also the ACCESS_MASK must always be 0 415""" 416class SYSTEM_SCOPED_POLICY_ID_ACE(ACCESS_ALLOWED_ACE): 417 ACE_TYPE = 0x13 418 419# All the ACE types in a list 420ACE_TYPES = [ 421 ACCESS_ALLOWED_ACE, 422 ACCESS_ALLOWED_OBJECT_ACE, 423 ACCESS_DENIED_ACE, 424 ACCESS_DENIED_OBJECT_ACE, 425 ACCESS_ALLOWED_CALLBACK_ACE, 426 ACCESS_DENIED_CALLBACK_ACE, 427 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE, 428 ACCESS_DENIED_CALLBACK_OBJECT_ACE, 429 SYSTEM_AUDIT_ACE, 430 SYSTEM_AUDIT_OBJECT_ACE, 431 SYSTEM_AUDIT_CALLBACK_ACE, 432 SYSTEM_MANDATORY_LABEL_ACE, 433 SYSTEM_AUDIT_CALLBACK_OBJECT_ACE, 434 SYSTEM_RESOURCE_ATTRIBUTE_ACE, 435 SYSTEM_SCOPED_POLICY_ID_ACE 436] 437 438# A dict of all the ACE types indexed by their type number 439ACE_TYPE_MAP = {ace.ACE_TYPE: ace for ace in ACE_TYPES} 440 441""" 442ACL as described in 2.4.5 443https://msdn.microsoft.com/en-us/library/cc230297.aspx 444""" 445class ACL(Structure): 446 structure = ( 447 ('AclRevision', 'B'), 448 ('Sbz1', 'B'), 449 ('AclSize', '<H'), 450 ('AceCount', '<H'), 451 ('Sbz2', '<H'), 452 # Virtual field to calculate data length from AclSize 453 ('DataLen', '_-Data', 'self["AclSize"]-8'), 454 ('Data', ':'), 455 ) 456 457 def fromString(self, data): 458 self.aces = [] 459 Structure.fromString(self, data) 460 for i in range(self['AceCount']): 461 # If we don't have any data left, return 462 if len(self['Data']) == 0: 463 raise Exception, "ACL header indicated there are more ACLs to unpack, but there is no more data" 464 ace = ACE(data=self['Data']) 465 self.aces.append(ace) 466 self['Data'] = self['Data'][ace['AceSize']:] 467 self['Data'] = self.aces 468 469 def getData(self): 470 self['AceCount'] = len(self.aces) 471 # We modify the data field to be able to use the 472 # parent class parsing 473 self['Data'] = ''.join([ace.getData() for ace in self.aces]) 474 self['AclSize'] = len(self['Data'])+8 # Header size (8 bytes) is included 475 data = Structure.getData(self) 476 # Put the ACEs back in data 477 self['Data'] = self.aces 478 return data 479 480""" 481objectClass mapping to GUID for some common classes (index is the ldapDisplayName). 482Reference: 483 https://msdn.microsoft.com/en-us/library/ms680938(v=vs.85).aspx 484Can also be queried from the Schema 485""" 486OBJECTTYPE_GUID_MAP = { 487 'group': 'bf967a9c-0de6-11d0-a285-00aa003049e2', 488 'domain': '19195a5a-6da0-11d0-afd3-00c04fd930c9', 489 'organizationalUnit': 'bf967aa5-0de6-11d0-a285-00aa003049e2', 490 'user': 'bf967aba-0de6-11d0-a285-00aa003049e2', 491 'groupPolicyContainer': 'f30e3bc2-9ff0-11d1-b603-0000f80367c1' 492} 493