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) 2018 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# RFC 1964 Partial Implementation 

11# RFC 4757 Partial Implementation 

12# RFC 4121 Partial Implementation 

13# RFC 3962 Partial Implementation 

14# 

15# Author: 

16# Alberto Solino (@agsolino) 

17# 

18import struct 

19import random 

20import string 

21from six import b 

22 

23from Cryptodome.Hash import HMAC, MD5 

24from Cryptodome.Cipher import ARC4 

25 

26from impacket.structure import Structure 

27from impacket.krb5 import constants, crypto 

28 

29# Our random number generator 

30try: 

31 rand = random.SystemRandom() 

32except NotImplementedError: 

33 rand = random 

34 pass 

35 

36# Constants 

37GSS_C_DCE_STYLE = 0x1000 

38GSS_C_DELEG_FLAG = 1 

39GSS_C_MUTUAL_FLAG = 2 

40GSS_C_REPLAY_FLAG = 4 

41GSS_C_SEQUENCE_FLAG = 8 

42GSS_C_CONF_FLAG = 0x10 

43GSS_C_INTEG_FLAG = 0x20 

44 

45# Mic Semantics 

46GSS_HMAC = 0x11 

47# Wrap Semantics 

48GSS_RC4 = 0x10 

49 

50# 2. Key Derivation for Per-Message Tokens 

51KG_USAGE_ACCEPTOR_SEAL = 22 

52KG_USAGE_ACCEPTOR_SIGN = 23 

53KG_USAGE_INITIATOR_SEAL = 24 

54KG_USAGE_INITIATOR_SIGN = 25 

55 

56KRB5_AP_REQ = struct.pack('<H', 0x1) 

57 

58# 1.1.1. Initial Token - Checksum field 

59class CheckSumField(Structure): 

60 structure = ( 

61 ('Lgth','<L=16'), 

62 ('Bnd','16s=b""'), 

63 ('Flags','<L=0'), 

64 ) 

65 

66def GSSAPI(cipher): 

67 if cipher.enctype == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true

68 return GSSAPI_AES256() 

69 if cipher.enctype == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: 69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true

70 return GSSAPI_AES128() 

71 elif cipher.enctype == constants.EncryptionTypes.rc4_hmac.value: 71 ↛ 74line 71 didn't jump to line 74, because the condition on line 71 was never false

72 return GSSAPI_RC4() 

73 else: 

74 raise Exception('Unsupported etype 0x%x' % cipher.enctype) 

75 

76# 7.2. GSS-API MIC Semantics 

77class GSSAPI_RC4: 

78 # 1.2.1. Per-message Tokens - MIC 

79 class MIC(Structure): 

80 structure = ( 

81 ('TOK_ID','<H=0x0101'), 

82 ('SGN_ALG','<H=0'), 

83 ('Filler','<L=0xffffffff'), 

84 ('SND_SEQ','8s=b""'), 

85 ('SGN_CKSUM','8s=b""'), 

86 ) 

87 

88 # 1.2.2. Per-message Tokens - Wrap 

89 class WRAP(Structure): 

90 structure = ( 

91 ('TOK_ID','<H=0x0102'), 

92 ('SGN_ALG','<H=0'), 

93 ('SEAL_ALG','<H=0'), 

94 ('Filler','<H=0xffff'), 

95 ('SND_SEQ','8s=b""'), 

96 ('SGN_CKSUM','8s=b""'), 

97 ('Confounder','8s=b""'), 

98 ) 

99 

100 def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'): 

101 GSS_GETMIC_HEADER = b'\x60\x23\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02' 

102 token = self.MIC() 

103 

104 # Let's pad the data 

105 pad = (4 - (len(data) % 4)) & 0x3 

106 padStr = b(chr(pad)) * pad 

107 data += padStr 

108 

109 token['SGN_ALG'] = GSS_HMAC 

110 if direction == 'init': 110 ↛ 113line 110 didn't jump to line 113, because the condition on line 110 was never false

111 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4 

112 else: 

113 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4 

114 

115 Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest() 

116 Sgn_Cksum = MD5.new( struct.pack('<L',15) + token.getData()[:8] + data).digest() 

