Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 

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 

17 

18# Global constant if the library should recalculate ACE sizes in objects that are decoded/re-encoded. 

19# This defaults to True, but this causes the ACLs to not match on a binary level 

20# since Active Directory for some reason sometimes adds null bytes to the end of ACEs. 

21# This is valid according to the spec (see 2.4.4), but since impacket encodes them more efficiently 

22# this should be turned off if running unit tests. 

23RECALC_ACE_SIZE = True 

24 

25# LDAP SID structure - based on SAMR_RPC_SID, except the SubAuthority is LE here 

26class LDAP_SID_IDENTIFIER_AUTHORITY(Structure): 

27 structure = ( 

28 ('Value','6s'), 

29 ) 

30 

31class LDAP_SID(Structure): 

32 structure = ( 

33 ('Revision','<B'), 

34 ('SubAuthorityCount','<B'), 

35 ('IdentifierAuthority',':',LDAP_SID_IDENTIFIER_AUTHORITY), 

36 ('SubLen','_-SubAuthority','self["SubAuthorityCount"]*4'), 

37 ('SubAuthority',':'), 

38 ) 

39 

40 def formatCanonical(self): 

41 ans = 'S-%d-%d' % (self['Revision'], ord(self['IdentifierAuthority']['Value'][5:6])) 

42 for i in range(self['SubAuthorityCount']): 

43 ans += '-%d' % ( unpack('<L',self['SubAuthority'][i*4:i*4+4])[0]) 

44 return ans 

45 

46 def fromCanonical(self, canonical): 

47 items = canonical.split('-') 

48 self['Revision'] = int(items[1]) 

49 self['IdentifierAuthority'] = LDAP_SID_IDENTIFIER_AUTHORITY() 

50 self['IdentifierAuthority']['Value'] = b'\x00\x00\x00\x00\x00' + pack('B',int(items[2])) 

51 self['SubAuthorityCount'] = len(items) - 3 

52 self['SubAuthority'] = b'' 

53 for i in range(self['SubAuthorityCount']): 

54 self['SubAuthority'] += pack('<L', int(items[i+3])) 

55 

56""" 

57Self-relative security descriptor as described in 2.4.6 

58https://msdn.microsoft.com/en-us/library/cc230366.aspx 

59""" 

60class SR_SECURITY_DESCRIPTOR(Structure): 

61 structure = ( 

62 ('Revision','c'), 

63 ('Sbz1','c'), 

64 ('Control','<H'), 

65 ('OffsetOwner','<L'), 

66 ('OffsetGroup','<L'), 

67 ('OffsetSacl','<L'), 

68 ('OffsetDacl','<L'), 

69 ('Sacl',':'), 

70 ('Dacl',':'), 

71 ('OwnerSid',':'), 

72 ('GroupSid',':'), 

73 ) 

74 

75 def fromString(self, data): 

76 Structure.fromString(self, data) 

77 # All these fields are optional, if the offset is 0 they are empty 

78 # there are also flags indicating if they are present 

79 # TODO: parse those if it adds value 

80 if self['OffsetOwner'] != 0: 

81 self['OwnerSid'] = LDAP_SID(data=data[self['OffsetOwner']:]) 

82 else: 

83 self['OwnerSid'] = b'' 

84 

85 if self['OffsetGroup'] != 0: 

86 self['GroupSid'] = LDAP_SID(data=data[self['OffsetGroup']:]) 

87 else: 

88 self['GroupSid'] = b'' 

89 

90 if self['OffsetSacl'] != 0: 

91 self['Sacl'] = ACL(data=data[self['OffsetSacl']:]) 

92 else: 

93 self['Sacl'] = b'' 

94 

95 if self['OffsetDacl'] != 0: 

96 self['Dacl'] = ACL(data=data[self['OffsetDacl']:]) 

97 else: 

98 self['Sacl'] = b'' 

99 

100 def getData(self): 

101 headerlen = 20 

102 # Reconstruct the security descriptor 

103 # flags are currently not set automatically 

104 # TODO: do this? 

