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# Kerberos Keytab format implementation 

11# based on file format described at: 

12# https://repo.or.cz/w/krb5dissect.git/blob_plain/HEAD:/keytab.txt 

13# As the ccache implementation, pretty lame and quick 

14# Feel free to improve 

15# 

16# Author: 

17# Patrick Welzel (@kcirtapw) 

18# 

19from datetime import datetime 

20from enum import Enum 

21from six import b 

22 

23from struct import pack, unpack, calcsize 

24from binascii import hexlify 

25 

26from impacket.structure import Structure 

27from impacket import LOG 

28 

29 

30class Enctype(Enum): 

31 DES_CRC = 1 

32 DES_MD4 = 2 

33 DES_MD5 = 3 

34 DES3 = 16 

35 AES128 = 17 

36 AES256 = 18 

37 RC4 = 23 

38 

39 

40class CountedOctetString(Structure): 

41 """ 

42 Note: This is very similar to the CountedOctetString structure in ccache, except: 

43 * `length` is uint16 instead of uint32 

44 """ 

45 structure = ( 

46 ('length','!H=0'), 

47 ('_data','_-data','self["length"]'), 

48 ('data',':'), 

49 ) 

50 

51 def prettyPrint(self, indent=''): 

52 return "%s%s" % (indent, hexlify(self['data'])) 

53 

54 

55class KeyBlock(Structure): 

56 structure = ( 

57 ('keytype','!H=0'), 

58 ('keyvalue',':', CountedOctetString), 

59 ) 

60 

61 def prettyKeytype(self): 

62 try: 

63 return Enctype(self['keytype']).name 

64 except: 

65 return "UNKNOWN:0x%x" % (self['keytype']) 

66 

67 def hexlifiedValue(self): 

68 return hexlify(self['keyvalue']['data']) 

69 

70 def prettyPrint(self): 

71 return "(%s)%s" % (self.prettyKeytype(), self.hexlifiedValue()) 

72 

73 

74class KeytabPrincipal: 

75 """ 

76 Note: This is very similar to the principal structure in ccache, except: 

77 * `num_components` is just uint16 

78 * using other size type for CountedOctetString 

79 * `name_type` field follows the other fields behind. 

80 """ 

81 class PrincipalHeader1(Structure): 

82 structure = ( 

83 ('num_components', '!H=0'), 

84 ) 

85 

86 class PrincipalHeader2(Structure): 

87 structure = ( 

88 ('name_type', '!L=0'), 

89 ) 

90 

91 def __init__(self, data=None): 

92 self.components = [] 

93 self.realm = None 

94 if data is not None: 

95 self.header1 = self.PrincipalHeader1(data) 

96 data = data[len(self.header1):] 

97 self.realm = CountedOctetString(data) 

98 data = data[len(self.realm):] 

99 self.components = [] 

100 for component in range(self.header1['num_components']): 

101 comp = CountedOctetString(data) 

102 data = data[len(comp):] 

103 self.components.append(comp) 

104 self.header2 = self.PrincipalHeader2(data) 

105 else: 

106 self.header1 = self.PrincipalHeader1() 

107 self.header2 = self.PrincipalHeader2() 

108 

109 def __len__(self): 

110 totalLen = len(self.header1) + len(self.header2) + len(self.realm) 

111 for i in self.components: 

112 totalLen += len(i) 

113 return totalLen 

114 

115 def getData(self): 

116 data = self.header1.getData() + self.realm.getData() 

117 for component in self.components: 

118 data += component.getData() 

119 data += self.header2.getData() 

120 return data 

121 

122 def __str__(self): 

123 return self.getData() 

124 

125 def prettyPrint(self): 

126 principal = b'' 

127 for component in self.components: 

128 if isinstance(component['data'], bytes) is not True: 

129 component = b(component['data']) 

130 else: 

131 component = component['data'] 

132 principal += component + b'/' 

133 

134 principal = principal[:-1] 

135 if isinstance(self.realm['data'], bytes): 

136 realm = self.realm['data'] 

137 else: 

138 realm = b(self.realm['data']) 

139 principal += b'@' + realm 

140 return principal 

141 

142 

143class KeytabEntry: 

144 class KeytabEntryMainpart(Structure): 

145 """ 

146 keytab_entry { 

147 int32_t size; # wtf, signed size. what could possibly ... 

148 uint16_t num_components; /* sub 1 if version 0x501 */ |\ 

149 counted_octet_string realm; | \\ Keytab 

150 counted_octet_string components[num_components]; | / Princial 

151 uint32_t name_type; /* not present if version 0x501 */ |/ 

152 uint32_t timestamp; 

153 uint8_t vno8; 

154 keyblock key; 

155 uint32_t vno; /* only present if >= 4 bytes left in entry */ 

156 }; 

157 """ 

158 structure = ( 

159 ('size', '!l=0'), 

160 ('principal', ':', KeytabPrincipal), 

161 ('timestamp', '!L=0'), 

162 ('vno8', '!B=0'), 

163 ('keyblock', ':', KeyBlock), 

164 ) 

165 

166 def __init__(self, data=None): 

167 self.rest = b'' 

168 if data: 

169 self.main_part = self.KeytabEntryMainpart(data) 

170 self.size = abs(self.main_part['size']) + 4 # size field itself not included 

