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# Author: Patrick Welzel (@kcirtapw) 

2# 

3# Description: 

4# Kerberos Keytab format implementation 

5# based on file format described at: 

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

7# As the ccache implementation, pretty lame and quick 

8# Feel free to improve 

9# 

10from datetime import datetime 

11from enum import Enum 

12from six import b 

13 

14from struct import pack, unpack, calcsize 

15from binascii import hexlify 

16 

17from impacket.structure import Structure 

18from impacket import LOG 

19 

20 

21class Enctype(Enum): 

22 DES_CRC = 1 

23 DES_MD4 = 2 

24 DES_MD5 = 3 

25 DES3 = 16 

26 AES128 = 17 

27 AES256 = 18 

28 RC4 = 23 

29 

30 

31class CountedOctetString(Structure): 

32 """ 

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

34 * `length` is uint16 instead of uint32 

35 """ 

36 structure = ( 

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

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

39 ('data',':'), 

40 ) 

41 

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

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

44 

45 

46class KeyBlock(Structure): 

47 structure = ( 

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

49 ('keyvalue',':', CountedOctetString), 

50 ) 

51 

52 def prettyKeytype(self): 

53 try: 

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

55 except: 

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

57 

58 def hexlifiedValue(self): 

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

60 

61 def prettyPrint(self): 

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

63 

64 

65class KeytabPrincipal: 

66 """ 

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

68 * `num_components` is just uint16 

69 * using other size type for CountedOctetString 

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

71 """ 

72 class PrincipalHeader1(Structure): 

73 structure = ( 

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

75 ) 

76 

77 class PrincipalHeader2(Structure): 

78 structure = ( 

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

80 ) 

81 

82 def __init__(self, data=None): 

83 self.components = [] 

84 self.realm = None 

85 if data is not None: 

86 self.header1 = self.PrincipalHeader1(data) 

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

88 self.realm = CountedOctetString(data) 

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

90 self.components = [] 

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

92 comp = CountedOctetString(data) 

93 data = data[len(comp):] 

94 self.components.append(comp) 

95 self.header2 = self.PrincipalHeader2(data) 

96 else: 

97 self.header1 = self.PrincipalHeader1() 

98 self.header2 = self.PrincipalHeader2() 

99 

100 def __len__(self): 

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

102 for i in self.components: 

103 totalLen += len(i) 

104 return totalLen 

105 

106 def getData(self): 

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

108 for component in self.components: 

109 data += component.getData() 

110 data += self.header2.getData() 

111 return data 

112 

113 def __str__(self): 

114 return self.getData() 

115 

116 def prettyPrint(self): 

117 principal = b'' 

118 for component in self.components: 

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

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

121 else: 

122 component = component['data'] 

123 principal += component + b'/' 

124 

125 principal = principal[:-1] 

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

127 realm = self.realm['data'] 

128 else: 

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

130 principal += b'@' + realm 

131 return principal 

132 

133 

134class KeytabEntry: 

135 class KeytabEntryMainpart(Structure): 

136 """ 

137 keytab_entry { 

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

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

140 counted_octet_string realm; | \ Keytab 

141 counted_octet_string components[num_components]; | / Princial 

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

143 uint32_t timestamp; 

144 uint8_t vno8; 

145 keyblock key; 

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

147 }; 

148 """ 

149 structure = ( 

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

151 ('principal', ':', KeytabPrincipal), 

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

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

154 ('keyblock', ':', KeyBlock), 

155 ) 

156 

157 def __init__(self, data=None): 

158 self.rest = b'' 

159 if data: 

160 self.main_part = self.KeytabEntryMainpart(data) 

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

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

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

164 len_main = len(self.main_part) 

165 if self.size > len_main: 

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

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

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

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

170 else: 

171 self.main_part = self.KeytabEntryMainpart() 

172 self.deleted = True 

173 self.size = len(self.main_part) 

174 self.kvno = 0 

175 

176 def __len__(self): 

177 return self.size 

178 

179 def getData(self): 

180 data = self.main_part.getData() 

181 if self.rest: 

182 data += self.rest 

183 return data 

184 

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

186 if self.deleted: 

187 return "%s[DELETED]" % indent 

188 else: 

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

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

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

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

193 #if self.rest: 

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

195 return text 

196 

197 

198class Keytab: 

199 

200 GetkeyEnctypePreference = (Enctype.AES256.value, 

201 Enctype.AES128.value, 

202 Enctype.RC4.value) 

203 

204 class MiniHeader(Structure): 

205 structure = ( 

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

207 ) 

208 

209 def __init__(self, data=None): 

210 self.miniHeader = None 

211 self.entries = [] 

212 if data is not None: 

213 self.miniHeader = self.MiniHeader(data) 

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

215 while len(data): 

216 entry = KeytabEntry(data) 

217 self.entries.append(entry) 

218 data = data[len(entry):] 

219 

220 def getData(self): 

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

222 for entry in self.entries: 

223 data += entry.getData() 

224 return data 

225 

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

227 principal = b(principal.upper()) 

228 if ignoreRealm: 

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

230 matching_keys = {} 

231 for entry in self.entries: 

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

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

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

235 if keytype == specificEncType: 

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

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

238 return entry.main_part["keyblock"] 

239 elif specificEncType is None: 

240 matching_keys[keytype] = entry 

241 

242 if specificEncType is None and matching_keys: 

243 for preference in self.GetkeyEnctypePreference: 

244 if preference in matching_keys: 

245 entry = matching_keys[preference] 

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

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

248 return entry.main_part["keyblock"] 

249 

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

251 return None 

252 

253 @classmethod 

254 def loadFile(cls, fileName): 

255 f = open(fileName, 'rb') 

256 data = f.read() 

257 f.close() 

258 return cls(data) 

259 

260 @classmethod 

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

262 keytab = Keytab.loadFile(fileName) 

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

264 if keyblock: 

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

266 options.aesKey = keyblock.hexlifiedValue() 

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

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

269 else: 

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

271 

272 

273 def saveFile(self, fileName): 

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

275 f.write(self.getData()) 

276 f.close() 

277 

278 def prettyPrint(self): 

279 print("Keytab Entries:") 

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

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

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

283 

284 

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

286 import sys 

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

288 keytab.prettyPrint()