105 datalen = 0 

106 if self['Sacl'] != b'': 

107 self['OffsetSacl'] = headerlen + datalen 

108 datalen += len(self['Sacl'].getData()) 

109 else: 

110 self['OffsetSacl'] = 0 

111 

112 if self['Dacl'] != b'': 

113 self['OffsetDacl'] = headerlen + datalen 

114 datalen += len(self['Dacl'].getData()) 

115 else: 

116 self['OffsetDacl'] = 0 

117 

118 if self['OwnerSid'] != b'': 

119 self['OffsetOwner'] = headerlen + datalen 

120 datalen += len(self['OwnerSid'].getData()) 

121 else: 

122 self['OffsetOwner'] = 0 

123 

124 if self['GroupSid'] != b'': 

125 self['OffsetGroup'] = headerlen + datalen 

126 datalen += len(self['GroupSid'].getData()) 

127 else: 

128 self['OffsetGroup'] = 0 

129 return Structure.getData(self) 

130 

131""" 

132ACE as described in 2.4.4 

133https://msdn.microsoft.com/en-us/library/cc230295.aspx 

134""" 

135class ACE(Structure): 

136 # Flag constants 

137 CONTAINER_INHERIT_ACE = 0x02 

138 FAILED_ACCESS_ACE_FLAG = 0x80 

139 INHERIT_ONLY_ACE = 0x08 

140 INHERITED_ACE = 0x10 

141 NO_PROPAGATE_INHERIT_ACE = 0x04 

142 OBJECT_INHERIT_ACE = 0x01 

143 SUCCESSFUL_ACCESS_ACE_FLAG = 0x40 

144 

145 structure = ( 

146 # 

147 # ACE_HEADER as described in 2.4.4.1 

148 # https://msdn.microsoft.com/en-us/library/cc230296.aspx 

149 # 

150 ('AceType','B'), 

151 ('AceFlags','B'), 

152 ('AceSize','<H'), 

153 # Virtual field to calculate data length from AceSize 

154 ('AceLen', '_-Ace', 'self["AceSize"]-4'), 

155 # 

156 # ACE body, is parsed depending on the type 

157 # 

158 ('Ace',':') 

159 ) 

160 

161 def fromString(self, data): 

162 # This will parse the header 

163 Structure.fromString(self, data) 

164 # Now we parse the ACE body according to its type 

165 self['TypeName'] = ACE_TYPE_MAP[self['AceType']].__name__ 

166 self['Ace'] = ACE_TYPE_MAP[self['AceType']](data=self['Ace']) 

167 

168 def getData(self): 

169 if RECALC_ACE_SIZE or 'AceSize' not in self.fields: 

170 self['AceSize'] = len(self['Ace'].getData())+4 # Header size (4 bytes) is included 

171 if self['AceSize'] % 4 != 0: 

172 # Make sure the alignment is correct 

173 self['AceSize'] += self['AceSize'] % 4 

174 data = Structure.getData(self) 

175 # For some reason ACEs are sometimes longer than they need to be 

176 # we fill this space up with null bytes to make sure the object 

177 # we create is identical to the original object 

178 if len(data) < self['AceSize']: 

179 data += '\x00' * (self['AceSize'] - len(data)) 

180 return data 

181 

182 def hasFlag(self, flag): 

183 return self['AceFlags'] & flag == flag 

184 

185""" 

186ACCESS_MASK as described in 2.4.3 

187https://msdn.microsoft.com/en-us/library/cc230294.aspx 

188""" 

189class ACCESS_MASK(Structure): 

190 # Flag constants 

191 GENERIC_READ = 0x80000000 

192 GENERIC_WRITE = 0x40000000 

193 GENERIC_EXECUTE = 0x20000000 

194 GENERIC_ALL = 0x10000000 

195 MAXIMUM_ALLOWED = 0x02000000 

196 ACCESS_SYSTEM_SECURITY = 0x01000000 

197 SYNCHRONIZE = 0x00100000 

198 WRITE_OWNER = 0x00080000 

199 WRITE_DACL = 0x00040000 

