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# Impacket - Collection of Python classes for working with network protocols. 

2# 

3# SECUREAUTH LABS. Copyright (C) 2020 SecureAuth Corporation. All rights reserved. 

4# 

5# This software is provided under a slightly modified version 

6# of the Apache Software License. See the accompanying LICENSE file 

7# for more information. 

8# 

9# Description: 

10# Structures and types used in LDAP 

11# Contains the Structures for the NT Security Descriptor (non-RPC format) and 

12# all ACL related structures 

13# 

14# Author: 

15# Dirk-jan Mollema (@_dirkjan) / Fox-IT (https://www.fox-it.com) 

16# 

17from struct import unpack, pack 

18from impacket.structure import Structure 

19 

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

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

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

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

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

25RECALC_ACE_SIZE = True 

26 

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

28class LDAP_SID_IDENTIFIER_AUTHORITY(Structure): 

29 structure = ( 

30 ('Value','6s'), 

31 ) 

32 

33class LDAP_SID(Structure): 

34 structure = ( 

35 ('Revision','<B'), 

36 ('SubAuthorityCount','<B'), 

37 ('IdentifierAuthority',':',LDAP_SID_IDENTIFIER_AUTHORITY), 

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

39 ('SubAuthority',':'), 

40 ) 

41 

42 def formatCanonical(self): 

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

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

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

46 return ans 

47 

48 def fromCanonical(self, canonical): 

49 items = canonical.split('-') 

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

51 self['IdentifierAuthority'] = LDAP_SID_IDENTIFIER_AUTHORITY() 

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

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

54 self['SubAuthority'] = b'' 

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

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

57 

58""" 

59Self-relative security descriptor as described in 2.4.6 

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

61""" 

62class SR_SECURITY_DESCRIPTOR(Structure): 

63 structure = ( 

64 ('Revision','c'), 

65 ('Sbz1','c'), 

66 ('Control','<H'), 

67 ('OffsetOwner','<L'), 

68 ('OffsetGroup','<L'), 

69 ('OffsetSacl','<L'), 

70 ('OffsetDacl','<L'), 

71 ('Sacl',':'), 

72 ('Dacl',':'), 

73 ('OwnerSid',':'), 

74 ('GroupSid',':'), 

75 ) 

76 

77 def fromString(self, data): 

78 Structure.fromString(self, data) 

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

80 # there are also flags indicating if they are present 

81 # TODO: parse those if it adds value 

82 if self['OffsetOwner'] != 0: 82 ↛ 85line 82 didn't jump to line 85, because the condition on line 82 was never false

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

84 else: 

85 self['OwnerSid'] = b'' 

86 

87 if self['OffsetGroup'] != 0: 87 ↛ 90line 87 didn't jump to line 90, because the condition on line 87 was never false

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

89 else: 

90 self['GroupSid'] = b'' 

91 

92 if self['OffsetSacl'] != 0: 92 ↛ 95line 92 didn't jump to line 95, because the condition on line 92 was never false

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

94 else: 

95 self['Sacl'] = b'' 

96 

97 if self['OffsetDacl'] != 0: 97 ↛ 100line 97 didn't jump to line 100, because the condition on line 97 was never false

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

99 else: 

100 self['Sacl'] = b'' 

101 

102 def getData(self): 

103 headerlen = 20 

104 # Reconstruct the security descriptor 

105 # flags are currently not set automatically 

106 # TODO: do this? 

107 datalen = 0 

108 if self['Sacl'] != b'': 108 ↛ 112line 108 didn't jump to line 112, because the condition on line 108 was never false

109 self['OffsetSacl'] = headerlen + datalen 

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

111 else: 

112 self['OffsetSacl'] = 0 

113 

114 if self['Dacl'] != b'': 114 ↛ 118line 114 didn't jump to line 118, because the condition on line 114 was never false

115 self['OffsetDacl'] = headerlen + datalen 

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

117 else: 

118 self['OffsetDacl'] = 0 

119 

120 if self['OwnerSid'] != b'': 120 ↛ 124line 120 didn't jump to line 124, because the condition on line 120 was never false

