github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/rdp_check.py (about) 1 #!/usr/bin/env python 2 # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 3 # 4 # This software is provided under under a slightly modified version 5 # of the Apache Software License. See the accompanying LICENSE file 6 # for more information. 7 # 8 # Author: 9 # Alberto Solino (@agsolino) 10 # 11 # Description: [MS-RDPBCGR] and [MS-CREDSSP] partial implementation 12 # just to reach CredSSP auth. This example test whether 13 # an account is valid on the target host. 14 # 15 # ToDo: 16 # [x] Manage to grab the server's SSL key so we can finalize the whole 17 # authentication process (check [MS-CSSP] section 3.1.5) 18 # 19 20 from struct import pack, unpack 21 22 from impacket.examples import logger 23 from impacket.structure import Structure 24 from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode 25 26 TDPU_CONNECTION_REQUEST = 0xe0 27 TPDU_CONNECTION_CONFIRM = 0xd0 28 TDPU_DATA = 0xf0 29 TPDU_REJECT = 0x50 30 TPDU_DATA_ACK = 0x60 31 32 # RDP_NEG_REQ constants 33 TYPE_RDP_NEG_REQ = 1 34 PROTOCOL_RDP = 0 35 PROTOCOL_SSL = 1 36 PROTOCOL_HYBRID = 2 37 38 # RDP_NEG_RSP constants 39 TYPE_RDP_NEG_RSP = 2 40 EXTENDED_CLIENT_DATA_SUPPORTED = 1 41 DYNVC_GFX_PROTOCOL_SUPPORTED = 2 42 43 # RDP_NEG_FAILURE constants 44 TYPE_RDP_NEG_FAILURE = 3 45 SSL_REQUIRED_BY_SERVER = 1 46 SSL_NOT_ALLOWED_BY_SERVER = 2 47 SSL_CERT_NOT_ON_SERVER = 3 48 INCONSISTENT_FLAGS = 4 49 HYBRID_REQUIRED_BY_SERVER = 5 50 SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6 51 52 class TPKT(Structure): 53 commonHdr = ( 54 ('Version','B=3'), 55 ('Reserved','B=0'), 56 ('Length','>H=len(TPDU)+4'), 57 ('_TPDU','_-TPDU','self["Length"]-4'), 58 ('TPDU',':=""'), 59 ) 60 61 class TPDU(Structure): 62 commonHdr = ( 63 ('LengthIndicator','B=len(VariablePart)+1'), 64 ('Code','B=0'), 65 ('VariablePart',':=""'), 66 ) 67 68 def __init__(self, data = None): 69 Structure.__init__(self,data) 70 self['VariablePart']='' 71 72 class CR_TPDU(Structure): 73 commonHdr = ( 74 ('DST-REF','<H=0'), 75 ('SRC-REF','<H=0'), 76 ('CLASS-OPTION','B=0'), 77 ('Type','B=0'), 78 ('Flags','B=0'), 79 ('Length','<H=8'), 80 ) 81 82 class DATA_TPDU(Structure): 83 commonHdr = ( 84 ('EOT','B=0x80'), 85 ('UserData',':=""'), 86 ) 87 88 def __init__(self, data = None): 89 Structure.__init__(self,data) 90 self['UserData'] ='' 91 92 93 class RDP_NEG_REQ(CR_TPDU): 94 structure = ( 95 ('requestedProtocols','<L'), 96 ) 97 def __init__(self,data=None): 98 CR_TPDU.__init__(self,data) 99 if data is None: 100 self['Type'] = TYPE_RDP_NEG_REQ 101 102 class RDP_NEG_RSP(CR_TPDU): 103 structure = ( 104 ('selectedProtocols','<L'), 105 ) 106 107 class RDP_NEG_FAILURE(CR_TPDU): 108 structure = ( 109 ('failureCode','<L'), 110 ) 111 112 class TSPasswordCreds(GSSAPI): 113 # TSPasswordCreds ::= SEQUENCE { 114 # domainName [0] OCTET STRING, 115 # userName [1] OCTET STRING, 116 # password [2] OCTET STRING 117 # } 118 def __init__(self, data=None): 119 GSSAPI.__init__(self,data) 120 del self['UUID'] 121 122 def getData(self): 123 ans = pack('B', ASN1_SEQUENCE) 124 ans += asn1encode( pack('B', 0xa0) + 125 asn1encode( pack('B', ASN1_OCTET_STRING) + 126 asn1encode( self['domainName'].encode('utf-16le'))) + 127 pack('B', 0xa1) + 128 asn1encode( pack('B', ASN1_OCTET_STRING) + 129 asn1encode( self['userName'].encode('utf-16le'))) + 130 pack('B', 0xa2) + 131 asn1encode( pack('B', ASN1_OCTET_STRING) + 132 asn1encode( self['password'].encode('utf-16le'))) ) 133 return ans 134 135 class TSCredentials(GSSAPI): 136 # TSCredentials ::= SEQUENCE { 137 # credType [0] INTEGER, 138 # credentials [1] OCTET STRING 139 # } 140 def __init__(self, data=None): 141 GSSAPI.__init__(self,data) 142 del self['UUID'] 143 144 def getData(self): 145 # Let's pack the credentials field 146 credentials = pack('B',0xa1) 147 credentials += asn1encode(pack('B',ASN1_OCTET_STRING) + 148 asn1encode(self['credentials'])) 149 150 ans = pack('B',ASN1_SEQUENCE) 151 ans += asn1encode( pack('B', 0xa0) + 152 asn1encode( pack('B', 0x02) + 153 asn1encode( pack('B', self['credType']))) + 154 credentials) 155 return ans 156 157 class TSRequest(GSSAPI): 158 # TSRequest ::= SEQUENCE { 159 # version [0] INTEGER, 160 # negoTokens [1] NegoData OPTIONAL, 161 # authInfo [2] OCTET STRING OPTIONAL, 162 # pubKeyAuth [3] OCTET STRING OPTIONAL, 163 #} 164 # 165 # NegoData ::= SEQUENCE OF SEQUENCE { 166 # negoToken [0] OCTET STRING 167 #} 168 # 169 170 def __init__(self, data=None): 171 GSSAPI.__init__(self,data) 172 del self['UUID'] 173 174 def fromString(self, data = None): 175 next_byte = unpack('B',data[:1])[0] 176 if next_byte != ASN1_SEQUENCE: 177 raise Exception('SEQUENCE expected! (%x)' % next_byte) 178 data = data[1:] 179 decode_data, total_bytes = asn1decode(data) 180 181 next_byte = unpack('B',decode_data[:1])[0] 182 if next_byte != 0xa0: 183 raise Exception('0xa0 tag not found %x' % next_byte) 184 decode_data = decode_data[1:] 185 next_bytes, total_bytes = asn1decode(decode_data) 186 # The INTEGER tag must be here 187 if unpack('B',next_bytes[0:1])[0] != 0x02: 188 raise Exception('INTEGER tag not found %r' % next_byte) 189 next_byte, _ = asn1decode(next_bytes[1:]) 190 self['Version'] = unpack('B',next_byte)[0] 191 decode_data = decode_data[total_bytes:] 192 next_byte = unpack('B',decode_data[:1])[0] 193 if next_byte == 0xa1: 194 # We found the negoData token 195 decode_data, total_bytes = asn1decode(decode_data[1:]) 196 197 next_byte = unpack('B',decode_data[:1])[0] 198 if next_byte != ASN1_SEQUENCE: 199 raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte) 200 decode_data, total_bytes = asn1decode(decode_data[1:]) 201 202 next_byte = unpack('B',decode_data[:1])[0] 203 if next_byte != ASN1_SEQUENCE: 204 raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte) 205 decode_data, total_bytes = asn1decode(decode_data[1:]) 206 207 next_byte = unpack('B',decode_data[:1])[0] 208 if next_byte != 0xa0: 209 raise Exception('0xa0 tag not found %r' % next_byte) 210 decode_data, total_bytes = asn1decode(decode_data[1:]) 211 212 next_byte = unpack('B',decode_data[:1])[0] 213 if next_byte != ASN1_OCTET_STRING: 214 raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte) 215 decode_data2, total_bytes = asn1decode(decode_data[1:]) 216 # the rest should be the data 217 self['NegoData'] = decode_data2 218 decode_data = decode_data[total_bytes+1:] 219 220 if next_byte == 0xa2: 221 # ToDo: Check all this 222 # We found the authInfo token 223 decode_data, total_bytes = asn1decode(decode_data[1:]) 224 next_byte = unpack('B',decode_data[:1])[0] 225 if next_byte != ASN1_OCTET_STRING: 226 raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte) 227 decode_data2, total_bytes = asn1decode(decode_data[1:]) 228 self['authInfo'] = decode_data2 229 decode_data = decode_data[total_bytes+1:] 230 231 if next_byte == 0xa3: 232 # ToDo: Check all this 233 # We found the pubKeyAuth token 234 decode_data, total_bytes = asn1decode(decode_data[1:]) 235 next_byte = unpack('B',decode_data[:1])[0] 236 if next_byte != ASN1_OCTET_STRING: 237 raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte) 238 decode_data2, total_bytes = asn1decode(decode_data[1:]) 239 self['pubKeyAuth'] = decode_data2 240 241 def getData(self): 242 # Do we have pubKeyAuth? 243 if 'pubKeyAuth' in self.fields: 244 pubKeyAuth = pack('B',0xa3) 245 pubKeyAuth += asn1encode(pack('B', ASN1_OCTET_STRING) + 246 asn1encode(self['pubKeyAuth'])) 247 else: 248 pubKeyAuth = b'' 249 250 if 'authInfo' in self.fields: 251 authInfo = pack('B',0xa2) 252 authInfo+= asn1encode(pack('B', ASN1_OCTET_STRING) + 253 asn1encode(self['authInfo'])) 254 else: 255 authInfo = b'' 256 257 if 'NegoData' in self.fields: 258 negoData = pack('B',0xa1) 259 negoData += asn1encode(pack('B', ASN1_SEQUENCE) + 260 asn1encode(pack('B', ASN1_SEQUENCE) + 261 asn1encode(pack('B', 0xa0) + 262 asn1encode(pack('B', ASN1_OCTET_STRING) + 263 asn1encode(self['NegoData']))))) 264 else: 265 negoData = b'' 266 ans = pack('B', ASN1_SEQUENCE) 267 ans += asn1encode(pack('B',0xa0) + 268 asn1encode(pack('B',0x02) + asn1encode(pack('B',0x02))) + 269 negoData + authInfo + pubKeyAuth) 270 271 return ans 272 273 if __name__ == '__main__': 274 275 import socket 276 import argparse 277 import sys 278 import logging 279 from binascii import a2b_hex 280 from Cryptodome.Cipher import ARC4 281 from impacket import ntlm, version 282 try: 283 from OpenSSL import SSL, crypto 284 except: 285 logging.critical("pyOpenSSL is not installed, can't continue") 286 sys.exit(1) 287 288 289 class SPNEGOCipher: 290 def __init__(self, flags, randomSessionKey): 291 self.__flags = flags 292 if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: 293 self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey) 294 self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server") 295 self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey) 296 self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server") 297 # Preparing the keys handle states 298 cipher3 = ARC4.new(self.__clientSealingKey) 299 self.__clientSealingHandle = cipher3.encrypt 300 cipher4 = ARC4.new(self.__serverSealingKey) 301 self.__serverSealingHandle = cipher4.encrypt 302 else: 303 # Same key for everything 304 self.__clientSigningKey = randomSessionKey 305 self.__serverSigningKey = randomSessionKey 306 self.__clientSealingKey = randomSessionKey 307 self.__clientSealingKey = randomSessionKey 308 cipher = ARC4.new(self.__clientSigningKey) 309 self.__clientSealingHandle = cipher.encrypt 310 self.__serverSealingHandle = cipher.encrypt 311 self.__sequence = 0 312 313 def encrypt(self, plain_data): 314 if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: 315 # When NTLM2 is on, we sign the whole pdu, but encrypt just 316 # the data, not the dcerpc header. Weird.. 317 sealedMessage, signature = ntlm.SEAL(self.__flags, 318 self.__clientSigningKey, 319 self.__clientSealingKey, 320 plain_data, 321 plain_data, 322 self.__sequence, 323 self.__clientSealingHandle) 324 else: 325 sealedMessage, signature = ntlm.SEAL(self.__flags, 326 self.__clientSigningKey, 327 self.__clientSealingKey, 328 plain_data, 329 plain_data, 330 self.__sequence, 331 self.__clientSealingHandle) 332 333 self.__sequence += 1 334 335 return signature, sealedMessage 336 337 def decrypt(self, answer): 338 if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: 339 # TODO: FIX THIS, it's not calculating the signature well 340 # Since I'm not testing it we don't care... yet 341 answer, signature = ntlm.SEAL(self.__flags, 342 self.__serverSigningKey, 343 self.__serverSealingKey, 344 answer, 345 answer, 346 self.__sequence, 347 self.__serverSealingHandle) 348 else: 349 answer, signature = ntlm.SEAL(self.__flags, 350 self.__serverSigningKey, 351 self.__serverSealingKey, 352 answer, 353 answer, 354 self.__sequence, 355 self.__serverSealingHandle) 356 self.__sequence += 1 357 358 return signature, answer 359 360 def check_rdp(host, username, password, domain, hashes = None): 361 362 if hashes is not None: 363 lmhash, nthash = hashes.split(':') 364 lmhash = a2b_hex(lmhash) 365 nthash = a2b_hex(nthash) 366 367 else: 368 lmhash = '' 369 nthash = '' 370 371 tpkt = TPKT() 372 tpdu = TPDU() 373 rdp_neg = RDP_NEG_REQ() 374 rdp_neg['Type'] = TYPE_RDP_NEG_REQ 375 rdp_neg['requestedProtocols'] = PROTOCOL_HYBRID | PROTOCOL_SSL 376 tpdu['VariablePart'] = rdp_neg.getData() 377 tpdu['Code'] = TDPU_CONNECTION_REQUEST 378 tpkt['TPDU'] = tpdu.getData() 379 380 s = socket.socket() 381 s.connect((host,3389)) 382 s.sendall(tpkt.getData()) 383 pkt = s.recv(8192) 384 tpkt.fromString(pkt) 385 tpdu.fromString(tpkt['TPDU']) 386 cr_tpdu = CR_TPDU(tpdu['VariablePart']) 387 if cr_tpdu['Type'] == TYPE_RDP_NEG_FAILURE: 388 rdp_failure = RDP_NEG_FAILURE(tpdu['VariablePart']) 389 rdp_failure.dump() 390 logging.error("Server doesn't support PROTOCOL_HYBRID, hence we can't use CredSSP to check credentials") 391 return 392 else: 393 rdp_neg.fromString(tpdu['VariablePart']) 394 395 # Since we were accepted to talk PROTOCOL_HYBRID, below is its implementation 396 397 # 1. The CredSSP client and CredSSP server first complete the TLS handshake, 398 # as specified in [RFC2246]. After the handshake is complete, all subsequent 399 # CredSSP Protocol messages are encrypted by the TLS channel. 400 # The CredSSP Protocol does not extend the TLS wire protocol. As part of the TLS 401 # handshake, the CredSSP server does not request the client's X.509 certificate 402 # (thus far, the client is anonymous). Also, the CredSSP Protocol does not require 403 # the client to have a commonly trusted certification authority root with the 404 # CredSSP server. Thus, the CredSSP server MAY use, for example, 405 # a self-signed X.509 certificate. 406 407 # Switching to TLS now 408 ctx = SSL.Context(SSL.TLSv1_2_METHOD) 409 ctx.set_cipher_list('RC4,AES') 410 tls = SSL.Connection(ctx,s) 411 tls.set_connect_state() 412 tls.do_handshake() 413 414 # If you want to use Python internal ssl, uncomment this and comment 415 # the previous lines 416 #tls = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, ciphers='RC4') 417 418 # 2. Over the encrypted TLS channel, the SPNEGO handshake between the client 419 # and server completes mutual authentication and establishes an encryption key 420 # that is used by the SPNEGO confidentiality services, as specified in [RFC4178]. 421 # All SPNEGO tokens as well as the underlying encryption algorithms are opaque to 422 # the calling application (the CredSSP client and CredSSP server). 423 # The wire protocol for SPNEGO is specified in [MS-SPNG]. 424 # The SPNEGO tokens exchanged between the client and the server are encapsulated 425 # in the negoTokens field of the TSRequest structure. Both the client and the 426 # server use this structure as many times as necessary to complete the SPNEGO 427 # exchange.<9> 428 # 429 # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted 430 # from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth 431 # field is omitted by the client unless the client is sending the last SPNEGO token. 432 # If the client is sending the last SPNEGO token, the TSRequest structure MUST have 433 # both the negoToken and the pubKeyAuth fields filled in. 434 435 # NTLMSSP stuff 436 auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True) 437 438 ts_request = TSRequest() 439 ts_request['NegoData'] = auth.getData() 440 441 tls.send(ts_request.getData()) 442 buff = tls.recv(4096) 443 ts_request.fromString(buff) 444 445 446 # 3. The client encrypts the public key it received from the server (contained 447 # in the X.509 certificate) in the TLS handshake from step 1, by using the 448 # confidentiality support of SPNEGO. The public key that is encrypted is the 449 # ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509 450 # certificate, as specified in [RFC3280] section 4.1. The encrypted key is 451 # encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over 452 # the TLS channel to the server. 453 # 454 # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted 455 # from the TSRequest structure; the client MUST send its last SPNEGO token to the 456 # server in the negoTokens field (see step 2) along with the encrypted public key 457 # in the pubKeyAuth field. 458 459 # Last SPNEGO token calculation 460 #ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData']) 461 type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True) 462 463 # Get server public key 464 server_cert = tls.get_peer_certificate() 465 pkey = server_cert.get_pubkey() 466 dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey) 467 468 # Fix up due to PyOpenSSL lack for exporting public keys 469 dump = dump[7:] 470 dump = b'\x30'+ asn1encode(dump) 471 472 cipher = SPNEGOCipher(type3['flags'], exportedSessionKey) 473 signature, cripted_key = cipher.encrypt(dump) 474 ts_request['NegoData'] = type3.getData() 475 ts_request['pubKeyAuth'] = signature.getData() + cripted_key 476 477 try: 478 # Sending the Type 3 NTLM blob 479 tls.send(ts_request.getData()) 480 # The other end is waiting for the pubKeyAuth field, but looks like it's 481 # not needed to check whether authentication worked. 482 # If auth is unsuccessful, it throws an exception with the previous send(). 483 # If auth is successful, the server waits for the pubKeyAuth and doesn't answer 484 # anything. So, I'm sending garbage so the server returns an error. 485 # Luckily, it's a different error so we can determine whether or not auth worked ;) 486 buff = tls.recv(1024) 487 except Exception as err: 488 if str(err).find("denied") > 0: 489 logging.error("Access Denied") 490 else: 491 logging.error(err) 492 return 493 494 # 4. After the server receives the public key in step 3, it first verifies that 495 # it has the same public key that it used as part of the TLS handshake in step 1. 496 # The server then adds 1 to the first byte representing the public key (the ASN.1 497 # structure corresponding to the SubjectPublicKey field, as described in step 3) 498 # and encrypts the binary result by using the SPNEGO encryption services. 499 # Due to the addition of 1 to the binary data, and encryption of the data as a binary 500 # structure, the resulting value may not be valid ASN.1-encoded values. 501 # The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest 502 # structure and is sent over the encrypted TLS channel to the client. 503 # The addition of 1 to the first byte of the public key is performed so that the 504 # client-generated pubKeyAuth message cannot be replayed back to the client by an 505 # attacker. 506 # 507 # Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens 508 # fields are omitted from the TSRequest structure. 509 510 ts_request = TSRequest(buff) 511 512 # Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;) 513 signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:]) 514 515 # 5. After the client successfully verifies server authenticity by performing a 516 # binary comparison of the data from step 4 to that of the data representing 517 # the public key from the server's X.509 certificate (as specified in [RFC3280], 518 # section 4.1), it encrypts the user's credentials (either password or smart card 519 # PIN) by using the SPNEGO encryption services. The resulting value is 520 # encapsulated in the authInfo field of the TSRequest structure and sent over 521 # the encrypted TLS channel to the server. 522 # The TSCredentials structure within the authInfo field of the TSRequest 523 # structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure, 524 # but MUST NOT contain both. 525 # 526 # Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens 527 # fields are omitted from the TSRequest structure. 528 tsp = TSPasswordCreds() 529 tsp['domainName'] = domain 530 tsp['userName'] = username 531 tsp['password'] = password 532 tsc = TSCredentials() 533 tsc['credType'] = 1 # TSPasswordCreds 534 tsc['credentials'] = tsp.getData() 535 536 signature, cripted_creds = cipher.encrypt(tsc.getData()) 537 ts_request = TSRequest() 538 ts_request['authInfo'] = signature.getData() + cripted_creds 539 tls.send(ts_request.getData()) 540 tls.close() 541 logging.info("Access Granted") 542 543 # Init the example's logger theme 544 logger.init() 545 print(version.BANNER) 546 547 parser = argparse.ArgumentParser(add_help = True, description = "Test whether an account is valid on the target " 548 "host using the RDP protocol.") 549 550 parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>') 551 552 group = parser.add_argument_group('authentication') 553 554 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 555 if len(sys.argv)==1: 556 parser.print_help() 557 sys.exit(1) 558 559 options = parser.parse_args() 560 561 import re 562 domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('') 563 564 #In case the password contains '@' 565 if '@' in address: 566 password = password + '@' + address.rpartition('@')[0] 567 address = address.rpartition('@')[2] 568 569 if domain is None: 570 domain = '' 571 572 if password == '' and username != '' and options.hashes is None: 573 from getpass import getpass 574 password = getpass("Password:") 575 576 check_rdp(address, username, password, domain, options.hashes)