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