121 self['OffsetOwner'] = headerlen + datalen 

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

123 else: 

124 self['OffsetOwner'] = 0 

125 

126 if self['GroupSid'] != b'': 126 ↛ 130line 126 didn't jump to line 130, because the condition on line 126 was never false

127 self['OffsetGroup'] = headerlen + datalen 

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

129 else: 

130 self['OffsetGroup'] = 0 

131 return Structure.getData(self) 

132 

133""" 

134ACE as described in 2.4.4 

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

136""" 

137class ACE(Structure): 

138 # Flag constants 

139 CONTAINER_INHERIT_ACE = 0x02 

140 FAILED_ACCESS_ACE_FLAG = 0x80 

141 INHERIT_ONLY_ACE = 0x08 

142 INHERITED_ACE = 0x10 

143 NO_PROPAGATE_INHERIT_ACE = 0x04 

144 OBJECT_INHERIT_ACE = 0x01 

145 SUCCESSFUL_ACCESS_ACE_FLAG = 0x40 

146 

147 structure = ( 

148 # 

149 # ACE_HEADER as described in 2.4.4.1 

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

151 # 

152 ('AceType','B'), 

153 ('AceFlags','B'), 

154 ('AceSize','<H'), 

155 # Virtual field to calculate data length from AceSize 

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

157 # 

158 # ACE body, is parsed depending on the type 

159 # 

160 ('Ace',':') 

161 ) 

162 

163 def fromString(self, data): 

164 # This will parse the header 

165 Structure.fromString(self, data) 

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

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

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

169 

170 def getData(self): 

171 if RECALC_ACE_SIZE or 'AceSize' not in self.fields: 171 ↛ 173line 171 didn't jump to line 173, because the condition on line 171 was never false

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

173 if self['AceSize'] % 4 != 0: 173 ↛ 175line 173 didn't jump to line 175, because the condition on line 173 was never true

174 # Make sure the alignment is correct 

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

176 data = Structure.getData(self) 

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

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

179 # we create is identical to the original object 

180 if len(data) < self['AceSize']: 180 ↛ 181line 180 didn't jump to line 181, because the condition on line 180 was never true

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

182 return data 

183 

184 def hasFlag(self, flag): 

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

186 

187""" 

188ACCESS_MASK as described in 2.4.3 

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

190""" 

191class ACCESS_MASK(Structure): 

192 # Flag constants 

193 GENERIC_READ = 0x80000000 

194 GENERIC_WRITE = 0x40000000 

195 GENERIC_EXECUTE = 0x20000000 

196 GENERIC_ALL = 0x10000000 

197 MAXIMUM_ALLOWED = 0x02000000 

198 ACCESS_SYSTEM_SECURITY = 0x01000000 

199 SYNCHRONIZE = 0x00100000 

200 WRITE_OWNER = 0x00080000 

201 WRITE_DACL = 0x00040000 

202 READ_CONTROL = 0x00020000 

203 DELETE = 0x00010000 

204 

205 structure = ( 

206 ('Mask', '<L'), 

207 ) 

208 

209 def hasPriv(self, priv): 

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

211 

212 def setPriv(self, priv): 

213 self['Mask'] |= priv 

214 

215 def removePriv(self, priv): 

216 self['Mask'] ^= priv 

217 

218""" 

219ACCESS_ALLOWED_ACE as described in 2.4.4.2 

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

221""" 

222class ACCESS_ALLOWED_ACE(Structure): 

223 ACE_TYPE = 0x00 

224 structure = ( 

225 ('Mask', ':', ACCESS_MASK), 

226 ('Sid', ':', LDAP_SID) 

227 ) 

228 

229""" 

230ACCESS_ALLOWED_OBJECT_ACE as described in 2.4.4.3 

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

232""" 

233class ACCESS_ALLOWED_OBJECT_ACE(Structure): 

234 ACE_TYPE = 0x05 

235 

236 # Flag contstants 

237 ACE_OBJECT_TYPE_PRESENT = 0x01 

238 ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x02 

239 

240 # ACE type specific mask constants 

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

242 # for ACCESS_ALLOWED_ACE types 