200 READ_CONTROL = 0x00020000 

201 DELETE = 0x00010000 

202 

203 structure = ( 

204 ('Mask', '<L'), 

205 ) 

206 

207 def hasPriv(self, priv): 

208 return self['Mask'] & priv == priv 

209 

210 def setPriv(self, priv): 

211 self['Mask'] |= priv 

212 

213 def removePriv(self, priv): 

214 self['Mask'] ^= priv 

215 

216""" 

217ACCESS_ALLOWED_ACE as described in 2.4.4.2 

218https://msdn.microsoft.com/en-us/library/cc230286.aspx 

219""" 

220class ACCESS_ALLOWED_ACE(Structure): 

221 ACE_TYPE = 0x00 

222 structure = ( 

223 ('Mask', ':', ACCESS_MASK), 

224 ('Sid', ':', LDAP_SID) 

225 ) 

226 

227""" 

228ACCESS_ALLOWED_OBJECT_ACE as described in 2.4.4.3 

229https://msdn.microsoft.com/en-us/library/cc230289.aspx 

230""" 

231class ACCESS_ALLOWED_OBJECT_ACE(Structure): 

232 ACE_TYPE = 0x05 

233 

234 # Flag contstants 

235 ACE_OBJECT_TYPE_PRESENT = 0x01 

236 ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x02 

237 

238 # ACE type specific mask constants 

239 # Note that while not documented, these also seem valid 

240 # for ACCESS_ALLOWED_ACE types 

241 ADS_RIGHT_DS_CONTROL_ACCESS = 0x00000100 

242 ADS_RIGHT_DS_CREATE_CHILD = 0x00000001 

243 ADS_RIGHT_DS_DELETE_CHILD = 0x00000002 

244 ADS_RIGHT_DS_READ_PROP = 0x00000010 

245 ADS_RIGHT_DS_WRITE_PROP = 0x00000020 

246 ADS_RIGHT_DS_SELF = 0x00000008 

247 

248 

249 structure = ( 

250 ('Mask', ':', ACCESS_MASK), 

251 ('Flags', '<L'), 

252 # Optional field 

253 ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'), 

254 ('ObjectType', ':=""'), 

255 # Optional field 

256 ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'), 

257 ('InheritedObjectType', ':=""'), 

258 ('Sid', ':', LDAP_SID) 

259 ) 

260 

261 @staticmethod 

262 def checkInheritedObjectType(flags): 

263 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT: 

264 return 16 

265 return 0 

266 

267 @staticmethod 

268 def checkObjectType(flags): 

269 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT: 

270 return 16 

271 return 0 

272 

273 def getData(self): 

274 # Set the correct flags 

275 if self['ObjectType'] != b'': 

276 self['Flags'] |= self.ACE_OBJECT_TYPE_PRESENT 

277 if self['InheritedObjectType'] != b'': 

278 self['Flags'] |= self.ACE_INHERITED_OBJECT_TYPE_PRESENT 

279 return Structure.getData(self) 

280 

281 def hasFlag(self, flag): 

282 return self['Flags'] & flag == flag 

283 

284""" 

285ACCESS_DENIED_ACE as described in 2.4.4.4 

286https://msdn.microsoft.com/en-us/library/cc230291.aspx 

287Structure is identical to ACCESS_ALLOWED_ACE 

288""" 

289class ACCESS_DENIED_ACE(ACCESS_ALLOWED_ACE): 

290 ACE_TYPE = 0x01 

291 

292""" 

293ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.5 

294https://msdn.microsoft.com/en-us/library/gg750297.aspx 

295Structure is identical to ACCESS_ALLOWED_OBJECT_ACE 

296""" 

297class ACCESS_DENIED_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE): 

298 ACE_TYPE = 0x06 

299 

300""" 

301ACCESS_ALLOWED_CALLBACK_ACE as described in 2.4.4.6 

302https://msdn.microsoft.com/en-us/library/cc230287.aspx 

303""" 

304class ACCESS_ALLOWED_CALLBACK_ACE(Structure): 

