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# Author: Alberto Solino (@agsolino) 

8# 

9# Description: 

10# RFC 1964 Partial Implementation 

11# RFC 4757 Partial Implementation 

12# RFC 4121 Partial Implementation 

13# RFC 3962 Partial Implementation 

14 

15import struct 

16import random 

17import string 

18from six import b 

19 

20from Cryptodome.Hash import HMAC, MD5 

21from Cryptodome.Cipher import ARC4 

22 

23from impacket.structure import Structure 

24from impacket.krb5 import constants, crypto 

25 

26# Our random number generator 

27try: 

28 rand = random.SystemRandom() 

29except NotImplementedError: 

30 rand = random 

31 pass 

32 

33# Constants 

34GSS_C_DCE_STYLE = 0x1000 

35GSS_C_DELEG_FLAG = 1 

36GSS_C_MUTUAL_FLAG = 2 

37GSS_C_REPLAY_FLAG = 4 

38GSS_C_SEQUENCE_FLAG = 8 

39GSS_C_CONF_FLAG = 0x10 

40GSS_C_INTEG_FLAG = 0x20 

41 

42# Mic Semantics 

43GSS_HMAC = 0x11 

44# Wrap Semantics 

45GSS_RC4 = 0x10 

46 

47# 2. Key Derivation for Per-Message Tokens 

48KG_USAGE_ACCEPTOR_SEAL = 22 

49KG_USAGE_ACCEPTOR_SIGN = 23 

50KG_USAGE_INITIATOR_SEAL = 24 

51KG_USAGE_INITIATOR_SIGN = 25 

52 

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

54 

55# 1.1.1. Initial Token - Checksum field 

56class CheckSumField(Structure): 

57 structure = ( 

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

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

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

61 ) 

62 

63def GSSAPI(cipher): 

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

65 return GSSAPI_AES256() 

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

67 return GSSAPI_AES128() 

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

69 return GSSAPI_RC4() 

70 else: 

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

72 

73# 7.2. GSS-API MIC Semantics 

74class GSSAPI_RC4: 

75 # 1.2.1. Per-message Tokens - MIC 

76 class MIC(Structure): 

77 structure = ( 

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

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

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

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

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

83 ) 

84 

85 # 1.2.2. Per-message Tokens - Wrap 

86 class WRAP(Structure): 

87 structure = ( 

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

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

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

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

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

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

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

95 ) 

96 

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

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

99 token = self.MIC() 

100 

101 # Let's pad the data 

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

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

104 data += padStr 

105 

106 token['SGN_ALG'] = GSS_HMAC 

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

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

109 else: 

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

111 

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

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

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

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

116 

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

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

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

120 finalData = GSS_GETMIC_HEADER + token.getData() 

121 return finalData 

122 

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

124 # Damn inacurate RFC, useful info from here 

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

126 # and here 

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

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

129 token = self.WRAP() 

130 

131 # Let's pad the data 

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

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

134 data += padStr 

135 

136 token['SGN_ALG'] = GSS_HMAC 

137 token['SEAL_ALG'] = GSS_RC4 

138 

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

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

141 else: 

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

143 

144 # Random confounder :) 

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

146 

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

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

149 

150 Klocal = bytearray() 

151 from builtins import bytes 

152 for n in bytes(sessionKey.contents): 

153 Klocal.append( n ^ 0xF0) 

154 

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

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

157 

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

159 

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

161 

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

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

164 

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

166 

167 if authData is not None: 

168 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER 

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

170 snd_seq = wrap['SND_SEQ'] 

171 

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

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

174 

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

176 

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

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

179 rc4 = ARC4.new(Kcrypt) 

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

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

182 rc4 = ARC4.new(Kcrypt) 

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

184 cipherText = rc4.encrypt(data) 

185 else: 

186 cipherText = data 

187 

188 finalData = GSS_WRAP_HEADER + token.getData() 

189 return cipherText, finalData 

190 

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

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

193 

194class GSSAPI_AES(): 

195 checkSumProfile = None 

196 cipherType = None 

197 

198 class MIC(Structure): 

199 structure = ( 

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

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

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

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

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

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

206 ) 

207 

208 # 1.2.2. Per-message Tokens - Wrap 

209 class WRAP(Structure): 

210 structure = ( 

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

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

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

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

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

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

217 ) 

218 

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

220 token = self.MIC() 

221 

222 # Let's pad the data 

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

224 padStr = chr(pad) * pad 

225 data += padStr 

226 

227 checkSumProfile = self.checkSumProfile() 

228 

229 token['Flags'] = 4 

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

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

232 

233 return token.getData() 

234 

235 def rotate(self, data, numBytes): 

236 numBytes %= len(data) 

237 left = len(data) - numBytes 

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

239 return result 

240 

241 def unrotate(self, data, numBytes): 

242 numBytes %= len(data) 

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

244 return result 

245 

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

247 token = self.WRAP() 

248 

249 cipher = self.cipherType() 

250 

251 # Let's pad the data 

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

253 padStr = b'\xFF' * pad 

254 data += padStr 

255 

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

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

258 rrc = 28 

259 

260 token['Flags'] = 6 

261 token['EC'] = pad 

262 token['RRC'] = 0 

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

264 

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

266 token['RRC'] = rrc 

267 

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

269 

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

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

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

273 

274 return ret1, ret2 

275 

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

277 from impacket.dcerpc.v5.rpcrt import SEC_TRAILER 

278 

279 cipher = self.cipherType() 

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

281 

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

283 

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

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

286 

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

288 

289class GSSAPI_AES256(GSSAPI_AES): 

290 checkSumProfile = crypto._SHA1AES256 

291 cipherType = crypto._AES256CTS 

292 

293class GSSAPI_AES128(GSSAPI_AES): 

294 checkSumProfile = crypto._SHA1AES128 

295 cipherType = crypto._AES128CTS