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# SPNEGO functions used by SMB, SMB2/3 and DCERPC 

11# 

12# Author: 

13# Alberto Solino (@agsolino) 

14# 

15 

16from __future__ import division 

17from __future__ import print_function 

18from struct import pack, unpack, calcsize 

19 

20############### GSS Stuff ################ 

21GSS_API_SPNEGO_UUID = b'\x2b\x06\x01\x05\x05\x02' 

22ASN1_SEQUENCE = 0x30 

23ASN1_AID = 0x60 

24ASN1_OID = 0x06 

25ASN1_OCTET_STRING = 0x04 

26ASN1_MECH_TYPE = 0xa0 

27ASN1_MECH_TOKEN = 0xa2 

28ASN1_SUPPORTED_MECH = 0xa1 

29ASN1_RESPONSE_TOKEN = 0xa2 

30ASN1_ENUMERATED = 0x0a 

31MechTypes = { 

32b'+\x06\x01\x04\x01\x827\x02\x02\n': 'NTLMSSP - Microsoft NTLM Security Support Provider', 

33b'*\x86H\x82\xf7\x12\x01\x02\x02': 'MS KRB5 - Microsoft Kerberos 5', 

34b'*\x86H\x86\xf7\x12\x01\x02\x02': 'KRB5 - Kerberos 5', 

35b'*\x86H\x86\xf7\x12\x01\x02\x02\x03': 'KRB5 - Kerberos 5 - User to User', 

36b'\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x1e': 'NEGOEX - SPNEGO Extended Negotiation Security Mechanism' 

37} 

38 

39TypesMech = dict((v,k) for k, v in MechTypes.items()) 

40 

41def asn1encode(data = ''): 

42 #res = asn1.SEQUENCE(str).encode() 

43 #import binascii 

44 #print '\nalex asn1encode str: %s\n' % binascii.hexlify(str) 

45 if 0 <= len(data) <= 0x7F: 

46 res = pack('B', len(data)) + data 

47 elif 0x80 <= len(data) <= 0xFF: 47 ↛ 48line 47 didn't jump to line 48, because the condition on line 47 was never true

48 res = pack('BB', 0x81, len(data)) + data 

49 elif 0x100 <= len(data) <= 0xFFFF: 49 ↛ 51line 49 didn't jump to line 51, because the condition on line 49 was never false

50 res = pack('!BH', 0x82, len(data)) + data 

51 elif 0x10000 <= len(data) <= 0xffffff: 

52 res = pack('!BBH', 0x83, len(data) >> 16, len(data) & 0xFFFF) + data 

53 elif 0x1000000 <= len(data) <= 0xffffffff: 

54 res = pack('!BL', 0x84, len(data)) + data 

55 else: 

56 raise Exception('Error in asn1encode') 

57 return res 

58 

59def asn1decode(data = ''): 

60 len1 = unpack('B', data[:1])[0] 

61 data = data[1:] 

62 if len1 == 0x81: 

63 pad = calcsize('B') 

64 len2 = unpack('B',data[:pad])[0] 

65 data = data[pad:] 

66 ans = data[:len2] 

67 elif len1 == 0x82: 67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true

68 pad = calcsize('H') 

69 len2 = unpack('!H', data[:pad])[0] 

70 data = data[pad:] 

71 ans = data[:len2] 

72 elif len1 == 0x83: 72 ↛ 73line 72 didn't jump to line 73, because the condition on line 72 was never true

73 pad = calcsize('B') + calcsize('!H') 

74 len2, len3 = unpack('!BH', data[:pad]) 

75 data = data[pad:] 

76 ans = data[:len2 << 16 + len3] 

77 elif len1 == 0x84: 77 ↛ 78line 77 didn't jump to line 78, because the condition on line 77 was never true

78 pad = calcsize('!L') 

79 len2 = unpack('!L', data[:pad])[0] 

80 data = data[pad:] 

81 ans = data[:len2] 

82 # 1 byte length, string <= 0x7F 

83 else: 

84 pad = 0 

85 ans = data[:len1] 

86 return ans, len(ans)+pad+1 

87 

88class GSSAPI: 

89# Generic GSSAPI Header Format  

90 def __init__(self, data = None): 

91 self.fields = {} 

92 self['UUID'] = GSS_API_SPNEGO_UUID 

93 if data: 93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true

94 self.fromString(data) 

95 pass 

96 

97 def __setitem__(self,key,value): 

98 self.fields[key] = value 

99 

100 def __getitem__(self, key): 

101 return self.fields[key] 

102 