117 Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest() 

118 token['SGN_CKSUM'] = Sgn_Cksum[:8] 

119 

120 Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest() 

121 Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest() 

122 token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ']) 

123 finalData = GSS_GETMIC_HEADER + token.getData() 

124 return finalData 

125 

126 def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None): 

127 # Damn inacurate RFC, useful info from here 

128 # https://social.msdn.microsoft.com/Forums/en-US/fb98e8f4-e697-4652-bcb7-604e027e14cc/gsswrap-token-size-kerberos-and-rc4hmac?forum=os_windowsprotocols 

129 # and here 

130 # http://www.rfc-editor.org/errata_search.php?rfc=4757 

131 GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02' 

132 token = self.WRAP() 

133 

134 # Let's pad the data 

135 pad = (8 - (len(data) % 8)) & 0x7 

136 padStr = b(chr(pad)) * pad 

137 data += padStr 

138 

139 token['SGN_ALG'] = GSS_HMAC 

140 token['SEAL_ALG'] = GSS_RC4 

141 

142 if direction == 'init': 142 ↛ 145line 142 didn't jump to line 145, because the condition on line 142 was never false

143 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\x00'*4 

144 else: 

145 token['SND_SEQ'] = struct.pack('>L', sequenceNumber) + b'\xff'*4 

146 

147 # Random confounder :) 

148 token['Confounder'] = b(''.join([rand.choice(string.ascii_letters) for _ in range(8)])) 

149 

150 Ksign = HMAC.new(sessionKey.contents, b'signaturekey\0', MD5).digest() 

151 Sgn_Cksum = MD5.new(struct.pack('<L',13) + token.getData()[:8] + token['Confounder'] + data).digest() 

152 

153 Klocal = bytearray() 

154 from builtins import bytes 

155 for n in bytes(sessionKey.contents): 

156 Klocal.append( n ^ 0xF0) 

157 

158 Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest() 

159 Kcrypt = HMAC.new(Kcrypt,struct.pack('>L', sequenceNumber), MD5).digest() 

160 

161 Sgn_Cksum = HMAC.new(Ksign, Sgn_Cksum, MD5).digest() 

162 

163 token['SGN_CKSUM'] = Sgn_Cksum[:8] 

164 

165 Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest() 

166 Kseq = HMAC.new(Kseq, token['SGN_CKSUM'], MD5).digest() 

167 

168 token['SND_SEQ'] = ARC4.new(Kseq).encrypt(token['SND_SEQ']) 

169 

170 if authData is not None: 

171 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER 

172 wrap = self.WRAP(authData[len(SEC_TRAILER()) + len(GSS_WRAP_HEADER):]) 

173 snd_seq = wrap['SND_SEQ'] 

174 

175 Kseq = HMAC.new(sessionKey.contents, struct.pack('<L',0), MD5).digest() 

176 Kseq = HMAC.new(Kseq, wrap['SGN_CKSUM'], MD5).digest() 

177 

178 snd_seq = ARC4.new(Kseq).encrypt(wrap['SND_SEQ']) 

179 

180 Kcrypt = HMAC.new(Klocal,struct.pack('<L',0), MD5).digest() 

181 Kcrypt = HMAC.new(Kcrypt,snd_seq[:4], MD5).digest() 

182 rc4 = ARC4.new(Kcrypt) 

183 cipherText = rc4.decrypt(token['Confounder'] + data)[8:] 

184 elif encrypt is True: 184 ↛ 189line 184 didn't jump to line 189, because the condition on line 184 was never false

185 rc4 = ARC4.new(Kcrypt) 

186 token['Confounder'] = rc4.encrypt(token['Confounder']) 

187 cipherText = rc4.encrypt(data) 

188 else: 

189 cipherText = data 

190 

191 finalData = GSS_WRAP_HEADER + token.getData() 

192 return cipherText, finalData 

193 

194 def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None): 

195 return self.GSS_Wrap(sessionKey, data, sequenceNumber, direction, encrypt, authData) 

196 

197class GSSAPI_AES(): 

198 checkSumProfile = None 

199 cipherType = None 

200 

201 class MIC(Structure): 