243 ADS_RIGHT_DS_CONTROL_ACCESS = 0x00000100 

244 ADS_RIGHT_DS_CREATE_CHILD = 0x00000001 

245 ADS_RIGHT_DS_DELETE_CHILD = 0x00000002 

246 ADS_RIGHT_DS_READ_PROP = 0x00000010 

247 ADS_RIGHT_DS_WRITE_PROP = 0x00000020 

248 ADS_RIGHT_DS_SELF = 0x00000008 

249 

250 

251 structure = ( 

252 ('Mask', ':', ACCESS_MASK), 

253 ('Flags', '<L'), 

254 # Optional field 

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

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

257 # Optional field 

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

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

260 ('Sid', ':', LDAP_SID) 

261 ) 

262 

263 @staticmethod 

264 def checkInheritedObjectType(flags): 

265 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_INHERITED_OBJECT_TYPE_PRESENT: 

266 return 16 

267 return 0 

268 

269 @staticmethod 

270 def checkObjectType(flags): 

271 if flags & ACCESS_ALLOWED_OBJECT_ACE.ACE_OBJECT_TYPE_PRESENT: 

272 return 16 

273 return 0 

274 

275 def getData(self): 

276 # Set the correct flags 

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

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

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

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

281 return Structure.getData(self) 

282 

283 def hasFlag(self, flag): 

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

285 

286""" 

287ACCESS_DENIED_ACE as described in 2.4.4.4 

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

289Structure is identical to ACCESS_ALLOWED_ACE 

290""" 

291class ACCESS_DENIED_ACE(ACCESS_ALLOWED_ACE): 

292 ACE_TYPE = 0x01 

293 

294""" 

295ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.5 

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

297Structure is identical to ACCESS_ALLOWED_OBJECT_ACE 

298""" 

299class ACCESS_DENIED_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE): 

300 ACE_TYPE = 0x06 

301 

302""" 

303ACCESS_ALLOWED_CALLBACK_ACE as described in 2.4.4.6 

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

305""" 

306class ACCESS_ALLOWED_CALLBACK_ACE(Structure): 

307 ACE_TYPE = 0x09 

308 structure = ( 

309 ('Mask', ':', ACCESS_MASK), 

310 ('Sid', ':', LDAP_SID), 

311 ('ApplicationData', ':') 

312 ) 

313 

314""" 

315ACCESS_DENIED_OBJECT_ACE as described in 2.4.4.7 

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

317Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 

318""" 

319class ACCESS_DENIED_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 

320 ACE_TYPE = 0x0A 

321 

322""" 

323ACCESS_ALLOWED_CALLBACK_OBJECT_ACE as described in 2.4.4.8 

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

325""" 

326class ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_OBJECT_ACE): 

327 ACE_TYPE = 0x0B 

328 structure = ( 

329 ('Mask', ':', ACCESS_MASK), 

330 ('Flags', '<L'), 

331 # Optional field 

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

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

334 # Optional field 

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

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

337 ('Sid', ':', LDAP_SID), 

338 ('ApplicationData', ':') 

339 ) 

340 

341""" 

342ACCESS_DENIED_CALLBACK_OBJECT_ACE as described in 2.4.4.7 

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

344Structure is identical to ACCESS_ALLOWED_OBJECT_OBJECT_ACE 

345""" 

346class ACCESS_DENIED_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 

347 ACE_TYPE = 0x0C 

348 

349""" 

350SYSTEM_AUDIT_ACE as described in 2.4.4.10 

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

352Structure is identical to ACCESS_ALLOWED_ACE 

353""" 

354class SYSTEM_AUDIT_ACE(ACCESS_ALLOWED_ACE): 

355 ACE_TYPE = 0x02 

356 

357 

358""" 

359SYSTEM_AUDIT_OBJECT_ACE as described in 2.4.4.11 

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

361Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE 

362""" 

363class SYSTEM_AUDIT_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 

364 ACE_TYPE = 0x07 

365 

366 

367""" 

368SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.12 

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

370Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 

371""" 

372class SYSTEM_AUDIT_CALLBACK_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 

373 ACE_TYPE = 0x0D 

374 