305 ACE_TYPE = 0x09 

306 structure = ( 

307 ('Mask', ':', ACCESS_MASK), 

308 ('Sid', ':', LDAP_SID), 

309 ('ApplicationData', ':') 

310 ) 

311 

312""" 

313ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.7 

314https://msdn.microsoft.com/en-us/library/cc230292.aspx 

315Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 

316""" 

317class ACCESS_DENIED_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 

318 ACE_TYPE = 0x0A 

319 

320""" 

321ACCESS_ALLOWED_CALLBACK_OBJECT_ACE as described in 2.4.4.8 

322https://msdn.microsoft.com/en-us/library/cc230288.aspx 

323""" 

324class ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE): 

325 ACE_TYPE = 0x0B 

326 structure = ( 

327 ('Mask', ':', ACCESS_MASK), 

328 ('Flags', '<L'), 

329 # Optional field 

330 ('ObjectTypeLen','_-ObjectType','self.checkObjectType(self["Flags"])'), 

331 ('ObjectType', ':=""'), 

332 # Optional field 

333 ('InheritedObjectTypeLen','_-InheritedObjectType','self.checkInheritedObjectType(self["Flags"])'), 

334 ('InheritedObjectType', ':=""'), 

335 ('Sid', ':', LDAP_SID), 

336 ('ApplicationData', ':') 

337 ) 

338 

339""" 

340ACCESS_DENIED_CALLBACK_OBJECT_ACE as described in 2.4.4.7 

341https://msdn.microsoft.com/en-us/library/cc230292.aspx 

342Structure is identical to ACCESS_ALLOWED_OBJECT_OBJECT_ACE 

343""" 

344class ACCESS_DENIED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 

345 ACE_TYPE = 0x0C 

346 

347""" 

348SYSTEM_AUDIT_ACE as described in 2.4.4.10 

349https://msdn.microsoft.com/en-us/library/cc230376.aspx 

350Structure is identical to ACCESS_ALLOWED_ACE 

351""" 

352class SYSTEM_AUDIT_ACE(ACCESS_ALLOWED_ACE): 

353 ACE_TYPE = 0x02 

354 

355 

356""" 

357SYSTEM_AUDIT_OBJECT_ACE as described in 2.4.4.11 

358https://msdn.microsoft.com/en-us/library/gg750298.aspx 

359Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE 

360""" 

361class SYSTEM_AUDIT_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 

362 ACE_TYPE = 0x07 

363 

364 

365""" 

366SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.12 

367https://msdn.microsoft.com/en-us/library/cc230377.aspx 

368Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 

369""" 

370class SYSTEM_AUDIT_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 

371 ACE_TYPE = 0x0D 

372 

373""" 

374SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.13 

375https://msdn.microsoft.com/en-us/library/cc230379.aspx 

376Structure is identical to ACCESS_ALLOWED_ACE, but with custom masks and meanings. 

377Lets keep it separate for now 

378""" 

379class SYSTEM_MANDATORY_LABEL_ACE(Structure): 

380 ACE_TYPE = 0x11 

381 structure = ( 

382 ('Mask', ':', ACCESS_MASK), 

383 ('Sid', ':', LDAP_SID) 

384 ) 

385 

386""" 

387SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.14 

388https://msdn.microsoft.com/en-us/library/cc230378.aspx 

389Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE 

390""" 

391class SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 

392 ACE_TYPE = 0x0F 

393 

394""" 

395SYSTEM_RESOURCE_ATTRIBUTE_ACE as described in 2.4.4.15 

396https://msdn.microsoft.com/en-us/library/hh877837.aspx 

397Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 

398The application data however is encoded in CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 

399format as described in section 2.4.10.1 

400Todo: implement this substructure if needed 

401""" 

402class SYSTEM_RESOURCE_ATTRIBUTE_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 

403 ACE_TYPE = 0x12 

404 

405 