103 def __delitem__(self, key): 

104 del self.fields[key] 

105 

106 def __len__(self): 

107 return len(self.getData()) 

108 

109 def __str__(self): 

110 return len(self.getData()) 

111 

112 def fromString(self, data = None): 

113 # Manual parse of the GSSAPI Header Format 

114 # It should be something like 

115 # AID = 0x60 TAG, BER Length 

116 # OID = 0x06 TAG 

117 # GSSAPI OID 

118 # UUID data (BER Encoded) 

119 # Payload 

120 next_byte = unpack('B',data[:1])[0] 

121 if next_byte != ASN1_AID: 

122 raise Exception('Unknown AID=%x' % next_byte) 

123 data = data[1:] 

124 decode_data, total_bytes = asn1decode(data) 

125 # Now we should have a OID tag 

126 next_byte = unpack('B',decode_data[:1])[0] 

127 if next_byte != ASN1_OID: 

128 raise Exception('OID tag not found %x' % next_byte) 

129 decode_data = decode_data[1:] 

130 # Now the OID contents, should be SPNEGO UUID 

131 uuid, total_bytes = asn1decode(decode_data) 

132 self['OID'] = uuid 

133 # the rest should be the data 

134 self['Payload'] = decode_data[total_bytes:] 

135 #pass 

136 

137 def dump(self): 

138 for i in list(self.fields.keys()): 

139 print("%s: {%r}" % (i,self[i])) 

140 

141 def getData(self): 

142 ans = pack('B',ASN1_AID) 

143 ans += asn1encode( 

144 pack('B',ASN1_OID) + 

145 asn1encode(self['UUID']) + 

146 self['Payload'] ) 

147 return ans 

148 

149class SPNEGO_NegTokenResp: 

150 # https://tools.ietf.org/html/rfc4178#page-9 

151 # NegTokenResp ::= SEQUENCE { 

152 # negState [0] ENUMERATED { 

153 # accept-completed (0), 

154 # accept-incomplete (1), 

155 # reject (2), 

156 # request-mic (3) 

157 # } OPTIONAL, 

158 # -- REQUIRED in the first reply from the target 

159 # supportedMech [1] MechType OPTIONAL, 

160 # -- present only in the first reply from the target 

161 # responseToken [2] OCTET STRING OPTIONAL, 

162 # mechListMIC [3] OCTET STRING OPTIONAL, 

163 # ... 

164 # } 

165 # This structure is not prepended by a GSS generic header! 

166 SPNEGO_NEG_TOKEN_RESP = 0xa1 

167 SPNEGO_NEG_TOKEN_TARG = 0xa0 

168 

169 def __init__(self, data = None): 

170 self.fields = {} 

171 if data: 

172 self.fromString(data) 

173 pass 

174 

175 def __setitem__(self,key,value): 

176 self.fields[key] = value 

177 

178 def __getitem__(self, key): 

179 return self.fields[key] 

180 

181 def __delitem__(self, key): 

182 del self.fields[key] 

183 

184 def __len__(self): 

185 return len(self.getData()) 

186 

187 def __str__(self): 

188 return self.getData() 

189 

190 def fromString(self, data = 0): 

191 payload = data 

192 next_byte = unpack('B', payload[:1])[0] 

193 if next_byte != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 193 ↛ 194line 193 didn't jump to line 194, because the condition on line 193 was never true

194 raise Exception('NegTokenResp not found %x' % next_byte) 

195 payload = payload[1:] 

196 decode_data, total_bytes = asn1decode(payload) 

197 next_byte = unpack('B', decode_data[:1])[0] 

198 if next_byte != ASN1_SEQUENCE: 198 ↛ 199line 198 didn't jump to line 199, because the condition on line 198 was never true

199 raise Exception('SEQUENCE tag not found %x' % next_byte) 

200 decode_data = decode_data[1:] 

201 decode_data, total_bytes = asn1decode(decode_data) 

202 next_byte = unpack('B',decode_data[:1])[0] 

203 

204 if next_byte != ASN1_MECH_TYPE: 204 ↛ 206line 204 didn't jump to line 206, because the condition on line 204 was never true

205 # MechType not found, could be an AUTH answer 

206 if next_byte != ASN1_RESPONSE_TOKEN: 

207 raise Exception('MechType/ResponseToken tag not found %x' % next_byte) 

208 else: 

209 decode_data2 = decode_data[1:] 

210 decode_data2, total_bytes = asn1decode(decode_data2) 

211 next_byte = unpack('B', decode_data2[:1])[0] 