375""" 

376SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.13 

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

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

379Lets keep it separate for now 

380""" 

381class SYSTEM_MANDATORY_LABEL_ACE(Structure): 

382 ACE_TYPE = 0x11 

383 structure = ( 

384 ('Mask', ':', ACCESS_MASK), 

385 ('Sid', ':', LDAP_SID) 

386 ) 

387 

388""" 

389SYSTEM_AUDIT_CALLBACK_ACE as described in 2.4.4.14 

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

391Structure is identical to ACCESS_ALLOWED_CALLBACK_OBJECT_ACE 

392""" 

393class SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(ACCESS_ALLOWED_CALLBACK_OBJECT_ACE): 

394 ACE_TYPE = 0x0F 

395 

396""" 

397SYSTEM_RESOURCE_ATTRIBUTE_ACE as described in 2.4.4.15 

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

399Structure is identical to ACCESS_ALLOWED_CALLBACK_ACE 

400The application data however is encoded in CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1 

401format as described in section 2.4.10.1 

402Todo: implement this substructure if needed 

403""" 

404class SYSTEM_RESOURCE_ATTRIBUTE_ACE(ACCESS_ALLOWED_CALLBACK_ACE): 

405 ACE_TYPE = 0x12 

406 

407 

408""" 

409SYSTEM_SCOPED_POLICY_ID_ACE as described in 2.4.4.16 

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

411Structure is identical to ACCESS_ALLOWED_ACE 

412The Sid data MUST match a CAPID of a CentralAccessPolicy 

413contained in the CentralAccessPoliciesList 

414Todo: implement this substructure if needed 

415Also the ACCESS_MASK must always be 0 

416""" 

417class SYSTEM_SCOPED_POLICY_ID_ACE(ACCESS_ALLOWED_ACE): 

418 ACE_TYPE = 0x13 

419 

420# All the ACE types in a list 

421ACE_TYPES = [ 

422 ACCESS_ALLOWED_ACE, 

423 ACCESS_ALLOWED_OBJECT_ACE, 

424 ACCESS_DENIED_ACE, 

425 ACCESS_DENIED_OBJECT_ACE, 

426 ACCESS_ALLOWED_CALLBACK_ACE, 

427 ACCESS_DENIED_CALLBACK_ACE, 

428 ACCESS_ALLOWED_CALLBACK_OBJECT_ACE, 

429 ACCESS_DENIED_CALLBACK_OBJECT_ACE, 

430 SYSTEM_AUDIT_ACE, 

431 SYSTEM_AUDIT_OBJECT_ACE, 

432 SYSTEM_AUDIT_CALLBACK_ACE, 

433 SYSTEM_MANDATORY_LABEL_ACE, 

434 SYSTEM_AUDIT_CALLBACK_OBJECT_ACE, 

435 SYSTEM_RESOURCE_ATTRIBUTE_ACE, 

436 SYSTEM_SCOPED_POLICY_ID_ACE 

437] 

438 

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

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

441 

442""" 

443ACL as described in 2.4.5 

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

445""" 

446class ACL(Structure): 

447 structure = ( 

448 ('AclRevision', 'B'), 

449 ('Sbz1', 'B'), 

450 ('AclSize', '<H'), 

451 ('AceCount', '<H'), 

452 ('Sbz2', '<H'), 

453 # Virtual field to calculate data length from AclSize 

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

455 ('Data', ':'), 

456 ) 

457 

458 def fromString(self, data): 

459 self.aces = [] 

460 Structure.fromString(self, data) 

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

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

463 if len(self['Data']) == 0: 463 ↛ 464line 463 didn't jump to line 464, because the condition on line 463 was never true

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

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

466 self.aces.append(ace) 

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

468 self['Data'] = self.aces 

469 

470 def getData(self): 

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

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

473 # parent class parsing 

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

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

476 data = Structure.getData(self) 

477 # Put the ACEs back in data 

478 self['Data'] = self.aces 

479 return data 

480 

481""" 

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

483Reference: 

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

485Can also be queried from the Schema 

486""" 

487OBJECTTYPE_GUID_MAP = { 

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

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

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

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

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

493}