406""" 

407SYSTEM_SCOPED_POLICY_ID_ACE as described in 2.4.4.16 

408https://msdn.microsoft.com/en-us/library/hh877846.aspx 

409Structure is identical to ACCESS_ALLOWED_ACE 

410The Sid data MUST match a CAPID of a CentralAccessPolicy 

411contained in the CentralAccessPoliciesList 

412Todo: implement this substructure if needed 

413Also the ACCESS_MASK must always be 0 

414""" 

415class SYSTEM_SCOPED_POLICY_ID_ACE(ACCESS_ALLOWED_ACE): 

416 ACE_TYPE = 0x13 

417 

418# All the ACE types in a list 

419ACE_TYPES = [ 

420 ACCESS_ALLOWED_ACE, 

421 ACCESS_ALLOWED_OBJECT_ACE, 

422 ACCESS_DENIED_ACE, 

423 ACCESS_DENIED_OBJECT_ACE, 

424 ACCESS_ALLOWED_CALLBACK_ACE, 

425 ACCESS_DENIED_CALLBACK_ACE, 

426 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE, 

427 ACCESS_DENIED_CALLBACK_OBJECT_ACE, 

428 SYSTEM_AUDIT_ACE, 

429 SYSTEM_AUDIT_OBJECT_ACE, 

430 SYSTEM_AUDIT_CALLBACK_ACE, 

431 SYSTEM_MANDATORY_LABEL_ACE, 

432 SYSTEM_AUDIT_CALLBACK_OBJECT_ACE, 

433 SYSTEM_RESOURCE_ATTRIBUTE_ACE, 

434 SYSTEM_SCOPED_POLICY_ID_ACE 

435] 

436 

437# A dict of all the ACE types indexed by their type number 

438ACE_TYPE_MAP = {ace.ACE_TYPE: ace for ace in ACE_TYPES} 

439 

440""" 

441ACL as described in 2.4.5 

442https://msdn.microsoft.com/en-us/library/cc230297.aspx 

443""" 

444class ACL(Structure): 

445 structure = ( 

446 ('AclRevision', 'B'), 

447 ('Sbz1', 'B'), 

448 ('AclSize', '<H'), 

449 ('AceCount', '<H'), 

450 ('Sbz2', '<H'), 

451 # Virtual field to calculate data length from AclSize 

452 ('DataLen', '_-Data', 'self["AclSize"]-8'), 

453 ('Data', ':'), 

454 ) 

455 

456 def fromString(self, data): 

457 self.aces = [] 

458 Structure.fromString(self, data) 

459 for i in range(self['AceCount']): 

460 # If we don't have any data left, return 

461 if len(self['Data']) == 0: 

462 raise Exception("ACL header indicated there are more ACLs to unpack, but there is no more data") 

463 ace = ACE(data=self['Data']) 

464 self.aces.append(ace) 

465 self['Data'] = self['Data'][ace['AceSize']:] 

466 self['Data'] = self.aces 

467 

468 def getData(self): 

469 self['AceCount'] = len(self.aces) 

470 # We modify the data field to be able to use the 

471 # parent class parsing 

472 self['Data'] = b''.join([ace.getData() for ace in self.aces]) 

473 self['AclSize'] = len(self['Data'])+8 # Header size (8 bytes) is included 

474 data = Structure.getData(self) 

475 # Put the ACEs back in data 

476 self['Data'] = self.aces 

477 return data 

478 

479""" 

480objectClass mapping to GUID for some common classes (index is the ldapDisplayName). 

481Reference: 

482 https://msdn.microsoft.com/en-us/library/ms680938(v=vs.85).aspx 

483Can also be queried from the Schema 

484""" 

485OBJECTTYPE_GUID_MAP = { 

486 b'group': 'bf967a9c-0de6-11d0-a285-00aa003049e2', 

487 b'domain': '19195a5a-6da0-11d0-afd3-00c04fd930c9', 

488 b'organizationalUnit': 'bf967aa5-0de6-11d0-a285-00aa003049e2', 

489 b'user': 'bf967aba-0de6-11d0-a285-00aa003049e2', 

490 b'groupPolicyContainer': 'f30e3bc2-9ff0-11d1-b603-0000f80367c1' 

491}