212 if next_byte != ASN1_ENUMERATED: 212 ↛ 213line 212 didn't jump to line 213, because the condition on line 212 was never true

213 raise Exception('Enumerated tag not found %x' % next_byte) 

214 item, total_bytes2 = asn1decode(decode_data2[1:]) 

215 self['NegState'] = item 

216 decode_data = decode_data[1:] 

217 decode_data = decode_data[total_bytes:] 

218 

219 # Do we have more data? 

220 if len(decode_data) == 0: 220 ↛ 221line 220 didn't jump to line 221, because the condition on line 220 was never true

221 return 

222 

223 next_byte = unpack('B', decode_data[:1])[0] 

224 if next_byte != ASN1_SUPPORTED_MECH: 224 ↛ 225line 224 didn't jump to line 225, because the condition on line 224 was never true

225 if next_byte != ASN1_RESPONSE_TOKEN: 

226 raise Exception('Supported Mech/ResponseToken tag not found %x' % next_byte) 

227 else: 

228 decode_data2 = decode_data[1:] 

229 decode_data2, total_bytes = asn1decode(decode_data2) 

230 next_byte = unpack('B', decode_data2[:1])[0] 

231 if next_byte != ASN1_OID: 231 ↛ 232line 231 didn't jump to line 232, because the condition on line 231 was never true

232 raise Exception('OID tag not found %x' % next_byte) 

233 decode_data2 = decode_data2[1:] 

234 item, total_bytes2 = asn1decode(decode_data2) 

235 self['SupportedMech'] = item 

236 

237 decode_data = decode_data[1:] 

238 decode_data = decode_data[total_bytes:] 

239 next_byte = unpack('B', decode_data[:1])[0] 

240 if next_byte != ASN1_RESPONSE_TOKEN: 240 ↛ 241line 240 didn't jump to line 241, because the condition on line 240 was never true

241 raise Exception('Response token tag not found %x' % next_byte) 

242 

243 decode_data = decode_data[1:] 

244 decode_data, total_bytes = asn1decode(decode_data) 

245 next_byte = unpack('B', decode_data[:1])[0] 

246 if next_byte != ASN1_OCTET_STRING: 246 ↛ 247line 246 didn't jump to line 247, because the condition on line 246 was never true

247 raise Exception('Octet string token tag not found %x' % next_byte) 

248 decode_data = decode_data[1:] 

249 decode_data, total_bytes = asn1decode(decode_data) 

250 self['ResponseToken'] = decode_data 

251 

252 def dump(self): 

253 for i in list(self.fields.keys()): 

254 print("%s: {%r}" % (i,self[i])) 

255 def getData(self): 

256 ans = pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP) 

257 if 'NegState' in self.fields and 'SupportedMech' in self.fields and 'ResponseToken' in self.fields: 257 ↛ 259line 257 didn't jump to line 259, because the condition on line 257 was never true

258 # Server resp 

