1# Copyright (c) 2003-2016 CORE Security Technologies 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 (beto@coresecurity.com) 8# 9# Description: 10# SPNEGO functions used by SMB, SMB2/3 and DCERPC 11# 12 13from struct import pack, unpack, calcsize 14 15############### GSS Stuff ################ 16GSS_API_SPNEGO_UUID = '\x2b\x06\x01\x05\x05\x02' 17ASN1_SEQUENCE = 0x30 18ASN1_AID = 0x60 19ASN1_OID = 0x06 20ASN1_OCTET_STRING = 0x04 21ASN1_MECH_TYPE = 0xa0 22ASN1_MECH_TOKEN = 0xa2 23ASN1_SUPPORTED_MECH = 0xa1 24ASN1_RESPONSE_TOKEN = 0xa2 25ASN1_ENUMERATED = 0x0a 26MechTypes = { 27'+\x06\x01\x04\x01\x827\x02\x02\x1e': 'SNMPv2-SMI::enterprises.311.2.2.30', 28'+\x06\x01\x04\x01\x827\x02\x02\n': 'NTLMSSP - Microsoft NTLM Security Support Provider', 29'*\x86H\x82\xf7\x12\x01\x02\x02': 'MS KRB5 - Microsoft Kerberos 5', 30'*\x86H\x86\xf7\x12\x01\x02\x02': 'KRB5 - Kerberos 5', 31'*\x86H\x86\xf7\x12\x01\x02\x02\x03': 'KRB5 - Kerberos 5 - User to User' 32} 33TypesMech = dict((v,k) for k, v in MechTypes.iteritems()) 34 35def asn1encode(data = ''): 36 #res = asn1.SEQUENCE(str).encode() 37 #import binascii 38 #print '\nalex asn1encode str: %s\n' % binascii.hexlify(str) 39 if 0 <= len(data) <= 0x7F: 40 res = pack('B', len(data)) + data 41 elif 0x80 <= len(data) <= 0xFF: 42 res = pack('BB', 0x81, len(data)) + data 43 elif 0x100 <= len(data) <= 0xFFFF: 44 res = pack('!BH', 0x82, len(data)) + data 45 elif 0x10000 <= len(data) <= 0xffffff: 46 res = pack('!BBH', 0x83, len(data) >> 16, len(data) & 0xFFFF) + data 47 elif 0x1000000 <= len(data) <= 0xffffffff: 48 res = pack('!BL', 0x84, len(data)) + data 49 else: 50 raise Exception('Error in asn1encode') 51 return str(res) 52 53def asn1decode(data = ''): 54 len1 = unpack('B', data[:1])[0] 55 data = data[1:] 56 if len1 == 0x81: 57 pad = calcsize('B') 58 len2 = unpack('B',data[:pad])[0] 59 data = data[pad:] 60 ans = data[:len2] 61 elif len1 == 0x82: 62 pad = calcsize('H') 63 len2 = unpack('!H', data[:pad])[0] 64 data = data[pad:] 65 ans = data[:len2] 66 elif len1 == 0x83: 67 pad = calcsize('B') + calcsize('!H') 68 len2, len3 = unpack('!BH', data[:pad]) 69 data = data[pad:] 70 ans = data[:len2 << 16 + len3] 71 elif len1 == 0x84: 72 pad = calcsize('!L') 73 len2 = unpack('!L', data[:pad])[0] 74 data = data[pad:] 75 ans = data[:len2] 76 # 1 byte length, string <= 0x7F 77 else: 78 pad = 0 79 ans = data[:len1] 80 return ans, len(ans)+pad+1 81 82class GSSAPI: 83# Generic GSSAPI Header Format 84 def __init__(self, data = None): 85 self.fields = {} 86 self['UUID'] = GSS_API_SPNEGO_UUID 87 if data: 88 self.fromString(data) 89 pass 90 91 def __setitem__(self,key,value): 92 self.fields[key] = value 93 94 def __getitem__(self, key): 95 return self.fields[key] 96 97 def __delitem__(self, key): 98 del self.fields[key] 99 100 def __len__(self): 101 return len(self.getData()) 102 103 def __str__(self): 104 return len(self.getData()) 105 106 def fromString(self, data = None): 107 # Manual parse of the GSSAPI Header Format 108 # It should be something like 109 # AID = 0x60 TAG, BER Length 110 # OID = 0x06 TAG 111 # GSSAPI OID 112 # UUID data (BER Encoded) 113 # Payload 114 next_byte = unpack('B',data[:1])[0] 115 if next_byte != ASN1_AID: 116 raise Exception('Unknown AID=%x' % next_byte) 117 data = data[1:] 118 decode_data, total_bytes = asn1decode(data) 119 # Now we should have a OID tag 120 next_byte = unpack('B',decode_data[:1])[0] 121 if next_byte != ASN1_OID: 122 raise Exception('OID tag not found %x' % next_byte) 123 decode_data = decode_data[1:] 124 # Now the OID contents, should be SPNEGO UUID 125 uuid, total_bytes = asn1decode(decode_data) 126 self['OID'] = uuid 127 # the rest should be the data 128 self['Payload'] = decode_data[total_bytes:] 129 #pass 130 131 def dump(self): 132 for i in self.fields.keys(): 133 print "%s: {%r}" % (i,self[i]) 134 135 def getData(self): 136 ans = pack('B',ASN1_AID) 137 ans += asn1encode( 138 pack('B',ASN1_OID) + 139 asn1encode(self['UUID']) + 140 self['Payload'] ) 141 return ans 142 143class SPNEGO_NegTokenResp: 144 # http://tools.ietf.org/html/rfc4178#page-9 145 # NegTokenResp ::= SEQUENCE { 146 # negState [0] ENUMERATED { 147 # accept-completed (0), 148 # accept-incomplete (1), 149 # reject (2), 150 # request-mic (3) 151 # } OPTIONAL, 152 # -- REQUIRED in the first reply from the target 153 # supportedMech [1] MechType OPTIONAL, 154 # -- present only in the first reply from the target 155 # responseToken [2] OCTET STRING OPTIONAL, 156 # mechListMIC [3] OCTET STRING OPTIONAL, 157 # ... 158 # } 159 # This structure is not prepended by a GSS generic header! 160 SPNEGO_NEG_TOKEN_RESP = 0xa1 161 SPNEGO_NEG_TOKEN_TARG = 0xa0 162 163 def __init__(self, data = None): 164 self.fields = {} 165 if data: 166 self.fromString(data) 167 pass 168 169 def __setitem__(self,key,value): 170 self.fields[key] = value 171 172 def __getitem__(self, key): 173 return self.fields[key] 174 175 def __delitem__(self, key): 176 del self.fields[key] 177 178 def __len__(self): 179 return len(self.getData()) 180 181 def __str__(self): 182 return len(self.getData()) 183 184 def fromString(self, data = 0): 185 payload = data 186 next_byte = unpack('B', payload[:1])[0] 187 if next_byte != SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 188 raise Exception('NegTokenResp not found %x' % next_byte) 189 payload = payload[1:] 190 decode_data, total_bytes = asn1decode(payload) 191 next_byte = unpack('B', decode_data[:1])[0] 192 if next_byte != ASN1_SEQUENCE: 193 raise Exception('SEQUENCE tag not found %x' % next_byte) 194 decode_data = decode_data[1:] 195 decode_data, total_bytes = asn1decode(decode_data) 196 next_byte = unpack('B',decode_data[:1])[0] 197 198 if next_byte != ASN1_MECH_TYPE: 199 # MechType not found, could be an AUTH answer 200 if next_byte != ASN1_RESPONSE_TOKEN: 201 raise Exception('MechType/ResponseToken tag not found %x' % next_byte) 202 else: 203 decode_data2 = decode_data[1:] 204 decode_data2, total_bytes = asn1decode(decode_data2) 205 next_byte = unpack('B', decode_data2[:1])[0] 206 if next_byte != ASN1_ENUMERATED: 207 raise Exception('Enumerated tag not found %x' % next_byte) 208 item, total_bytes2 = asn1decode(decode_data) 209 self['NegResult'] = item 210 decode_data = decode_data[1:] 211 decode_data = decode_data[total_bytes:] 212 213 # Do we have more data? 214 if len(decode_data) == 0: 215 return 216 217 next_byte = unpack('B', decode_data[:1])[0] 218 if next_byte != ASN1_SUPPORTED_MECH: 219 if next_byte != ASN1_RESPONSE_TOKEN: 220 raise Exception('Supported Mech/ResponseToken tag not found %x' % next_byte) 221 else: 222 decode_data2 = decode_data[1:] 223 decode_data2, total_bytes = asn1decode(decode_data2) 224 next_byte = unpack('B', decode_data2[:1])[0] 225 if next_byte != ASN1_OID: 226 raise Exception('OID tag not found %x' % next_byte) 227 decode_data2 = decode_data2[1:] 228 item, total_bytes2 = asn1decode(decode_data2) 229 self['SupportedMech'] = item 230 231 decode_data = decode_data[1:] 232 decode_data = decode_data[total_bytes:] 233 next_byte = unpack('B', decode_data[:1])[0] 234 if next_byte != ASN1_RESPONSE_TOKEN: 235 raise Exception('Response token tag not found %x' % next_byte) 236 237 decode_data = decode_data[1:] 238 decode_data, total_bytes = asn1decode(decode_data) 239 next_byte = unpack('B', decode_data[:1])[0] 240 if next_byte != ASN1_OCTET_STRING: 241 raise Exception('Octet string token tag not found %x' % next_byte) 242 decode_data = decode_data[1:] 243 decode_data, total_bytes = asn1decode(decode_data) 244 self['ResponseToken'] = decode_data 245 246 def dump(self): 247 for i in self.fields.keys(): 248 print "%s: {%r}" % (i,self[i]) 249 250 def getData(self): 251 ans = pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP) 252 if self.fields.has_key('NegResult') and self.fields.has_key('SupportedMech'): 253 # Server resp 254 ans += asn1encode( 255 pack('B', ASN1_SEQUENCE) + 256 asn1encode( 257 pack('B',SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 258 asn1encode( 259 pack('B',ASN1_ENUMERATED) + 260 asn1encode( self['NegResult'] )) + 261 pack('B',ASN1_SUPPORTED_MECH) + 262 asn1encode( 263 pack('B',ASN1_OID) + 264 asn1encode(self['SupportedMech'])) + 265 pack('B',ASN1_RESPONSE_TOKEN ) + 266 asn1encode( 267 pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken'])))) 268 elif self.fields.has_key('NegResult'): 269 # Server resp 270 ans += asn1encode( 271 pack('B', ASN1_SEQUENCE) + 272 asn1encode( 273 pack('B', SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_TARG) + 274 asn1encode( 275 pack('B',ASN1_ENUMERATED) + 276 asn1encode( self['NegResult'] )))) 277 else: 278 # Client resp 279 ans += asn1encode( 280 pack('B', ASN1_SEQUENCE) + 281 asn1encode( 282 pack('B', ASN1_RESPONSE_TOKEN) + 283 asn1encode( 284 pack('B', ASN1_OCTET_STRING) + asn1encode(self['ResponseToken'])))) 285 return ans 286 287class SPNEGO_NegTokenInit(GSSAPI): 288 # http://tools.ietf.org/html/rfc4178#page-8 289 # NegTokeInit :: = SEQUENCE { 290 # mechTypes [0] MechTypeList, 291 # reqFlags [1] ContextFlags OPTIONAL, 292 # mechToken [2] OCTET STRING OPTIONAL, 293 # mechListMIC [3] OCTET STRING OPTIONAL, 294 # } 295 SPNEGO_NEG_TOKEN_INIT = 0xa0 296 def fromString(self, data = 0): 297 GSSAPI.fromString(self, data) 298 payload = self['Payload'] 299 next_byte = unpack('B', payload[:1])[0] 300 if next_byte != SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT: 301 raise Exception('NegTokenInit not found %x' % next_byte) 302 payload = payload[1:] 303 decode_data, total_bytes = asn1decode(payload) 304 # Now we should have a SEQUENCE Tag 305 next_byte = unpack('B', decode_data[:1])[0] 306 if next_byte != ASN1_SEQUENCE: 307 raise Exception('SEQUENCE tag not found %x' % next_byte) 308 decode_data = decode_data[1:] 309 decode_data, total_bytes2 = asn1decode(decode_data) 310 next_byte = unpack('B',decode_data[:1])[0] 311 if next_byte != ASN1_MECH_TYPE: 312 raise Exception('MechType tag not found %x' % next_byte) 313 decode_data = decode_data[1:] 314 remaining_data = decode_data 315 decode_data, total_bytes3 = asn1decode(decode_data) 316 next_byte = unpack('B', decode_data[:1])[0] 317 if next_byte != ASN1_SEQUENCE: 318 raise Exception('SEQUENCE tag not found %x' % next_byte) 319 decode_data = decode_data[1:] 320 decode_data, total_bytes4 = asn1decode(decode_data) 321 # And finally we should have the MechTypes 322 self['MechTypes'] = [] 323 while decode_data: 324 next_byte = unpack('B', decode_data[:1])[0] 325 if next_byte != ASN1_OID: 326 # Not a valid OID, there must be something else we won't unpack 327 break 328 decode_data = decode_data[1:] 329 item, total_bytes = asn1decode(decode_data) 330 self['MechTypes'].append(item) 331 decode_data = decode_data[total_bytes:] 332 333 # Do we have MechTokens as well? 334 decode_data = remaining_data[total_bytes3:] 335 if len(decode_data) > 0: 336 next_byte = unpack('B', decode_data[:1])[0] 337 if next_byte == ASN1_MECH_TOKEN: 338 # We have tokens in here! 339 decode_data = decode_data[1:] 340 decode_data, total_bytes = asn1decode(decode_data) 341 next_byte = unpack('B', decode_data[:1])[0] 342 if next_byte == ASN1_OCTET_STRING: 343 decode_data = decode_data[1:] 344 decode_data, total_bytes = asn1decode(decode_data) 345 self['MechToken'] = decode_data 346 347 def getData(self): 348 mechTypes = '' 349 for i in self['MechTypes']: 350 mechTypes += pack('B', ASN1_OID) 351 mechTypes += asn1encode(i) 352 353 mechToken = '' 354 # Do we have tokens to send? 355 if self.fields.has_key('MechToken'): 356 mechToken = pack('B', ASN1_MECH_TOKEN) + asn1encode( 357 pack('B', ASN1_OCTET_STRING) + asn1encode( 358 self['MechToken'])) 359 360 ans = pack('B',SPNEGO_NegTokenInit.SPNEGO_NEG_TOKEN_INIT) 361 ans += asn1encode( 362 pack('B', ASN1_SEQUENCE) + 363 asn1encode( 364 pack('B', ASN1_MECH_TYPE) + 365 asn1encode( 366 pack('B', ASN1_SEQUENCE) + 367 asn1encode(mechTypes)) + mechToken )) 368 369 370 self['Payload'] = ans 371 return GSSAPI.getData(self) 372 373