171 self.kvno = self.main_part['vno8'] 

172 self.deleted = self.main_part['size'] < 0 

173 len_main = len(self.main_part) 

174 if self.size > len_main: 

175 self.rest = data[len_main:self.size] 

176 if len(self.rest) >= 4 and \ 

177 self.rest[:4] != [0, 0, 0, 0]: # if "field" is present but all 0, it seems to gets ignored 

178 self.kvno = unpack('!L', self.rest[:4])[0] 

179 else: 

180 self.main_part = self.KeytabEntryMainpart() 

181 self.deleted = True 

182 self.size = len(self.main_part) 

183 self.kvno = 0 

184 

185 def __len__(self): 

186 return self.size 

187 

188 def getData(self): 

189 data = self.main_part.getData() 

190 if self.rest: 

191 data += self.rest 

192 return data 

193 

194 def prettyPrint(self, indent=''): 

195 if self.deleted: 

196 return "%s[DELETED]" % indent 

197 else: 

198 text = "%sPrincipal: %s\n" %(indent, self.main_part['principal'].prettyPrint()) 

199 text += "%sTimestamp: %s" % (indent, datetime.fromtimestamp(self.main_part['timestamp']).isoformat()) 

200 text += "\tKVNO: %i\n" % self.kvno 

201 text += "%sKey: %s" % (indent, self.main_part['keyblock'].prettyPrint()) 

202 #if self.rest: 

203 # text += "\n%sRest: %s" % (indent, self.rest) 

204 return text 

205 

206 

207class Keytab: 

208 

209 GetkeyEnctypePreference = (Enctype.AES256.value, 

210 Enctype.AES128.value, 

211 Enctype.RC4.value) 

212 

213 class MiniHeader(Structure): 

214 structure = ( 

215 ('file_format_version', '!H=0x0502'), 

216 ) 

217 

218 def __init__(self, data=None): 

219 self.miniHeader = None 

220 self.entries = [] 

221 if data is not None: 

222 self.miniHeader = self.MiniHeader(data) 

223 data = data[len(self.miniHeader):] 

224 while len(data): 

225 entry = KeytabEntry(data) 

226 self.entries.append(entry) 

227 data = data[len(entry):] 

228 

229 def getData(self): 

230 data = self.MiniHeader().getData() 

231 for entry in self.entries: 

232 data += entry.getData() 

233 return data 

234 

235 def getKey(self, principal, specificEncType=None, ignoreRealm=True): 

236 principal = b(principal.upper()) 

237 if ignoreRealm: 

238 principal = principal.split(b'@')[0] 

239 matching_keys = {} 

240 for entry in self.entries: 

241 entry_principal = entry.main_part['principal'].prettyPrint().upper() 

242 if entry_principal == principal or (ignoreRealm and entry_principal.split(b'@')[0] == principal): 

243 keytype = entry.main_part["keyblock"]["keytype"] 

244 if keytype == specificEncType: 

245 LOG.debug('Returning %s key for %s' % (entry.main_part['keyblock'].prettyKeytype(), 

246 entry.main_part['principal'].prettyPrint())) 

247 return entry.main_part["keyblock"] 

248 elif specificEncType is None: 

249 matching_keys[keytype] = entry 

250 

251 if specificEncType is None and matching_keys: 

252 for preference in self.GetkeyEnctypePreference: 

253 if preference in matching_keys: 

254 entry = matching_keys[preference] 

255 LOG.debug('Returning %s key for %s' % (entry.main_part['keyblock'].prettyKeytype(), 

256 entry.main_part['principal'].prettyPrint())) 

257 return entry.main_part["keyblock"] 

258 

259 LOG.debug('Principal %s not found in keytab' % principal) 

260 return None 

261 

262 @classmethod 

263 def loadFile(cls, fileName): 

264 f = open(fileName, 'rb') 

265 data = f.read() 

266 f.close() 

267 return cls(data) 

268 

269 @classmethod 

270 def loadKeysFromKeytab(cls, fileName, username, domain, options): 

271 keytab = Keytab.loadFile(fileName) 

272 keyblock = keytab.getKey("%s@%s" % (username, domain)) 

273 if keyblock: 

274 if keyblock["keytype"] == Enctype.AES256.value or keyblock["keytype"] == Enctype.AES128.value: 

275 options.aesKey = keyblock.hexlifiedValue() 

276 elif keyblock["keytype"] == Enctype.RC4.value: 

277 options.hashes= ':' + keyblock.hexlifiedValue().decode('ascii') 

278 else: 

279 LOG.warning("No matching key for SPN '%s' in given keytab found!", username) 

280 

281 

282 def saveFile(self, fileName): 

283 f = open(fileName, 'wb+') 

284 f.write(self.getData()) 

285 f.close() 

286 

287 def prettyPrint(self): 

288 print("Keytab Entries:") 

289 for i, entry in enumerate(self.entries): 

290 print(("[%d]" % i)) 

291 print(entry.prettyPrint('\t')) 

292 

293 

294if __name__ == '__main__': 294 ↛ 295line 294 didn't jump to line 295, because the condition on line 294 was never true

295 import sys 

296 keytab = Keytab.loadFile(sys.argv[1]) 

297 keytab.prettyPrint()