259 ans += asn1encode( 

260 pack('B', ASN1_SEQUENCE) + 

261 asn1encode( 

262 pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 

263 asn1encode( 

264 pack('B',ASN1_ENUMERATED) + 

265 asn1encode( self['NegState'] )) + 

266 pack('B',ASN1_SUPPORTED_MECH) + 

267 asn1encode( 

268 pack('B',ASN1_OID) + 

269 asn1encode(self['SupportedMech'])) + 

270 pack('B',ASN1_RESPONSE_TOKEN ) + 

271 asn1encode( 

272 pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken'])))) 

273 elif 'NegState' in self.fields and 'SupportedMech' in self.fields: 273 ↛ 275line 273 didn't jump to line 275, because the condition on line 273 was never true

274 # Server resp 

275 ans += asn1encode( 

276 pack('B', ASN1_SEQUENCE) + 

277 asn1encode( 

278 pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 

279 asn1encode( 

280 pack('B',ASN1_ENUMERATED) + 

281 asn1encode( self['NegState'] )) + 

282 pack('B',ASN1_SUPPORTED_MECH) + 

283 asn1encode( 

284 pack('B',ASN1_OID) + 

285 asn1encode(self['SupportedMech'])))) 

286 elif 'NegState' in self.fields: 286 ↛ 288line 286 didn't jump to line 288, because the condition on line 286 was never true

287 # Server resp 

288 ans += asn1encode( 

289 pack('B', ASN1_SEQUENCE) + 

290 asn1encode( 

291 pack('B', SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 

292 asn1encode( 

293 pack('B',ASN1_ENUMERATED) + 

294 asn1encode( self['NegState'] )))) 

295 else: 

296 # Client resp 

297 ans += asn1encode( 

298 pack('B', ASN1_SEQUENCE) + 

299 asn1encode( 

300 pack('B', ASN1_RESPONSE_TOKEN) + 

301 asn1encode( 

302 pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken'])))) 

303 return ans 

304 

305class SPNEGO_NegTokenInit(GSSAPI): 

306 # https://tools.ietf.org/html/rfc4178#page-8 

307 # NegTokeInit :: = SEQUENCE { 

308 # mechTypes [0] MechTypeList, 

309 # reqFlags [1] ContextFlags OPTIONAL, 

310 # mechToken [2] OCTET STRING OPTIONAL, 

311 # mechListMIC [3] OCTET STRING OPTIONAL, 

312 # } 

313 SPNEGO_NEG_TOKEN_INIT = 0xa0 

314 def fromString(self, data = 0): 

315 GSSAPI.fromString(self, data) 

316 payload = self['Payload'] 

317 next_byte = unpack('B', payload[:1])[0] 

318 if next_byte != SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT: 

319 raise Exception('NegTokenInit not found %x' % next_byte) 

320 payload = payload[1:] 

321 decode_data, total_bytes = asn1decode(payload) 

322 # Now we should have a SEQUENCE Tag 

323 next_byte = unpack('B', decode_data[:1])[0] 

324 if next_byte != ASN1_SEQUENCE: 

325 raise Exception('SEQUENCE tag not found %x' % next_byte) 

326 decode_data = decode_data[1:] 

327 decode_data, total_bytes2 = asn1decode(decode_data) 

328 next_byte = unpack('B',decode_data[:1])[0] 

329 if next_byte != ASN1_MECH_TYPE: 

330 raise Exception('MechType tag not found %x' % next_byte) 

331 decode_data = decode_data[1:] 

332 remaining_data = decode_data 

333 decode_data, total_bytes3 = asn1decode(decode_data) 

334 next_byte = unpack('B', decode_data[:1])[0] 

335 if next_byte != ASN1_SEQUENCE: 

336 raise Exception('SEQUENCE tag not found %x' % next_byte) 

337 decode_data = decode_data[1:] 

338 decode_data, total_bytes4 = asn1decode(decode_data) 

339 # And finally we should have the MechTypes 

340 self['MechTypes'] = [] 

341 while decode_data: 

342 next_byte = unpack('B', decode_data[:1])[0] 

343 if next_byte != ASN1_OID: 

344 # Not a valid OID, there must be something else we won't unpack 

345 break 

346 decode_data = decode_data[1:] 

347 item, total_bytes = asn1decode(decode_data) 

348 self['MechTypes'].append(item) 

349 decode_data = decode_data[total_bytes:] 

350 

351 # Do we have MechTokens as well? 

352 decode_data = remaining_data[total_bytes3:] 

353 if len(decode_data) > 0: 

354 next_byte = unpack('B', decode_data[:1])[0] 

355 if next_byte == ASN1_MECH_TOKEN: 

356 # We have tokens in here! 

357 decode_data = decode_data[1:] 

358 decode_data, total_bytes = asn1decode(decode_data) 

359 next_byte = unpack('B', decode_data[:1])[0] 

360 if next_byte == ASN1_OCTET_STRING: 

361 decode_data = decode_data[1:] 

362 decode_data, total_bytes = asn1decode(decode_data) 

363 self['MechToken'] = decode_data 

364 

365 def getData(self): 

366 mechTypes = b'' 

367 for i in self['MechTypes']: 

368 mechTypes += pack('B', ASN1_OID) 

369 mechTypes += asn1encode(i) 

370 

371 mechToken = b'' 

372 # Do we have tokens to send? 

373 if 'MechToken' in self.fields: 373 ↛ 378line 373 didn't jump to line 378, because the condition on line 373 was never false

374 mechToken = pack('B', ASN1_MECH_TOKEN) + asn1encode( 

375 pack('B', ASN1_OCTET_STRING) + asn1encode( 

376 self['MechToken'])) 

377 

378 ans = pack('B',SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT) 

379 ans += asn1encode( 

380 pack('B', ASN1_SEQUENCE) + 

381 asn1encode( 

382 pack('B', ASN1_MECH_TYPE) + 

383 asn1encode( 

384 pack('B', ASN1_SEQUENCE) + 

385 asn1encode(mechTypes)) + mechToken )) 

386 

387 

388 self['Payload'] = ans 

389 return GSSAPI.getData(self)