202 structure = ( 

203 ('TOK_ID','>H=0x0404'), 

204 ('Flags','B=0'), 

205 ('Filler0','B=0xff'), 

206 ('Filler','>L=0xffffffff'), 

207 ('SND_SEQ','8s=b""'), 

208 ('SGN_CKSUM','12s=b""'), 

209 ) 

210 

211 # 1.2.2. Per-message Tokens - Wrap 

212 class WRAP(Structure): 

213 structure = ( 

214 ('TOK_ID','>H=0x0504'), 

215 ('Flags','B=0'), 

216 ('Filler','B=0xff'), 

217 ('EC','>H=0'), 

218 ('RRC','>H=0'), 

219 ('SND_SEQ','8s=b""'), 

220 ) 

221 

222 def GSS_GetMIC(self, sessionKey, data, sequenceNumber, direction = 'init'): 

223 token = self.MIC() 

224 

225 # Let's pad the data 

226 pad = (4 - (len(data) % 4)) & 0x3 

227 padStr = chr(pad) * pad 

228 data += padStr 

229 

230 checkSumProfile = self.checkSumProfile() 

231 

232 token['Flags'] = 4 

233 token['SND_SEQ'] = struct.pack('>Q',sequenceNumber) 

234 token['SGN_CKSUM'] = checkSumProfile.checksum(sessionKey, KG_USAGE_INITIATOR_SIGN, data + token.getData()[:16]) 

235 

236 return token.getData() 

237 

238 def rotate(self, data, numBytes): 

239 numBytes %= len(data) 

240 left = len(data) - numBytes 

241 result = data[left:] + data[:left] 

242 return result 

243 

244 def unrotate(self, data, numBytes): 

245 numBytes %= len(data) 

246 result = data[numBytes:] + data[:numBytes] 

247 return result 

248 

249 def GSS_Wrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True): 

250 token = self.WRAP() 

251 

252 cipher = self.cipherType() 

253 

254 # Let's pad the data 

255 pad = (cipher.blocksize - (len(data) % cipher.blocksize)) & 15 

256 padStr = b'\xFF' * pad 

257 data += padStr 

258 

259 # The RRC field ([RFC4121] section 4.2.5) is 12 if no encryption is requested or 28 if encryption  

260 # is requested. The RRC field is chosen such that all the data can be encrypted in place. 

261 rrc = 28 

262 

263 token['Flags'] = 6 

264 token['EC'] = pad 

265 token['RRC'] = 0 

266 token['SND_SEQ'] = struct.pack('>Q',sequenceNumber) 

267 

268 cipherText = cipher.encrypt(sessionKey, KG_USAGE_INITIATOR_SEAL, data + token.getData(), None) 

269 token['RRC'] = rrc 

270 

271 cipherText = self.rotate(cipherText, token['RRC'] + token['EC']) 

272 

273 #nn = self.unrotate(cipherText, token['RRC'] + token['EC']) 

274 ret1 = cipherText[len(self.WRAP()) + token['RRC'] + token['EC']:] 

275 ret2 = token.getData() + cipherText[:len(self.WRAP()) + token['RRC'] + token['EC']] 

276 

277 return ret1, ret2 

278 

279 def GSS_Unwrap(self, sessionKey, data, sequenceNumber, direction = 'init', encrypt=True, authData=None): 

280 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER 

281 

282 cipher = self.cipherType() 

283 token = self.WRAP(authData[len(SEC_TRAILER()):]) 

284 

285 rotated = authData[len(self.WRAP())+len(SEC_TRAILER()):] + data 

286 

287 cipherText = self.unrotate(rotated, token['RRC'] + token['EC']) 

288 plainText = cipher.decrypt(sessionKey, KG_USAGE_ACCEPTOR_SEAL, cipherText) 

289 

290 return plainText[:-(token['EC']+len(self.WRAP()))], None 

291 

292class GSSAPI_AES256(GSSAPI_AES): 

293 checkSumProfile = crypto._SHA1AES256 

294 cipherType = crypto._AES256CTS 

295 

296class GSSAPI_AES128(GSSAPI_AES): 

297 checkSumProfile = crypto._SHA1AES128 

298 cipherType = crypto._AES128CTS