Projects STRLCPY aardwolf Commits 8f112b06
🤬
  • ■ ■ ■ ■ ■
    aardwolf/__init__.py
    skipped 6 lines
    7 7  handler.setFormatter(formatter)
    8 8  logger.addHandler(handler)
    9 9  logger.setLevel(logging.INFO)
     10 +logger.propagate = False
  • ■ ■ ■ ■
    aardwolf/_version.py
    1 1   
    2  -__version__ = "0.0.9"
     2 +__version__ = "0.1.0"
    3 3  __banner__ = \
    4 4  """
    5 5  # aardwolf %s
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/messages/asn1_structs.py
    1 1   
    2 2  from asn1crypto.core import Integer, ObjectIdentifier, Sequence, SequenceOf, Enumerated, GeneralString, OctetString, BitString, Choice, Any, Boolean
    3  -import enum
    4  -import os
    5  -import io
    6 3   
    7 4  TAG = 'explicit'
    8 5   
    skipped 132 lines
  • ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/native.py
    skipped 5 lines
    6 6   NegoData, TSRequest, TSRequest, TSPasswordCreds, TSCredentials, TSRemoteGuardCreds, \
    7 7   TSRemoteGuardPackageCred
    8 8  from hashlib import sha256
     9 +from asn1crypto.x509 import Certificate
    9 10   
    10 11  # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/385a7489-d46b-464c-b224-f7340e308a5c
    11 12   
    skipped 8 lines
    20 21   self.__internal_auth_continue = True
    21 22   self.seqno = 0
    22 23   
     24 + @staticmethod
     25 + def certificate_to_pubkey(certdata):
     26 + # credSSP auth requires knowledge of the server's public key
     27 + cert = Certificate.load(certdata)
     28 + return cert['tbs_certificate']['subject_public_key_info']['public_key'].dump()[5:] #why?
     29 + 
    23 30   def get_extra_info(self):
    24 31   return self.auth_ctx.get_extra_info()
    25 32   
    26  - async def authenticate(self, token, flags = None, pubkey = None, remote_credguard = False):
     33 + async def authenticate(self, token, flags = None, certificate = None, remote_credguard = False):
    27 34   try:
    28 35   # currently only SSPI supported
    29 36   
     37 + if certificate is not None:
     38 + pubkey = CredSSPAuth.certificate_to_pubkey(certificate)
    30 39   if self.mode != 'CLIENT':
    31 40   raise NotImplementedError()
    32 41   else:
    skipped 118 lines
  • ■ ■ ■ ■ ■ ■
    aardwolf/channels.py
    skipped 8 lines
    9 9   self.name = name
    10 10   self.options = options
    11 11   self.channel_id = None
    12  - self.raw_in_queue = asyncio.Queue()
    13  - self.raw_out_queue = None
    14  - self.in_queue = asyncio.Queue()
    15  - self.data_in_queue = asyncio.Queue()
    16  - self.out_queue = asyncio.Queue()
    17 12   self.connection = None
    18  - self.initiator = None
    19 13   
    20 14   def get_name(self):
    21 15   return self.name
    22 16  
    23 17   async def disconnect(self):
    24 18   try:
    25  - if self.monitor_out_task is not None:
    26  - self.monitor_out_task.cancel()
    27  - if self.monitor_in_task is not None:
    28  - self.monitor_in_task.cancel()
    29 19   _, err = await self.stop()
    30 20   if err is not None:
    31 21   raise err
    skipped 2 lines
    34 24   
    35 25   async def run(self, connection):
    36 26   try:
    37  - # this should take a queues which belong to the lower-layer
    38  - self.raw_out_queue = connection._x224net.out_queue
    39 27   self.connection = connection
    40  - self.initiator = self.connection._initiator
    41  - self.monitor_out_task = asyncio.create_task(self.monitor_out())
    42  - self.monitor_in_task = asyncio.create_task(self.monitor_in())
    43 28   _, err = await self.start()
    44 29   if err is not None:
    45 30   raise err
    skipped 17 lines
    63 48   except Exception as e:
    64 49   return None, e
    65 50   
    66  - async def monitor_in(self):
    67  - try:
    68  - while True:
    69  - data, err = await self.raw_in_queue.get()
    70  - if err is not None:
    71  - await self.out_queue.put((data, err))
    72  - raise err
    73  - #print('Channel data in! "%s(%s)" <- %s' % (self.name, self.channel_id, data))
     51 + async def process_channel_data(self, data):
     52 + # This function will be called when channel data is recieved from the server
     53 + raise NotImplementedError()
    74 54  
    75  - await self.out_queue.put((data, err))
     55 + async def process_user_data(self, data):
     56 + # This function will be called when the channel recieves a new user data
     57 + # Override this function to implement user data processing
     58 + raise NotImplementedError()
    76 59  
    77  - except asyncio.CancelledError:
    78  - return None, None
    79  - except Exception as e:
    80  - traceback.print_exc()
    81  - return None, e
     60 + async def send_channel_data(self, dataobj, sec_hdr, datacontrol_hdr, sharecontrol_hdr, is_fastpath):
     61 + await self.connection.handle_out_data(dataobj, sec_hdr, datacontrol_hdr, sharecontrol_hdr, self.channel_id, is_fastpath)
    82 62   
    83  - async def monitor_out(self):
    84  - try:
    85  - while True:
    86  - data = await self.in_queue.get()
    87  - #print('Sending data on channel "%s(%s)" -> %s' % (self.name, self.channel_id, data))
    88  - await self.raw_out_queue.put(data)
     63 + async def send_user_data(self, msg):
     64 + await self.connection.ext_out_queue.put(msg)
    89 65   
    90  - except asyncio.CancelledError:
    91  - return None, None
    92 66   
    93  - except Exception as e:
    94  - traceback.print_exc()
    95  - return None, e
     67 +class MCSChannel(Channel):
     68 + name = 'MCS'
     69 + def __init__(self, iosettings = None):
     70 + Channel.__init__(self, self.name, ChannelOption.INITIALIZED|ChannelOption.ENCRYPT_RDP)
     71 + self.out_queue = asyncio.Queue()
    96 72  
    97 73   
     74 + async def process_channel_data(self, data):
     75 + # This function will be called when channel data is recieved from the server
     76 + #print('Channel data in! "%s(%s)" <- %s' % (self.name, self.channel_id, data))
     77 + await self.out_queue.put((data, None))
  • ■ ■ ■ ■ ■ ■
    aardwolf/commons/authbuilder.py
    1  -import platform
    2  -import os
    3  - 
    4  -import copy
    5  -from aardwolf.commons.credential import RDPAuthProtocol, RDPCredential, \
    6  - RDPNTLMCredential, RDPCredentialsSecretType, RDPKerberosCredential, \
    7  - RDPKerberosSSPICredential, RDPNTLMSSPICredential, RDPMultiplexorCredential, \
    8  - RDPMPNCredential, RDPWSNETCredential, RDPSSPIProxyCredential
    9  -from aardwolf.commons.proxy import RDPProxyType
    10  -from aardwolf.authentication.spnego.native import SPNEGO
    11  -from aardwolf.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
    12  -from aardwolf.authentication.kerberos.native import RDPKerberos
    13  -from aardwolf.authentication.credssp.native import CredSSPAuth
    14  -from minikerberos.common.target import KerberosTarget
    15  -from minikerberos.common.proxy import KerberosProxy
    16  -from minikerberos.common.creds import KerberosCredential
    17  -from minikerberos.common.spn import KerberosSPN
    18  - 
    19  -from aardwolf.commons.target import RDPTarget
    20  - 
    21  - 
    22  -if platform.system().upper() == 'WINDOWS':
    23  - from aardwolf.authentication.kerberos.sspi import RDPKerberosSSPI
    24  - from aardwolf.authentication.ntlm.sspi import RDPNTLMSSPI
    25  - 
    26  -class AuthenticatorBuilder:
    27  - def __init__(self):
    28  - pass
    29  -
    30  - @staticmethod
    31  - def wrap_credssp(spnego, credtype, cred):
    32  - if credtype.find('KERBEROS') != -1:
    33  - ntlm = spnego
    34  - else:
    35  - ntlm = spnego.get_original_context('NTLMSSP - Microsoft NTLM Security Support Provider')
    36  - credssp_ctx = CredSSPAuth()
    37  - credssp_ctx.auth_ctx = ntlm
    38  - credssp_ctx.credtype = credtype
    39  - credssp_ctx.cred = cred
    40  - return credssp_ctx
    41  - 
    42  - 
    43  - 
    44  - @staticmethod
    45  - def to_credssp(creds:RDPCredential, target:RDPTarget = None):
    46  - if creds.authentication_type == RDPAuthProtocol.PLAIN:
    47  - ntlmcred = RDPNTLMCredential()
    48  - ntlmcred.username = creds.username
    49  - ntlmcred.domain = creds.domain if creds.domain is not None else ''
    50  - ntlmcred.workstation = None
    51  - ntlmcred.is_guest = False
    52  -
    53  - if creds.secret is None:
    54  - if creds.username is None and creds.domain is None:
    55  - ntlmcred.is_guest = True
    56  - else:
    57  - raise Exception('Authentication requres password!')
    58  -
    59  - ntlmcred.password = creds.secret
    60  -
    61  - settings = NTLMHandlerSettings(ntlmcred)
    62  - handler = NTLMAUTHHandler(settings)
    63  -
    64  - #setting up SPNEGO
    65  - spneg = SPNEGO()
    66  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    67  -
    68  - return AuthenticatorBuilder.wrap_credssp(spneg, 'NTLM', ntlmcred)
    69  - 
    70  - elif creds.authentication_type == RDPAuthProtocol.NTLM:
    71  - ntlmcred = RDPNTLMCredential()
    72  - ntlmcred.username = creds.username
    73  - ntlmcred.domain = creds.domain if creds.domain is not None else ''
    74  - ntlmcred.workstation = None
    75  - ntlmcred.is_guest = False
    76  -
    77  - if creds.secret is None:
    78  - if creds.username is None and creds.domain is None:
    79  - ntlmcred.is_guest = True
    80  - else:
    81  - raise Exception('NTLM authentication requres password!')
    82  -
    83  - if creds.secret_type == RDPCredentialsSecretType.NT:
    84  - if isinstance(creds.secret, str) is True and len(creds.secret) != 32:
    85  - raise Exception('This is not an NT hash!')
    86  - ntlmcred.nt_hash = creds.secret
    87  - elif creds.secret_type == RDPCredentialsSecretType.PASSWORD:
    88  - ntlmcred.password = creds.secret
    89  -
    90  - settings = NTLMHandlerSettings(ntlmcred)
    91  - handler = NTLMAUTHHandler(settings)
    92  -
    93  - #setting up SPNEGO
    94  - spneg = SPNEGO()
    95  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    96  -
    97  - return AuthenticatorBuilder.wrap_credssp(spneg, 'NTLM', ntlmcred)
    98  -
    99  - elif creds.authentication_type == RDPAuthProtocol.KERBEROS:
    100  - if target is None:
    101  - raise Exception('Target must be specified with Kerberos!')
    102  -
    103  - if target.hostname is None:
    104  - raise Exception('target must have a domain name or hostname for kerberos!')
    105  -
    106  - if target.dc_ip is None:
    107  - raise Exception('target must have a dc_ip for kerberos!')
    108  -
    109  - if creds.secret_type == RDPCredentialsSecretType.KEYTAB:
    110  - filename = creds.secret
    111  - if creds.secret.upper() == 'ENV':
    112  - filename = os.environ['KRB5KEYTAB']
    113  - 
    114  - kc = KerberosCredential.from_keytab(filename, creds.username, creds.domain)
    115  - 
    116  - elif creds.secret_type == RDPCredentialsSecretType.CCACHE:
    117  - filename = creds.secret
    118  - if creds.secret.upper() == 'ENV':
    119  - try:
    120  - filename = os.environ['KRB5CCACHE']
    121  - except:
    122  - raise Exception('Kerberos auth missing environment variable KRB5CCACHE')
    123  - kc = KerberosCredential.from_ccache_file(filename)
    124  - kc.username = creds.username
    125  - kc.domain = creds.domain
    126  -
    127  - elif creds.secret_type == RDPCredentialsSecretType.KIRBI:
    128  - filename = creds.secret
    129  - kc = KerberosCredential.from_kirbi(filename)
    130  - kc.username = creds.username
    131  - kc.domain = creds.domain
    132  -
    133  - else:
    134  - kc = KerberosCredential()
    135  - kc.username = creds.username
    136  - kc.domain = creds.domain
    137  - if creds.secret_type == RDPCredentialsSecretType.PASSWORD:
    138  - kc.password = creds.secret
    139  - elif creds.secret_type == RDPCredentialsSecretType.NT:
    140  - kc.nt_hash = creds.secret
    141  -
    142  - elif creds.secret_type == RDPCredentialsSecretType.AES:
    143  - if len(creds.secret) == 32:
    144  - kc.kerberos_key_aes_128 = creds.secret
    145  - elif len(creds.secret) == 64:
    146  - kc.kerberos_key_aes_256 = creds.secret
    147  -
    148  - elif creds.secret_type == RDPCredentialsSecretType.RC4:
    149  - kc.kerberos_key_rc4 = creds.secret
    150  - 
    151  - if kc is None:
    152  - raise Exception('No suitable secret type found to set up kerberos!')
    153  -
    154  -
    155  - kcred = RDPKerberosCredential()
    156  - kcred.ccred = kc
    157  - kcred.spn = KerberosSPN.from_target_string(target.to_target_string())
    158  -
    159  - if target.proxy is not None:
    160  - if target.proxy.type == RDPProxyType.ASYSOCKS:
    161  - kcred.target = KerberosTarget(target.dc_ip)
    162  - kcred.target.proxy = KerberosProxy()
    163  - kcred.target.proxy.target = copy.deepcopy(target.proxy.target)
    164  - kcred.target.proxy.target[-1].endpoint_ip = target.dc_ip
    165  - kcred.target.proxy.target[-1].endpoint_port = 88
    166  -
    167  - else:
    168  - raise NotImplementedError("Proxy type '%s' not implemented!" % target.proxy.type)
    169  - 
    170  - else:
    171  - kcred.target = KerberosTarget(target.dc_ip)
    172  - handler = RDPKerberos(kcred)
    173  - #setting up SPNEGO
    174  - spneg = SPNEGO()
    175  - spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
    176  - return AuthenticatorBuilder.wrap_credssp(spneg, 'KERBEROS', kc)
    177  -
    178  - elif creds.authentication_type == RDPAuthProtocol.SSPI_KERBEROS:
    179  - if target is None:
    180  - raise Exception('Target must be specified with Kerberos SSPI!')
    181  -
    182  - kerbcred = RDPKerberosSSPICredential()
    183  - kerbcred.client = None #creds.username #here we could submit the domain as well for impersonation? TODO!
    184  - kerbcred.password = creds.secret
    185  - kerbcred.target = target.to_target_string()
    186  -
    187  - handler = RDPKerberosSSPI(kerbcred)
    188  - #setting up SPNEGO
    189  - spneg = SPNEGO()
    190  - spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
    191  - return AuthenticatorBuilder.wrap_credssp(spneg, 'SSPI-KERBEROS', kerbcred)
    192  -
    193  - elif creds.authentication_type == RDPAuthProtocol.SSPI_NTLM:
    194  - ntlmcred = RDPNTLMSSPICredential()
    195  - ntlmcred.client = creds.username #here we could submit the domain as well for impersonation? TODO!
    196  - ntlmcred.password = creds.secret
    197  -
    198  - handler = RDPNTLMSSPI(ntlmcred)
    199  - #setting up SPNEGO
    200  - spneg = SPNEGO()
    201  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    202  - return AuthenticatorBuilder.wrap_credssp(spneg, 'SSPI-NTLM', ntlmcred)
    203  - 
    204  - elif creds.authentication_type.value.startswith('MPN'):
    205  - if creds.authentication_type in [RDPAuthProtocol.MPN_SSL_NTLM, RDPAuthProtocol.MPN_NTLM]:
    206  - from aardwolf.authentication.ntlm.mpn import RDPNTLMMPN
    207  - ntlmcred = RDPMPNCredential()
    208  - ntlmcred.type = 'NTLM'
    209  - ntlmcred.is_ssl = True if creds.authentication_type == RDPAuthProtocol.MPN_SSL_NTLM else False
    210  - ntlmcred.parse_settings(creds.settings)
    211  -
    212  - handler = RDPNTLMMPN(ntlmcred)
    213  - #setting up SPNEGO
    214  - spneg = SPNEGO()
    215  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    216  - return AuthenticatorBuilder.wrap_credssp(spneg, 'MPN-NTLM', ntlmcred)
    217  - 
    218  - elif creds.authentication_type in [RDPAuthProtocol.MPN_SSL_KERBEROS, RDPAuthProtocol.MPN_KERBEROS]:
    219  - from aardwolf.authentication.kerberos.mpn import RDPKerberosMPN
    220  - 
    221  - ntlmcred = RDPMPNCredential()
    222  - ntlmcred.type = 'KERBEROS'
    223  - ntlmcred.target = creds.target
    224  - if creds.username is not None:
    225  - ntlmcred.username = '<CURRENT>'
    226  - if creds.domain is not None:
    227  - ntlmcred.domain = '<CURRENT>'
    228  - if creds.secret is not None:
    229  - ntlmcred.password = '<CURRENT>'
    230  - ntlmcred.is_guest = False
    231  - ntlmcred.is_ssl = True if creds.authentication_type == RDPAuthProtocol.MPN_SSL_KERBEROS else False
    232  - ntlmcred.parse_settings(creds.settings)
    233  - 
    234  - handler = RDPKerberosMPN(ntlmcred)
    235  - #setting up SPNEGO
    236  - spneg = SPNEGO()
    237  - spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
    238  - return AuthenticatorBuilder.wrap_credssp(spneg, 'MPN-KERBEROS', ntlmcred)
    239  - 
    240  -
    241  - 
    242  - elif creds.authentication_type.value.startswith('MULTIPLEXOR'):
    243  - if creds.authentication_type in [RDPAuthProtocol.MULTIPLEXOR_SSL_NTLM, RDPAuthProtocol.MULTIPLEXOR_NTLM]:
    244  - from aardwolf.authentication.ntlm.multiplexor import RDPNTLMMultiplexor
    245  - 
    246  - ntlmcred = RDPMultiplexorCredential()
    247  - ntlmcred.type = 'NTLM'
    248  - if creds.username is not None:
    249  - ntlmcred.username = '<CURRENT>'
    250  - if creds.domain is not None:
    251  - ntlmcred.domain = '<CURRENT>'
    252  - if creds.secret is not None:
    253  - ntlmcred.password = '<CURRENT>'
    254  - ntlmcred.is_guest = False
    255  - ntlmcred.is_ssl = True if creds.authentication_type == RDPAuthProtocol.MULTIPLEXOR_SSL_NTLM else False
    256  - ntlmcred.parse_settings(creds.settings)
    257  -
    258  - handler = RDPNTLMMultiplexor(ntlmcred)
    259  - #setting up SPNEGO
    260  - spneg = SPNEGO()
    261  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    262  - return AuthenticatorBuilder.wrap_credssp(spneg, 'MULTIPLEXOR-NTLM', ntlmcred)
    263  - 
    264  - elif creds.authentication_type in [RDPAuthProtocol.MULTIPLEXOR_SSL_KERBEROS, RDPAuthProtocol.MULTIPLEXOR_KERBEROS]:
    265  - from aardwolf.authentication.kerberos.multiplexor import RDPKerberosMultiplexor
    266  - 
    267  - ntlmcred = RDPMultiplexorCredential()
    268  - ntlmcred.type = 'KERBEROS'
    269  - ntlmcred.target = creds.target
    270  - if creds.username is not None:
    271  - ntlmcred.username = '<CURRENT>'
    272  - if creds.domain is not None:
    273  - ntlmcred.domain = '<CURRENT>'
    274  - if creds.secret is not None:
    275  - ntlmcred.password = '<CURRENT>'
    276  - ntlmcred.is_guest = False
    277  - ntlmcred.is_ssl = True if creds.authentication_type == RDPAuthProtocol.MULTIPLEXOR_SSL_NTLM else False
    278  - ntlmcred.parse_settings(creds.settings)
    279  - 
    280  - handler = RDPKerberosMultiplexor(ntlmcred)
    281  - #setting up SPNEGO
    282  - spneg = SPNEGO()
    283  - spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
    284  - return AuthenticatorBuilder.wrap_credssp(spneg, 'MULTIPLEXOR-KERBEROS', ntlmcred)
    285  - 
    286  - elif creds.authentication_type.value.startswith('WSNET'):
    287  - if creds.authentication_type in [RDPAuthProtocol.WSNET_NTLM]:
    288  - from aardwolf.authentication.ntlm.wsnet import RDPWSNetNTLMAuth
    289  -
    290  - ntlmcred = RDPWSNETCredential()
    291  - ntlmcred.type = 'NTLM'
    292  - if creds.username is not None:
    293  - ntlmcred.username = '<CURRENT>'
    294  - if creds.domain is not None:
    295  - ntlmcred.domain = '<CURRENT>'
    296  - if creds.secret is not None:
    297  - ntlmcred.password = '<CURRENT>'
    298  - ntlmcred.is_guest = False
    299  -
    300  - handler = RDPWSNetNTLMAuth(ntlmcred)
    301  - spneg = SPNEGO()
    302  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    303  - return AuthenticatorBuilder.wrap_credssp(spneg, 'WSNET-NTLM', ntlmcred)
    304  -
    305  - 
    306  - elif creds.authentication_type in [RDPAuthProtocol.WSNET_KERBEROS]:
    307  - from aardwolf.authentication.kerberos.wsnet import RDPWSNetKerberosAuth
    308  - 
    309  - ntlmcred = RDPWSNETCredential()
    310  - ntlmcred.type = 'KERBEROS'
    311  - ntlmcred.target = creds.target
    312  - if creds.username is not None:
    313  - ntlmcred.username = '<CURRENT>'
    314  - if creds.domain is not None:
    315  - ntlmcred.domain = '<CURRENT>'
    316  - if creds.secret is not None:
    317  - ntlmcred.password = '<CURRENT>'
    318  - ntlmcred.is_guest = False
    319  - 
    320  - handler = RDPWSNetKerberosAuth(ntlmcred)
    321  - #setting up SPNEGO
    322  - spneg = SPNEGO()
    323  - spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
    324  - return AuthenticatorBuilder.wrap_credssp(spneg, 'WSNET-KERBEROS', ntlmcred)
    325  -
    326  - elif creds.authentication_type.value.startswith('SSPIPROXY'):
    327  - if creds.authentication_type == RDPAuthProtocol.SSPIPROXY_NTLM:
    328  - from aardwolf.authentication.ntlm.sspiproxy import RDPSSPIProxyNTLMAuth
    329  -
    330  - ntlmcred = RDPSSPIProxyCredential()
    331  - ntlmcred.type = 'NTLM'
    332  - if creds.username is not None:
    333  - ntlmcred.username = '<CURRENT>'
    334  - if creds.domain is not None:
    335  - ntlmcred.domain = '<CURRENT>'
    336  - if creds.secret is not None:
    337  - ntlmcred.password = '<CURRENT>'
    338  - ntlmcred.is_guest = False
    339  - ntlmcred.host = creds.settings['host'][0]
    340  - ntlmcred.port = int(creds.settings['port'][0])
    341  - ntlmcred.proto = 'ws'
    342  - if 'proto' in creds.settings:
    343  - ntlmcred.proto = creds.settings['proto'][0]
    344  - if 'agentid' in creds.settings:
    345  - ntlmcred.agent_id = bytes.fromhex(creds.settings['agentid'][0])
    346  -
    347  - handler = RDPSSPIProxyNTLMAuth(ntlmcred)
    348  - spneg = SPNEGO()
    349  - spneg.add_auth_context('NTLMSSP - Microsoft NTLM Security Support Provider', handler)
    350  - return AuthenticatorBuilder.wrap_credssp(spneg, 'SSPIPROXY-NTLM', ntlmcred)
    351  -
    352  - 
    353  - elif creds.authentication_type == RDPAuthProtocol.SSPIPROXY_KERBEROS:
    354  - from aardwolf.authentication.kerberos.sspiproxy import RDPSSPIProxyKerberosAuth
    355  - 
    356  - ntlmcred = RDPSSPIProxyCredential()
    357  - ntlmcred.type = 'KERBEROS'
    358  - ntlmcred.target = creds.target
    359  - if creds.username is not None:
    360  - ntlmcred.username = '<CURRENT>'
    361  - if creds.domain is not None:
    362  - ntlmcred.domain = '<CURRENT>'
    363  - if creds.secret is not None:
    364  - ntlmcred.password = '<CURRENT>'
    365  - ntlmcred.is_guest = False
    366  - ntlmcred.host = creds.settings['host'][0]
    367  - ntlmcred.port = int(creds.settings['port'][0])
    368  - ntlmcred.proto = 'ws'
    369  - if 'proto' in creds.settings:
    370  - ntlmcred.proto = creds.settings['proto'][0]
    371  - if 'agentid' in creds.settings:
    372  - ntlmcred.agent_id = bytes.fromhex(creds.settings['agentid'][0])
    373  - 
    374  - handler = RDPSSPIProxyKerberosAuth(ntlmcred)
    375  - #setting up SPNEGO
    376  - spneg = SPNEGO()
    377  - spneg.add_auth_context('MS KRB5 - Microsoft Kerberos 5', handler)
    378  - return AuthenticatorBuilder.wrap_credssp(spneg, 'SSPIPROXY-KERBEROS', ntlmcred)
    379  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/commons/credential.py
    1  -import enum
    2  -import platform
    3  -import uuid
    4  - 
    5  -from aardwolf.commons.target import RDPTarget
    6  - 
    7  - 
    8  -class RDPCredentialTypes(enum.Enum):
    9  - NTLM_NT = 'ntlm-nt'
    10  - NTLM_PASSWORD = 'ntlm-password'
    11  - NTLM_PWPROMPT = 'ntlm-pwprompt'
    12  - NTLM_PWHEX = 'ntlm-pwhex'
    13  - NTLM_PWB64 = 'ntlm-pwb64'
    14  - SSPI_NTLM = 'sspi-ntlm'
    15  - SSPI_KERBEROS = 'sspi-kerberos'
    16  - MULTIPLEXOR_NTLM = 'multiplexor-ntlm'
    17  - MULTIPLEXOR_KERBEROS = 'multiplexor-kerberos'
    18  - KERBEROS_PASSWORD = 'kerberos-password'
    19  - KERBEROS_PWPROMPT = 'kerberos-pwprompt'
    20  - KERBEROS_PWHEX = 'kerberos-pwhex'
    21  - KERBEROS_PWB64 = 'kerberos-pwb64'
    22  - KERBEROS_AES = 'kerberos-aes'
    23  - KERBEROS_RC4 = 'kerberos-rc4'
    24  - KERBEROS_CCACHE = 'kerberos-ccache'
    25  - KERBEROS_KIRBI = 'kerberos-kirbi'
    26  - NEGOEX_CERTFILE = 'negoex_certfile'
    27  - 
    28  -class RDPCredentialsSecretType(enum.Enum):
    29  - NT = 'NT'
    30  - PASSWORD = 'PASSWORD'
    31  - PWPROMPT = 'PWPROMPT'
    32  - PWHEX = 'PWHEX'
    33  - PWB64 = 'PWB64'
    34  - AES = 'AES'
    35  - RC4 = 'RC4'
    36  - CCACHE = 'CCACHE'
    37  - KEYTAB = 'KEYTAB'
    38  - KIRBI = 'KIRBI'
    39  - NONE = 'NONE'
    40  - CERTFILE = 'CERTFILE'
    41  - CERTSTORE = 'CERTSTORE'
    42  - 
    43  -class RDPAuthProtocol(enum.Enum):
    44  - NONE = 'NONE'
    45  - PLAIN = 'PLAIN'
    46  - NTLM = 'NTLM'
    47  - KERBEROS = 'KERBEROS'
    48  - SSPI_KERBEROS = 'SSPI_KERBEROS'
    49  - SSPI_NTLM = 'SSPI_NTLM'
    50  - MULTIPLEXOR_NTLM = 'MULTIPLEXOR_NTLM'
    51  - MULTIPLEXOR_KERBEROS = 'MULTIPLEXOR_KERBEROS'
    52  - MULTIPLEXOR_SSL_NTLM = 'MULTIPLEXOR_SSL_NTLM'
    53  - MULTIPLEXOR_SSL_KERBEROS = 'MULTIPLEXOR_SSL_KERBEROS'
    54  - MPN_SSL_NTLM = 'MPN_SSL_NTLM'
    55  - MPN_NTLM = 'MPN_NTLM'
    56  - MPN_SSL_KERBEROS = 'MPN_SSL_KERBEROS'
    57  - MPN_KERBEROS = 'MPN_KERBEROS'
    58  - WSNET_NTLM = 'WSNET_NTLM'
    59  - WSNET_KERBEROS = 'WSNET_KERBEROS'
    60  - SSPIPROXY_KERBEROS = 'SSPIPROXY_KERBEROS'
    61  - SSPIPROXY_NTLM = 'SSPIPROXY_NTLM'
    62  - NEGOEX = 'NEGOEX'
    63  - 
    64  -class RDPCredential:
    65  - def __init__(self,
    66  - username:str = None,
    67  - domain:str = None,
    68  - secret:str = None,
    69  - secret_type:RDPCredentialsSecretType = None,
    70  - authentication_type:RDPAuthProtocol = None,
    71  - settings = None,
    72  - target:RDPTarget = None):
    73  - self.username = username
    74  - self.domain = domain
    75  - self.secret = secret
    76  - self.secret_type = secret_type #RDPCredentialsSecretType
    77  - self.target = target #for kerberos authentication
    78  -
    79  - self.authentication_type = authentication_type #kerberos or NTLM or ...
    80  - self.settings = settings
    81  -
    82  - #domain/user/auth_type/secret_type:secret@target_ip_hostname_fqdn:target_port/dc_ip
    83  - 
    84  - def __str__(self):
    85  - t = '==== RDPCredential ====\r\n'
    86  - for k in self.__dict__:
    87  - t += '%s: %s\r\n' % (k, self.__dict__[k])
    88  -
    89  - return t
    90  -
    91  - @staticmethod
    92  - def from_credential_string(s):
    93  - """
    94  - Making users life more conveinent
    95  - """
    96  - return RDPCredential.from_connection_string(s + '@')
    97  - 
    98  - @staticmethod
    99  - def from_connection_string(s):
    100  - creds = RDPCredential()
    101  -
    102  - t, target = s.rsplit('@', 1)
    103  - creds.domain, t = t.split('/', 1)
    104  - creds.username, t = t.split('/', 1)
    105  - if t.find('/') != -1:
    106  - auth_type , t = t.split('/', 1)
    107  - secret_type, creds.secret = t.split(':',1)
    108  - creds.secret_type = RDPCredentialsSecretType(secret_type.upper())
    109  - else:
    110  - auth_type = t
    111  - creds.secret_type = RDPCredentialsSecretType.NONE
    112  - creds.authentication_type = RDPAuthProtocol(auth_type.upper().replace('-','_'))
    113  - 
    114  - #sanity check
    115  - if creds.secret_type == [RDPCredentialsSecretType.NT, RDPCredentialsSecretType.RC4]:
    116  - try:
    117  - bytes.fromhex(creds.secret)
    118  - if len(creds.secret) != 32:
    119  - raise Exception()
    120  - except Exception as e:
    121  - raise Exception('This is not a valid NT hash')
    122  -
    123  - elif creds.secret_type == RDPCredentialsSecretType.AES:
    124  - try:
    125  - bytes.fromhex(creds.secret)
    126  - if len(creds.secret) != 32 or len(creds.secret) != 64:
    127  - raise Exception()
    128  - except Exception as e:
    129  - raise Exception('This is not a valid NT hash')
    130  -
    131  - elif creds.secret_type == RDPCredentialsSecretType.CCACHE:
    132  - try:
    133  - with open(creds.secret, 'rb') as f:
    134  - a = 1
    135  - except Exception as e:
    136  - raise Exception('Could not open CCACHE file!')
    137  -
    138  -
    139  - if creds.authentication_type == RDPAuthProtocol.NTLM:
    140  - if creds.secret_type not in [RDPCredentialsSecretType.NT, RDPCredentialsSecretType.RC4, RDPCredentialsSecretType.PASSWORD]:
    141  - raise Exception('NTLM authentication requires either password or NT hash or RC4 key to be specified as secret!')
    142  -
    143  - elif creds.authentication_type == RDPAuthProtocol.KERBEROS:
    144  - if creds.secret_type not in [RDPCredentialsSecretType.NT, RDPCredentialsSecretType.RC4, RDPCredentialsSecretType.PASSWORD, RDPCredentialsSecretType.AES, RDPCredentialsSecretType.CCACHE]:
    145  - raise Exception('KERBEROS authentication requires either password or NT hash or RC4 key or AES key or CCACHE file to be specified as secret!')
    146  -
    147  - elif creds.authentication_type in [RDPAuthProtocol.SSPI_NTLM, RDPAuthProtocol.SSPI_KERBEROS]:
    148  - if platform.system() != 'Windows':
    149  - raise Exception('SSPI authentication on works on windows!')
    150  -
    151  - return creds
    152  -
    153  -class RDPKerberosCredential:
    154  - def __init__(self):
    155  - self.mode = 'CLIENT'
    156  - self.connection = None #KerberosCredential
    157  - self.target = None #KerberosTarget
    158  - self.ksoc = None #KerberosSocketAIO
    159  - self.ccred = None
    160  -
    161  -class RDPKerberosSSPICredential:
    162  - def __init__(self):
    163  - self.mode = 'CLIENT'
    164  - self.client = None
    165  - self.password = None
    166  - self.target = None
    167  -
    168  -class RDPNTLMSSPICredential:
    169  - def __init__(self):
    170  - self.mode = 'CLIENT'
    171  - self.client = None
    172  - self.passwrd = None
    173  -
    174  -class RDPNTLMCredential:
    175  - def __init__(self):
    176  - self.username = None
    177  - self.domain = ''
    178  - self.password = None
    179  - self.workstation = None
    180  - self.is_guest = False
    181  - self.nt_hash = None
    182  - self.lm_hash = None
    183  - 
    184  -class RDPWSNETCredential:
    185  - def __init__(self):
    186  - self.mode = 'CLIENT'
    187  - self.type = 'NTLM'
    188  - self.username = '<CURRENT>'
    189  - self.domain = '<CURRENT>'
    190  - self.password = '<CURRENT>'
    191  - self.target = None
    192  - self.is_guest = False
    193  - 
    194  -class RDPSSPIProxyCredential:
    195  - def __init__(self):
    196  - self.mode = 'CLIENT'
    197  - self.type = 'NTLM'
    198  - self.username = '<CURRENT>'
    199  - self.domain = '<CURRENT>'
    200  - self.password = '<CURRENT>'
    201  - self.target = None
    202  - self.is_guest = False
    203  - self.host = '127.0.0.1'
    204  - self.port = 9999
    205  - self.proto = 'ws'
    206  - self.agent_id = None
    207  - 
    208  -class RDPMultiplexorCredential:
    209  - def __init__(self):
    210  - self.mode = 'CLIENT'
    211  - self.type = 'NTLM'
    212  - self.username = '<CURRENT>'
    213  - self.domain = '<CURRENT>'
    214  - self.password = '<CURRENT>'
    215  - self.target = None
    216  - self.is_guest = False
    217  - self.is_ssl = False
    218  - self.mp_host = None
    219  - self.mp_port = None
    220  - self.mp_username = None
    221  - self.mp_domain = None
    222  - self.mp_password = None
    223  - self.agent_id = None
    224  - 
    225  - def get_url(self):
    226  - url_temp = 'ws://%s:%s'
    227  - if self.is_ssl is True:
    228  - url_temp = 'wss://%s:%s'
    229  - url = url_temp % (self.mp_host, self.mp_port)
    230  - return url
    231  - 
    232  - def parse_settings(self, settings):
    233  - self.mp_host = settings['host'][0]
    234  - self.mp_port = settings['port'][0]
    235  - if self.mp_port is None:
    236  - self.mp_port = '9999'
    237  - if 'user' in settings:
    238  - self.mp_username = settings.get('user')[0]
    239  - if 'domain' in settings:
    240  - self.mp_domain = settings.get('domain')[0]
    241  - if 'password' in settings:
    242  - self.mp_password = settings.get('password')[0]
    243  - self.agent_id = settings['agentid'][0]
    244  - 
    245  -class RDPMPNCredential:
    246  - def __init__(self):
    247  - self.mode = 'CLIENT'
    248  - self.type = 'NTLM'
    249  - self.username = '<CURRENT>'
    250  - self.domain = '<CURRENT>'
    251  - self.password = '<CURRENT>'
    252  - self.target = None
    253  - self.is_guest = False
    254  - self.is_ssl = False
    255  - self.mp_host = None
    256  - self.mp_port = 9999
    257  - self.mp_username = None
    258  - self.mp_domain = None
    259  - self.mp_password = None
    260  - self.agent_id = None
    261  - self.operator = None
    262  - self.timeout = 5
    263  - 
    264  - 
    265  - def get_url(self):
    266  - url_temp = 'ws://%s:%s'
    267  - if self.is_ssl is True:
    268  - url_temp = 'wss://%s:%s'
    269  - url = url_temp % (self.mp_host, self.mp_port)
    270  - return url
    271  - 
    272  - def parse_settings(self, settings):
    273  - self.mp_host = settings['host'][0]
    274  - if 'port' in settings:
    275  - self.mp_port = settings.get['port'][0]
    276  - if 'user' in settings:
    277  - self.mp_username = settings.get('user')[0]
    278  - if 'domain' in settings:
    279  - self.mp_domain = settings.get('domain')[0]
    280  - if 'password' in settings:
    281  - self.mp_password = settings.get('password')[0]
    282  - self.agent_id = uuid.UUID(settings['agentid'][0])
    283  - 
    284  -def test():
    285  - s = 'TEST/victim/ntlm/nt:[email protected]:3389'
    286  - creds = RDPCredential.from_connection_string(s)
    287  - print(str(creds))
    288  -
    289  - s = 'TEST/victim/[email protected]:3389/aaaa'
    290  - creds = RDPCredential.from_connection_string(s)
    291  -
    292  - print(str(creds))
    293  -
    294  -if __name__ == '__main__':
    295  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/commons/factory.py
     1 +import enum
     2 +from typing import List
     3 +from asysocks.unicomm.common.proxy import UniProxyProto, UniProxyTarget
     4 +from aardwolf.commons.iosettings import RDPIOSettings
     5 + 
     6 +from aardwolf.commons.target import RDPTarget, RDPConnectionDialect
     7 +from uniauth.common.credentials import UniCredential
     8 +from aardwolf.connection import RDPConnection
     9 +from aardwolf.vncconnection import VNCConnection
     10 +import ipaddress
     11 +import copy
     12 + 
     13 + 
     14 +class RDPConnectionFactory:
     15 + def __init__(self, iosettings=None, target:RDPTarget = None, credential:UniCredential = None, proxies:List[UniProxyTarget] = None):
     16 + self.credential = credential
     17 + self.target = target
     18 + self.proxies = proxies
     19 + self.iosettings = iosettings
     20 + 
     21 + def get_connection(self, iosettings):
     22 + tio = copy.deepcopy(iosettings)
     23 + credential = self.get_credential()
     24 + target = self.get_target()
     25 + if target.dialect is not None:
     26 + if target.dialect == RDPConnectionDialect.RDP:
     27 + return RDPConnection(target, credential, tio)
     28 + elif target.dialect == RDPConnectionDialect.VNC:
     29 + return VNCConnection(target, credential, tio)
     30 + else:
     31 + raise Exception('Unknown dialect %s' % target.dialect)
     32 + raise Exception('Either target or dialect must be defined first!')
     33 + 
     34 + def create_connection_newtarget(self, ip_or_hostname, iosettings):
     35 + tio = copy.deepcopy(iosettings)
     36 + credential = self.get_credential()
     37 + 
     38 + target = self.get_target()
     39 + try:
     40 + ipaddress.ip_address(ip_or_hostname)
     41 + target.ip = ip_or_hostname
     42 + target.hostname = None
     43 + except:
     44 + target.hostname = ip_or_hostname
     45 + target.ip = ip_or_hostname
     46 + 
     47 + if target.dialect == RDPConnectionDialect.RDP:
     48 + return RDPConnection(target, credential, tio)
     49 + elif target.dialect == RDPConnectionDialect.VNC:
     50 + return VNCConnection(target, credential, tio)
     51 + else:
     52 + raise Exception('Unknown dialect %s' % target.dialect)
     53 + 
     54 + def get_proxy(self) -> List[UniProxyTarget]:
     55 + return copy.deepcopy(self.target.proxies)
     56 + 
     57 + def get_target(self) -> RDPTarget:
     58 + return copy.deepcopy(self.target)
     59 + 
     60 + def get_credential(self) -> UniCredential:
     61 + return copy.deepcopy(self.credential)
     62 +
     63 + def get_settings(self) -> RDPIOSettings:
     64 + return copy.deepcopy(self.iosettings)
     65 + 
     66 + @staticmethod
     67 + def from_url(connection_url, iosettings):
     68 + target = RDPTarget.from_url(connection_url)
     69 + credential = UniCredential.from_url(connection_url)
     70 + return RDPConnectionFactory(iosettings, target, credential)
     71 + 
     72 + 
     73 + def __str__(self):
     74 + t = '==== RDPConnectionFactory ====\r\n'
     75 + for k in self.__dict__:
     76 + val = self.__dict__[k]
     77 + if isinstance(val, enum.IntFlag):
     78 + val = val
     79 + elif isinstance(val, enum.Enum):
     80 + val = val.name
     81 +
     82 + t += '%s: %s\r\n' % (k, str(val))
     83 +
     84 + return t
     85 + 
     86 +
     87 +
     88 +if __name__ == '__main__':
     89 + url_tests = [
     90 + #'RDP://10.10.10.2',
     91 + #'RDP://10.10.10.2:9000',
     92 + #'RDP-tcp://10.10.10.2',
     93 + #'RDP-tcp://10.10.10.2:9000',
     94 + #'RDP-udp://10.10.10.2:138',
     95 + #'RDP+ntlm-password://domain\\[email protected]',
     96 + #'RDP-tcp+ntlm-password://domain\\user:[email protected]',
     97 + #'RDP-tcp+ntlm-password://domain\\user:[email protected]:10000',
     98 + #'RDP-tcp+ntlm-nt://domain\\user:[email protected]',
     99 + #'RDP+ntlm-nt://domain\\user:[email protected]',
     100 + #'RDP+ntlm-nt://domain\\user:[email protected]',
     101 + #'RDP-tcp+kerberos-password://domain\\alma:[email protected]',
     102 + #'RDP-tcp+kerberos-aes://domain\\alma:[email protected]',
     103 + #'RDP-tcp+kerberos-aes://domain\\alma:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@10.10.10.10.2',
     104 + #'RDP-tcp+kerberos-nt://domain\\alma:[email protected]',
     105 + #'RDP-tcp+kerberos-rc4://domain\\alma:[email protected]',
     106 + #'RDP+sspi://10.10.10.10.2',
     107 + #'RDP+sspi-ntlm://10.10.10.10.2',
     108 + #'RDP+sspi-kerberos://10.10.10.10.2',
     109 + #'RDP+multiplexor://10.10.10.10.2',
     110 + #'RDP+multiplexor-ssl://10.10.10.10.2',
     111 + #'RDP+multiplexor-ssl-ntlm://10.10.10.10.2',
     112 + #'RDP+multiplexor-ssl-kerberos://10.10.10.10.2',
     113 + #'RDP://10.10.10.2/?timeout=10',
     114 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2',
     115 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5',
     116 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1',
     117 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma',
     118 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma&dc=10.10.10.2&dns=8.8.8.8',
     119 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5-ssl&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma&dc=10.10.10.2&dns=8.8.8.8',
     120 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyserver=127.0.0.1',
     121 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyserver=127.0.0.1&proxyagentid=alma',
     122 + #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyserver=127.0.0.1&proxyagentid=alma&proxytimeout=111',
     123 + 'RDP://10.10.10.2/C$/test/tst111.dmp?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyhost=127.0.0.1&proxyport=1&proxyagentid=alma&proxytimeout=111',
     124 + 
     125 + ]
     126 + for url in url_tests:
     127 + print('===========================================================================')
     128 + print(url)
     129 + try:
     130 + dec = RDPConnectionFactory.from_url(url)
     131 + creds = dec.get_credential()
     132 + target = dec.get_target()
     133 + RDPfile = dec.get_file()
     134 + print(RDPfile)
     135 + except Exception as e:
     136 + import traceback
     137 + traceback.print_exc()
     138 + print('ERROR! Reason: %s' % e)
     139 + input()
     140 + else:
     141 + print(str(creds))
     142 + print(str(target))
     143 + input()
     144 +
     145 +
     146 + 
  • ■ ■ ■ ■ ■ ■
    aardwolf/commons/proxy.py
    1  -import ipaddress
    2  -import enum
    3  -from urllib.parse import urlparse, parse_qs
    4  - 
    5  -from asysocks.common.clienturl import SocksClientURL
    6  - 
    7  - 
    8  -def stru(x):
    9  - return str(x).upper()
    10  - 
    11  -class RDPProxySecretType(enum.Enum):
    12  - NONE = 'NONE'
    13  - PLAIN = 'PLAIN'
    14  - 
    15  -class RDPProxyType(enum.Enum):
    16  - ASYSOCKS = 'ASYSOCKS'
    17  - 
    18  -class RDPProxy:
    19  - def __init__(self):
    20  - self.type:RDPProxyType = None
    21  - self.target = None
    22  - 
    23  - @staticmethod
    24  - def from_params(url_str):
    25  - proxy = RDPProxy()
    26  - url = urlparse(url_str)
    27  - if url.query is None:
    28  - return None
    29  - 
    30  - query = parse_qs(url.query)
    31  - if 'proxytype' not in query and 'sametype' not in query:
    32  - return None
    33  - 
    34  - if query['proxytype'][0].upper() in ['SOCKS5', 'SOCKS4', 'SOCKS4A', 'SOCKS5-SSL', 'SOCKS4-SSL', 'SOCKS4A-SSL', 'WSNET', 'WSNETWS', 'WSNETWSS']:
    35  - proxy.type = RDPProxyType.ASYSOCKS
    36  - else:
    37  - raise NotImplementedError(query['proxytype'][0].upper())
    38  -
    39  - if proxy.type == RDPProxyType.ASYSOCKS:
    40  - cu = SocksClientURL.from_params(url_str)
    41  - cu[-1].endpoint_port = 3389
    42  - proxy.target = cu
    43  - else:
    44  - raise NotImplementedError('Unknown proxy type "%s"' % proxy.type)
    45  -
    46  - return proxy
    47  - 
    48  - def __str__(self):
    49  - t = '==== RDPProxy ====\r\n'
    50  - for k in self.__dict__:
    51  - t += '%s: %s\r\n' % (k, self.__dict__[k])
    52  -
    53  - return t
    54  - 
    55  -def test():
    56  - t = ['socks5://10.10.10.1',
    57  - 'socks5+ssl://10.10.10.1',
    58  - 'socks5+ssl://admin:[email protected]',
    59  - ]
    60  - for x in t:
    61  - s = RDPProxy.from_connection_string(x)
    62  - print(str(s))
    63  - 
    64  -
    65  -if __name__ == '__main__':
    66  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/commons/target.py
    skipped 9 lines
    10 10  import ipaddress
    11 11  import enum
    12 12  import copy
    13  -from aardwolf.commons.proxy import RDPProxy
     13 +from typing import List
     14 +from urllib.parse import urlparse, parse_qs
     15 + 
     16 +from asysocks.unicomm.common.target import UniTarget, UniProto
     17 +from asysocks.unicomm.common.proxy import UniProxyProto, UniProxyTarget
    14 18   
    15 19  class RDPConnectionDialect(enum.Enum):
    16 20   RDP = 'RDP'
    17 21   VNC = 'VNC'
    18 22   
    19  -class RDPConnectionProtocol(enum.Enum):
    20  - TCP = 'TCP'
     23 +rdpurlconnection_param2var = {
     24 + 'RDP' : UniProto.CLIENT_TCP,
     25 + 'VNC' : UniProto.CLIENT_TCP,
     26 +}
    21 27   
    22  -class RDPTarget:
     28 +class RDPTarget(UniTarget):
    23 29   """
    24 30   """
    25 31   def __init__(
    skipped 4 lines
    30 36   timeout:int = 1,
    31 37   dc_ip:int=None,
    32 38   domain:str = None,
    33  - proxy:RDPProxy = None,
    34  - protocol:RDPConnectionProtocol = RDPConnectionProtocol.TCP,
    35  - serverip = None,
    36  - dialect:RDPConnectionDialect = RDPConnectionDialect.RDP):
    37  -
    38  - self.ip = ip
    39  - self.port = port
    40  - self.hostname = hostname
    41  - self.timeout = timeout
    42  - self.dc_ip = dc_ip
    43  - self.domain = domain
    44  - self.proxy = proxy
    45  - self.protocol = protocol
    46  - self.serverip = serverip
     39 + proxies:List[UniProxyTarget] = None,
     40 + protocol:UniProto = UniProto.CLIENT_TCP,
     41 + dialect:RDPConnectionDialect = RDPConnectionDialect.RDP,
     42 + dns:str = None):
     43 + UniTarget.__init__(self, ip, port, protocol, timeout, hostname = hostname, proxies = proxies, domain = domain, dc_ip = dc_ip, dns=dns)
    47 44   self.dialect = dialect
    48 45   if self.dialect == RDPConnectionDialect.VNC:
    49 46   self.port = 5900
    skipped 11 lines
    61 58   domain = self.domain,
    62 59   proxy = copy.deepcopy(self.proxy),
    63 60   protocol = self.protocol,
    64  - serverip = self.serverip,
    65  - dialect = self.dialect
     61 + dialect = self.dialect,
     62 + dns=self.dns
    66 63   )
    67 64   return t
    68 65   
    69 66   @staticmethod
    70  - def from_connection_string(s, is_rdp = True):
    71  - port = 3389
    72  - if is_rdp is False:
    73  - port = 5900
    74  -
    75  - dc = None
    76  -
    77  - _, target = s.rsplit('@', 1)
    78  - if target.find('/') != -1:
    79  - target, dc = target.split('/')
     67 + def from_url(connection_url):
     68 + url_e = urlparse(connection_url)
     69 + schemes = url_e.scheme.upper().split('+')
     70 + connection_tags = schemes[0].split('-')
     71 + if len(connection_tags) > 1:
     72 + dialect = RDPConnectionDialect(connection_tags[0])
     73 + protocol = rdpurlconnection_param2var[connection_tags[1]]
     74 + else:
     75 + dialect = RDPConnectionDialect(connection_tags[0])
     76 + protocol = UniProto.CLIENT_TCP
    80 77  
    81  - if target.find(':') != -1:
    82  - target, port = target.split(':')
     78 + if url_e.port:
     79 + port = url_e.port
     80 + elif dialect == RDPConnectionDialect.RDP:
     81 + port = 3389
     82 + elif dialect == RDPConnectionDialect.VNC:
     83 + port = 5800
     84 + else:
     85 + raise Exception('Port must be provided!')
    83 86  
    84  - st = RDPTarget()
    85  - st.port = port
    86  - st.dc_ip = dc
    87  - st.domain, _ = s.split('/', 1)
     87 + unitarget, extraparams = UniTarget.from_url(connection_url, protocol, port)
    88 88   
    89  - try:
    90  - st.ip = str(ipaddress.ip_address(target))
    91  - except:
    92  - st.hostname = target
     89 + target = RDPTarget(
     90 + ip = unitarget.ip,
     91 + port = unitarget.port,
     92 + hostname = unitarget.hostname,
     93 + timeout = unitarget.timeout,
     94 + dc_ip = unitarget.dc_ip,
     95 + domain = unitarget.domain,
     96 + proxies = unitarget.proxies,
     97 + protocol = unitarget.protocol,
     98 + dns = unitarget.dns,
     99 + )
    93 100  
    94  - return st
     101 + return target
    95 102  
    96 103   def get_ip(self):
    97 104   if not self.ip and not self.hostname:
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    aardwolf/commons/url.py
    1  -import enum
    2  -from urllib.parse import urlparse, parse_qs
    3  -from aardwolf.commons.credential import RDPCredential, RDPCredentialsSecretType, RDPAuthProtocol
    4  -from aardwolf.commons.proxy import RDPProxy
    5  -from aardwolf.commons.target import RDPTarget, RDPConnectionDialect, RDPConnectionProtocol
    6  -from aardwolf.commons.authbuilder import AuthenticatorBuilder
    7  -from aardwolf.connection import RDPConnection
    8  -from aardwolf.vncconnection import VNCConnection
    9  -from getpass import getpass
    10  -import base64
    11  -import ipaddress
    12  -import copy
    13  - 
    14  - 
    15  -class RDPConnectionURL:
    16  - def __init__(self, connection_url, target = None, cred = None, proxy = None):
    17  - self.connection_url = connection_url
    18  -
    19  - #credential
    20  - self.authentication_protocol = None
    21  - self.secret_type = None
    22  - self.domain = None
    23  - self.username = None
    24  - self.secret = None
    25  - self.is_anonymous = None
    26  - self.auth_settings = {}
    27  - self.cred = cred
    28  - 
    29  - #target
    30  - self.dialect = None
    31  - self.protocol = None
    32  - self.hostname = None
    33  - self.dc_ip = None
    34  - self.port = None
    35  - self.ip = None
    36  - self.timeout = 5
    37  - self.serverip = None
    38  - self.fragment = None
    39  - self.path = None
    40  - self.compression = False
    41  - self.scheme = None
    42  - 
    43  - self.target = target
    44  - 
    45  - #proxy
    46  - self.proxy = proxy
    47  - 
    48  - if self.connection_url is not None:
    49  - self.parse()
    50  - 
    51  - def get_connection(self, iosettings):
    52  - tio = copy.deepcopy(iosettings)
    53  - credential = self.get_credential()
    54  - target = self.get_target()
    55  - if target.dialect is not None:
    56  - if target.dialect == RDPConnectionDialect.RDP:
    57  - return RDPConnection(target, credential, tio)
    58  - elif target.dialect == RDPConnectionDialect.VNC:
    59  - return VNCConnection(target, credential, tio)
    60  - else:
    61  - raise Exception('Unknown dialect %s' % target.dialect)
    62  - raise Exception('Either target or dialect must be defined first!')
    63  - 
    64  - def create_connection_newtarget(self, ip_or_hostname, iosettings):
    65  - tio = copy.deepcopy(iosettings)
    66  - credential = self.get_credential()
    67  - credential.target = ip_or_hostname
    68  - 
    69  - target = self.get_target()
    70  - try:
    71  - ipaddress.ip_address(ip_or_hostname)
    72  - target.ip = ip_or_hostname
    73  - target.hostname = None
    74  - except:
    75  - target.hostname = ip_or_hostname
    76  - target.ip = ip_or_hostname
    77  - 
    78  - #spneg = AuthenticatorBuilder.to_credssp(credential, target)
    79  - if target.dialect == RDPConnectionDialect.RDP:
    80  - return RDPConnection(target, credential, tio)
    81  - elif target.dialect == RDPConnectionDialect.VNC:
    82  - return VNCConnection(target, credential, tio)
    83  - else:
    84  - raise Exception('Unknown dialect %s' % target.dialect)
    85  - 
    86  - def get_file(self):
    87  - return RDPFile.from_RDPurl(self)
    88  - 
    89  - def get_proxy(self):
    90  - if self.proxy is not None:
    91  - copy.deepcopy(self.proxy)
    92  - return None
    93  - 
    94  - def get_target(self):
    95  - if self.target is not None:
    96  - return copy.deepcopy(self.target)
    97  - 
    98  - if self.ip is not None and self.hostname is None:
    99  - try:
    100  - ipaddress.ip_address(self.ip)
    101  - except:
    102  - self.hostname = self.ip
    103  - if self.serverip is not None:
    104  - self.ip = self.serverip
    105  -
    106  - t = RDPTarget(
    107  - ip = self.ip,
    108  - port = self.port,
    109  - hostname = self.hostname,
    110  - timeout = self.timeout,
    111  - dc_ip= self.dc_ip,
    112  - domain = self.domain,
    113  - proxy = self.get_proxy(),
    114  - protocol=self.protocol,
    115  - dialect=self.dialect
    116  - )
    117  - return t
    118  - 
    119  - def get_credential(self):
    120  - if self.cred is not None:
    121  - return copy.deepcopy(self.cred)
    122  - return RDPCredential(
    123  - username = self.username,
    124  - domain = self.domain,
    125  - secret = self.secret,
    126  - secret_type = self.secret_type,
    127  - authentication_type = self.authentication_protocol,
    128  - settings = self.auth_settings,
    129  - target = self.ip
    130  - )
    131  -
    132  - 
    133  - def scheme_decoder(self, scheme):
    134  - self.schemes = scheme.upper().split('+')
    135  -
    136  - connection_tags = self.schemes[0].split('-')
    137  - if len(connection_tags) > 1:
    138  - self.dialect = RDPConnectionDialect(connection_tags[0])
    139  - self.protocol = RDPConnectionProtocol(connection_tags[1])
    140  - else:
    141  - self.dialect = RDPConnectionDialect(connection_tags[0])
    142  - self.protocol = RDPConnectionProtocol.TCP
    143  - 
    144  - if len(self.schemes) == 1:
    145  - # user did not specify a requested auth scheme, defaulting to plain
    146  - # later on when server requires credssp this will be converted to NTLM
    147  - self.authentication_protocol = RDPAuthProtocol.PLAIN
    148  - self.secret_type = RDPCredentialsSecretType.PASSWORD
    149  - return
    150  - 
    151  - auth_tags = self.schemes[1].replace('-','_')
    152  - try:
    153  - self.authentication_protocol = RDPAuthProtocol(auth_tags)
    154  - except:
    155  - auth_tags = self.schemes[1].split('-')
    156  - #print(auth_tags)
    157  - if len(auth_tags) > 1:
    158  - if auth_tags[0] == 'MULTIPLEXOR':
    159  - if auth_tags[1] == 'SSL':
    160  - if len(auth_tags) == 2:
    161  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_SSL_NTLM
    162  - else:
    163  - if auth_tags[2] == 'NTLM':
    164  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_SSL_NTLM
    165  - elif auth_tags[2] == 'KERBEROS':
    166  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_SSL_KERBEROS
    167  - else:
    168  - if auth_tags[1] == 'NTLM':
    169  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_NTLM
    170  - elif auth_tags[1] == 'KERBEROS':
    171  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_KERBEROS
    172  - elif auth_tags[0] == 'SSPI':
    173  - if auth_tags[1] == 'NTLM':
    174  - self.authentication_protocol = RDPAuthProtocol.SSPI_NTLM
    175  - elif auth_tags[1] == 'KERBEROS':
    176  - self.authentication_protocol = RDPAuthProtocol.SSPI_KERBEROS
    177  - else:
    178  - self.authentication_protocol = RDPAuthProtocol(auth_tags[0])
    179  - self.secret_type = RDPCredentialsSecretType(auth_tags[1])
    180  - else:
    181  - if auth_tags[0] == 'MULTIPLEXOR':
    182  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_NTLM
    183  - elif auth_tags[0] == 'MULTIPLEXOR_SSL':
    184  - self.authentication_protocol = RDPAuthProtocol.MULTIPLEXOR_SSL_NTLM
    185  - if auth_tags[0] == 'SSPI':
    186  - self.authentication_protocol = RDPAuthProtocol.SSPI_NTLM
    187  - else:
    188  - self.authentication_protocol = RDPAuthProtocol(auth_tags[0])
    189  - if self.authentication_protocol == RDPAuthProtocol.KERBEROS:
    190  - raise Exception('For kerberos auth you need to specify the secret type in the connection string!')
    191  - 
    192  - def __str__(self):
    193  - t = '==== RDPConnectionURL ====\r\n'
    194  - for k in self.__dict__:
    195  - val = self.__dict__[k]
    196  - if isinstance(val, enum.IntFlag):
    197  - val = val
    198  - elif isinstance(val, enum.Enum):
    199  - val = val.name
    200  -
    201  - t += '%s: %s\r\n' % (k, str(val))
    202  -
    203  - return t
    204  - 
    205  - def parse(self):
    206  - url_e = urlparse(self.connection_url)
    207  - self.scheme_decoder(url_e.scheme)
    208  -
    209  - if url_e.username is not None:
    210  - if url_e.username.find('\\') != -1:
    211  - self.domain , self.username = url_e.username.split('\\')
    212  - if self.domain == '.':
    213  - self.domain = None
    214  - else:
    215  - self.domain = None
    216  - self.username = url_e.username
    217  -
    218  - self.secret = url_e.password
    219  -
    220  - if self.secret_type == RDPCredentialsSecretType.PWPROMPT:
    221  - self.secret_type = RDPCredentialsSecretType.PASSWORD
    222  - self.secret = getpass()
    223  - 
    224  - if self.secret_type == RDPCredentialsSecretType.PWHEX:
    225  - self.secret_type = RDPCredentialsSecretType.PASSWORD
    226  - self.secret = bytes.fromhex(self.secret).decode()
    227  -
    228  - if self.secret_type == RDPCredentialsSecretType.PWB64:
    229  - self.secret_type = RDPCredentialsSecretType.PASSWORD
    230  - self.secret = base64.b64decode(self.secret).decode()
    231  -
    232  - if self.secret is None and self.username is None:
    233  - self.is_anonymous = True
    234  -
    235  - if self.authentication_protocol == RDPAuthProtocol.NTLM and self.secret_type is None:
    236  - if self.is_anonymous == True:
    237  - self.secret_type = RDPCredentialsSecretType.NONE
    238  - else:
    239  - if len(self.secret) == 32:
    240  - try:
    241  - bytes.fromhex(self.secret)
    242  - except:
    243  - self.secret_type = RDPCredentialsSecretType.NT
    244  - else:
    245  - self.secret_type = RDPCredentialsSecretType.PASSWORD
    246  - 
    247  - elif self.authentication_protocol in [RDPAuthProtocol.SSPI_KERBEROS, RDPAuthProtocol.SSPI_NTLM,
    248  - RDPAuthProtocol.MULTIPLEXOR_NTLM, RDPAuthProtocol.MULTIPLEXOR_KERBEROS]:
    249  - if self.username is None:
    250  - self.username = '<CURRENT>'
    251  - if self.domain is None:
    252  - self.domain = '<CURRENT>'
    253  - if self.secret is None:
    254  - self.secret = '<CURRENT>'
    255  - 
    256  - 
    257  - self.ip = url_e.hostname
    258  - if url_e.port:
    259  - self.port = url_e.port
    260  - elif self.protocol == RDPConnectionProtocol.TCP and self.dialect == RDPConnectionDialect.RDP:
    261  - self.port = 3389
    262  - elif self.protocol == RDPConnectionProtocol.TCP and self.dialect == RDPConnectionDialect.VNC:
    263  - self.port = 5800
    264  - #elif self.protocol == RDPConnectionProtocol.QUIC:
    265  - # self.port = 443
    266  - else:
    267  - raise Exception('Port must be provided!')
    268  - 
    269  - if url_e.path not in ['/', '', None]:
    270  - self.path = url_e.path
    271  -
    272  - 
    273  - # recognized parameters :
    274  - # dc -> domain controller IP
    275  - # proxytype -> proxy protocol
    276  - # proxyuser -> username for proxy auth
    277  - # proxypass -> password for proxy auth
    278  - #
    279  - #
    280  - proxy_present = False
    281  - if url_e.query is not None:
    282  - query = parse_qs(url_e.query)
    283  - for k in query:
    284  - if k.startswith('proxy') is True:
    285  - proxy_present = True
    286  - if k == 'dc':
    287  - self.dc_ip = query[k][0]
    288  - elif k == 'timeout':
    289  - self.timeout = int(query[k][0])
    290  - elif k == 'serverip':
    291  - self.serverip = query[k][0]
    292  - elif k == 'fragment':
    293  - self.fragment = int(query[k][0])
    294  - elif k == 'dns':
    295  - self.dns = query[k] #multiple dns can be set, so not trimming here
    296  - elif k == 'compress':
    297  - self.compression = int(query[k][0])
    298  - elif k.startswith('auth'):
    299  - self.auth_settings[k[len('auth'):]] = query[k]
    300  - elif k.startswith('same'):
    301  - self.auth_settings[k[len('same'):]] = query[k]
    302  -
    303  - if proxy_present is True:
    304  - self.proxy = RDPProxy.from_params(self.connection_url)
    305  -
    306  -if __name__ == '__main__':
    307  - from aardwolf.commons.interfaces.file import RDPFile
    308  - url_tests = [
    309  - #'RDP://10.10.10.2',
    310  - #'RDP://10.10.10.2:9000',
    311  - #'RDP-tcp://10.10.10.2',
    312  - #'RDP-tcp://10.10.10.2:9000',
    313  - #'RDP-udp://10.10.10.2:138',
    314  - #'RDP+ntlm-password://domain\\[email protected]',
    315  - #'RDP-tcp+ntlm-password://domain\\user:[email protected]',
    316  - #'RDP-tcp+ntlm-password://domain\\user:[email protected]:10000',
    317  - #'RDP-tcp+ntlm-nt://domain\\user:[email protected]',
    318  - #'RDP+ntlm-nt://domain\\user:[email protected]',
    319  - #'RDP+ntlm-nt://domain\\user:[email protected]',
    320  - #'RDP-tcp+kerberos-password://domain\\alma:[email protected]',
    321  - #'RDP-tcp+kerberos-aes://domain\\alma:[email protected]',
    322  - #'RDP-tcp+kerberos-aes://domain\\alma:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@10.10.10.10.2',
    323  - #'RDP-tcp+kerberos-nt://domain\\alma:[email protected]',
    324  - #'RDP-tcp+kerberos-rc4://domain\\alma:[email protected]',
    325  - #'RDP+sspi://10.10.10.10.2',
    326  - #'RDP+sspi-ntlm://10.10.10.10.2',
    327  - #'RDP+sspi-kerberos://10.10.10.10.2',
    328  - #'RDP+multiplexor://10.10.10.10.2',
    329  - #'RDP+multiplexor-ssl://10.10.10.10.2',
    330  - #'RDP+multiplexor-ssl-ntlm://10.10.10.10.2',
    331  - #'RDP+multiplexor-ssl-kerberos://10.10.10.10.2',
    332  - #'RDP://10.10.10.2/?timeout=10',
    333  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2',
    334  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5',
    335  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1',
    336  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma',
    337  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma&dc=10.10.10.2&dns=8.8.8.8',
    338  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=socks5-ssl&proxyserver=127.0.0.1&proxyuser=admin&proxypass=alma&dc=10.10.10.2&dns=8.8.8.8',
    339  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyserver=127.0.0.1',
    340  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyserver=127.0.0.1&proxyagentid=alma',
    341  - #'RDP://10.10.10.2/?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyserver=127.0.0.1&proxyagentid=alma&proxytimeout=111',
    342  - 'RDP://10.10.10.2/C$/test/tst111.dmp?timeout=10&dc=10.10.10.2&proxytype=multiplexor&proxyhost=127.0.0.1&proxyport=1&proxyagentid=alma&proxytimeout=111',
    343  - 
    344  - ]
    345  - for url in url_tests:
    346  - print('===========================================================================')
    347  - print(url)
    348  - try:
    349  - dec = RDPConnectionURL(url)
    350  - creds = dec.get_credential()
    351  - target = dec.get_target()
    352  - RDPfile = dec.get_file()
    353  - print(RDPfile)
    354  - except Exception as e:
    355  - import traceback
    356  - traceback.print_exc()
    357  - print('ERROR! Reason: %s' % e)
    358  - input()
    359  - else:
    360  - print(str(creds))
    361  - print(str(target))
    362  - input()
    363  -
    364  -
    365  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/connection.py
    skipped 12 lines
    13 13  from aardwolf.keyboard import VK_MODIFIERS
    14 14  from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT
    15 15  from aardwolf.commons.target import RDPTarget
    16  -from aardwolf.commons.credential import RDPCredential, RDPCredentialsSecretType
     16 +from uniauth.common.credentials import UniCredential
     17 +from uniauth.common.constants import UniAuthSecret, UniAuthProtocol
    17 18  from aardwolf.commons.cryptolayer import RDPCryptoLayer
    18  -from aardwolf.network.tpkt import TPKTNetwork
    19 19  from aardwolf.network.x224 import X224Network
    20  -from aardwolf.network.selector import NetworkSelector
     20 +from uniauth.common.credentials.credssp import CREDSSPCredential
    21 21   
    22 22  from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS, NEG_FLAGS
    23 23  from aardwolf.protocol.x224.server.connectionconfirm import RDP_NEG_RSP
    skipped 39 lines
    63 63   
    64 64  from aardwolf.protocol.fastpath import TS_FP_UPDATE_PDU, FASTPATH_UPDATETYPE, FASTPATH_FRAGMENT, FASTPATH_SEC, TS_FP_UPDATE
    65 65  from aardwolf.commons.queuedata import RDPDATATYPE, RDP_KEYBOARD_SCANCODE, RDP_KEYBOARD_UNICODE, RDP_MOUSE, RDP_VIDEO
    66  -from aardwolf.commons.authbuilder import AuthenticatorBuilder
    67  -from aardwolf.channels import Channel
     66 +from aardwolf.channels import MCSChannel
    68 67  from aardwolf.commons.iosettings import RDPIOSettings
    69 68   
     69 +from asysocks.unicomm.client import UniClient
     70 +from asysocks.unicomm.common.connection import UniConnection
     71 +from aardwolf.network.tpkt import TPKTPacketizer
     72 + 
     73 +from aardwolf.network.tpkt import CredSSPPacketizer
     74 +from asysocks.unicomm.common.packetizers import Packetizer
    70 75   
    71 76  class RDPConnection:
    72  - def __init__(self, target:RDPTarget, credentials:RDPCredential, iosettings:RDPIOSettings):
     77 + def __init__(self, target:RDPTarget, credentials:UniCredential, iosettings:RDPIOSettings):
    73 78   """RDP client connection object. After successful connection the two asynchronous queues named `ext_out_queue` and `ext_in_queue`
    74 79   can be used to communicate with the remote server
    75 80   
    skipped 14 lines
    90 95   self.ext_out_queue = asyncio.Queue()
    91 96   self.ext_in_queue = asyncio.Queue()
    92 97   
    93  - 
    94  - self.__tpkgnet = None
     98 + self.__connection:UniConnection = None
    95 99   self._x224net = None
    96  - self.__transportnet = None #TCP/SSL/SOCKS etc.
    97 100   self.__t125_ber_codec = None
    98 101   self._t125_per_codec = None
    99 102   self.__t124_codec = None
    skipped 13 lines
    113 116   self.__channel_task = {} #name -> channeltask
    114 117   
    115 118  
    116  - self.__fastpath_reader_task = None
    117 119   self.__external_reader_task = None
    118 120   self.__x224_reader_task = None
    119 121   self.client_x224_flags = 0
    120 122   self.client_x224_supported_protocols = self.iosettings.supported_protocols
    121 123   self.cryptolayer:RDPCryptoLayer = None
    122  - self.__fastpath_in_queue = None
    123 124   self.__desktop_buffer = None
    124 125   self.desktop_buffer_has_data = False
    125 126   
    skipped 60 lines
    186 187   if self.__external_reader_task is not None:
    187 188   self.__external_reader_task.cancel()
    188 189  
    189  - if self.__fastpath_reader_task is not None:
    190  - self.__fastpath_reader_task.cancel()
    191  -
    192 190   if self.__x224_reader_task is not None:
    193 191   self.__x224_reader_task.cancel()
    194  -
    195  - if self._x224net is not None:
    196  - await self._x224net.disconnect()
    197  - 
    198  - if self.__tpkgnet is not None:
    199  - await self.__tpkgnet.disconnect()
    200 192  
    201 193   return True, None
    202 194   except Exception as e:
    skipped 14 lines
    217 209   Tuple[bool, Exception]: _description_
    218 210   """
    219 211   try:
    220  - self.__fastpath_in_queue = asyncio.Queue()
    221  - self.__transportnet, err = await NetworkSelector.select(self.target)
    222  - if err is not None:
    223  - raise err
    224 212   
    225  - # starting lower-layer transports
    226  - _, err = await self.__transportnet.connect()
    227  - if err is not None:
    228  - raise err
    229  - 
    230  - # TPKT network handles both TPKT and FASTPATH packets
    231  - # This object is also capable to dynamically switch
    232  - # to SSL/TLS when needed (without reconnecting)
    233  - self.__tpkgnet = TPKTNetwork(self.__transportnet)
    234  - _, err = await self.__tpkgnet.run()
    235  - if err is not None:
    236  - raise err
    237  -
    238  - 
    239  - self.__fastpath_reader_task = asyncio.create_task(self.__fastpath_reader())
     213 + packetizer = TPKTPacketizer()
     214 + client = UniClient(self.target, packetizer)
     215 + self.__connection = await client.connect()
    240 216   
    241 217   # X224 channel is on top of TPKT, performs the initial negotiation
    242 218   # between the server and our client (restricted admin mode, authentication methods etc)
    243 219   # are set here
    244  - self._x224net = X224Network(self.__tpkgnet)
    245  - _, err = await self._x224net.run()
    246  - if err is not None:
    247  - raise err
     220 + self._x224net = X224Network(self.__connection)
     221 + if self.client_x224_supported_protocols is None and self.credentials is not None:
     222 + if self.credentials.protocol in [UniAuthProtocol.NTLM, UniAuthProtocol.KERBEROS]:
     223 + if self.credentials.secret is not None and self.credentials.stype not in [UniAuthSecret.PASSWORD, UniAuthSecret.PWPROMPT, UniAuthSecret.PWHEX, UniAuthSecret.PWB64]:
     224 + # user provided some secret but it's not a password
     225 + # here we request restricted admin mode
     226 + self.client_x224_flags = NEG_FLAGS.RESTRICTED_ADMIN_MODE_REQUIRED
     227 + self.client_x224_supported_protocols = SUPP_PROTOCOLS.RDP | SUPP_PROTOCOLS.SSL |SUPP_PROTOCOLS.HYBRID
     228 + else:
     229 + self.client_x224_flags = 0
     230 + self.client_x224_supported_protocols = SUPP_PROTOCOLS.RDP | SUPP_PROTOCOLS.SSL | SUPP_PROTOCOLS.HYBRID_EX | SUPP_PROTOCOLS.HYBRID
    248 231  
    249  - if self.client_x224_supported_protocols is None and self.credentials is not None:
    250  - if self.credentials.secret is not None and self.credentials.secret_type not in [RDPCredentialsSecretType.PASSWORD, RDPCredentialsSecretType.PWPROMPT, RDPCredentialsSecretType.PWHEX, RDPCredentialsSecretType.PWB64]:
    251  - # user provided some secret but it's not a password
    252  - # here we request restricted admin mode
    253  - self.client_x224_flags = NEG_FLAGS.RESTRICTED_ADMIN_MODE_REQUIRED
    254  - self.client_x224_supported_protocols = SUPP_PROTOCOLS.RDP | SUPP_PROTOCOLS.SSL |SUPP_PROTOCOLS.HYBRID
    255  - elif self.credentials.secret is None and self.credentials.username is None:
     232 + elif self.credentials.stype == UniAuthSecret.NONE: #and self.credentials.username is None:
    256 233   # not sending any passwords, hoping HYBRID is not required
    257 234   self.client_x224_flags = 0
    258 235   self.client_x224_supported_protocols = SUPP_PROTOCOLS.RDP | SUPP_PROTOCOLS.SSL
    259 236   else:
    260 237   self.client_x224_flags = 0
    261  - self.client_x224_supported_protocols = SUPP_PROTOCOLS.RDP | SUPP_PROTOCOLS.SSL | SUPP_PROTOCOLS.HYBRID_EX | SUPP_PROTOCOLS.HYBRID
     238 + self.client_x224_supported_protocols = SUPP_PROTOCOLS.RDP | SUPP_PROTOCOLS.SSL
    262 239  
    263 240   logger.debug('Client protocol flags: %s' % self.client_x224_flags)
    264 241   logger.debug('Client protocol offer: %s' % self.client_x224_supported_protocols)
    skipped 12 lines
    277 254   self.x224_protocol = self.x224_connection_reply.selectedProtocol
    278 255   self.x224_flag = self.x224_connection_reply.flags
    279 256   logger.debug('Server selected protocol: %s' % self.x224_protocol)
    280  - #print(self.x224_flag)
    281 257   if SUPP_PROTOCOLS.SSL in self.x224_protocol or SUPP_PROTOCOLS.HYBRID in self.x224_protocol or SUPP_PROTOCOLS.HYBRID_EX in self.x224_protocol:
    282  - from aardwolf.transport.ssl import SSLClientTunnel
    283  - _, err = await self.__tpkgnet.switch_transport(SSLClientTunnel)
    284  - if err is not None:
    285  - raise err
     258 + await self.__connection.wrap_ssl()
    286 259   
    287 260   # if the server expects HYBRID/HYBRID_EX authentication we do that here
    288 261   # This is basically credSSP
    skipped 1 lines
    290 263   _, err = await self.credssp_auth()
    291 264   if err is not None:
    292 265   raise err
     266 +
     267 + #switching back to tpkt
     268 + self.__connection.change_packetizer(TPKTPacketizer())
    293 269   
    294 270   else:
    295 271   # old RDP protocol is used
    skipped 8 lines
    304 280   
    305 281   # All steps below are required as stated in the following 'documentation'
    306 282   # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/1d263f84-6153-4a16-b329-8770be364e1b
     283 + logger.debug('Establish channels...')
    307 284   _, err = await self.__establish_channels()
    308 285   if err is not None:
    309 286   raise err
    skipped 59 lines
    369 346   if self.credentials is None:
    370 347   raise Exception('No auth API nor credentials were supplied!')
    371 348  
    372  - self.authapi = AuthenticatorBuilder.to_credssp(self.credentials, self.target)
    373  - # credSSP authentication exchange happens on the 'wire' directly
    374  - # without the use of TPKT or X224 so we have to suspend those layers
    375  - _, err = await self.__tpkgnet.suspend_read()
    376  - if err is not None:
    377  - raise err
    378 349  
    379  - # credSSP auth requires knowledge of the server's public key
    380  - pubkey = await self.__tpkgnet.transport.get_server_pubkey()
     350 + self.authapi = CREDSSPCredential([self.credentials]).build_context()
     351 + 
     352 + self.__connection.change_packetizer(CredSSPPacketizer())
     353 + 
     354 + certificate = self.__connection.get_peer_certificate()
    381 355   
    382 356   # credSSP auth happends here
    383 357   token = None
    384  - data, to_continue, err = await self.authapi.authenticate(token, flags = None, pubkey = pubkey)
     358 + data, to_continue, err = await self.authapi.authenticate(token, flags = None, certificate = certificate, spn=self.target.to_target_string())
    385 359   if err is not None:
    386 360   raise err
    387 361   
    388  - await self.__tpkgnet.transport.out_queue.put(data)
     362 + await self.__connection.write(data)
    389 363  
    390 364   for _ in range(10):
    391  - token, err = await self.__tpkgnet.transport.in_queue.get()
    392  - if err is not None:
    393  - raise err
    394  - 
    395  - data, to_continue, err = await self.authapi.authenticate(token, flags = None, pubkey = pubkey)
     365 + token = await self.__connection.read_one()
     366 + data, to_continue, err = await self.authapi.authenticate(token, flags = None, certificate = certificate, spn=self.target.to_target_string())
    396 367   if err is not None:
    397 368   raise err
    398 369  
    399 370   if to_continue is False:
    400 371   # credSSP auth finished, flushing remaining data
    401 372   if data is not None:
    402  - await self.__tpkgnet.transport.out_queue.put(data)
     373 + await self.__connection.write(data)
    403 374  
    404 375   # if HYBRID_EX auth was selected by the server, the server MUST send
    405 376   # an extra packet informing us if the credSSP auth was successful or not
    406 377   if SUPP_PROTOCOLS.HYBRID_EX in self.x224_protocol:
    407  - authresult_raw, err = await self.__tpkgnet.transport.in_queue.get()
    408  - if err is not None:
    409  - raise err
    410  -
     378 + self.__connection.change_packetizer(Packetizer())
     379 + authresult_raw = await self.__connection.read_one()
    411 380   authresult = int.from_bytes(authresult_raw, byteorder='little', signed=False)
    412 381   #print('Early User Authorization Result PDU %s' % authresult)
    413 382   if authresult == 5:
    414 383   raise Exception('Authentication failed! (early user auth)')
    415  - 
    416  - 
    417  -
    418  - _, err = await self.__tpkgnet.conitnue_read()
    419  - if err is not None:
    420  - raise err
    421 384   return True, None
    422 385  
    423  - await self.__tpkgnet.transport.out_queue.put(data)
    424  - 
    425  - _, err = await self.__tpkgnet.conitnue_read()
    426  - if err is not None:
    427  - raise err
     386 + await self.__connection.write(data)
    428 387   
    429 388   except Exception as e:
    430 389   return None, e
    skipped 154 lines
    585 544   }
    586 545   
    587 546   conf_create_req = self.__t125_ber_codec.encode('ConnectMCSPDU',('connect-initial', initialconnect))
    588  - conf_create_req = bytes(conf_create_req)
    589  - #print(conf_create_req)
     547 + await self._x224net.write(bytes(conf_create_req))
    590 548  
    591  - await self._x224net.out_queue.put(conf_create_req)
    592  - _, response_raw, err = await self._x224net.in_queue.get()
    593  - if err is not None:
    594  - raise err
     549 + response_raw = await self._x224net.read()
    595 550   server_res_raw = response_raw.data
    596 551   server_res_t125 = self.__t125_ber_codec.decode('ConnectMCSPDU', server_res_raw)
    597  - #print(server_res_t125)
    598 552   if server_res_t125[0] != 'connect-response':
    599 553   raise Exception('Unexpected response! %s' % server_res_t125)
    600 554   if server_res_t125[1]['result'] != 'rt-successful':
    skipped 24 lines
    625 579   self.__joined_channels[name].channel_id = scnet.channelIdArray[i]
    626 580   self.__channel_id_lookup[scnet.channelIdArray[i]] = self.__joined_channels[name]
    627 581   
    628  - self.__joined_channels['MCS'] = Channel('MCS') #TODO: options?
     582 + self.__joined_channels['MCS'] = MCSChannel() #TODO: options?
    629 583   self.__joined_channels['MCS'].channel_id = scnet.MCSChannelId
    630 584   self.__channel_id_lookup[scnet.MCSChannelId] = self.__joined_channels['MCS']
    631 585   
    skipped 7 lines
    639 593   # the parser could not decode nor encode this data correctly.
    640 594   # therefore we are sending these as bytes. it's static
    641 595   # (even according to docu)
    642  - await self._x224net.out_queue.put(bytes.fromhex('0400010001'))
     596 + await self._x224net.write(bytes.fromhex('0400010001'))
    643 597   return True, None
    644 598   except Exception as e:
    645 599   return None, e
    646 600  
    647 601   async def __attach_user(self):
    648 602   try:
    649  - await self._x224net.out_queue.put(bytes.fromhex('28'))
    650  - _, response, err = await self._x224net.in_queue.get()
    651  - if err is not None:
    652  - raise err
     603 + request = self._t125_per_codec.encode('DomainMCSPDU', ('attachUserRequest', {}))
     604 + await self._x224net.write(request)
     605 + response = await self._x224net.read()
    653 606   response_parsed = self._t125_per_codec.decode('DomainMCSPDU', response.data)
    654 607   if response_parsed[0] != 'attachUserConfirm':
    655  - raise Exception('Unexpected response! %s' % response_parsed)
     608 + raise Exception('Unexpected response! %s' % response_parsed[0])
    656 609   if response_parsed[1]['result'] != 'rt-successful':
    657  - raise Exception('Server returned error! %s' % response_parsed)
     610 + raise Exception('Server returned error! %s' % response_parsed[0])
    658 611   self._initiator = response_parsed[1]['initiator']
    659 612  
    660 613   return True, None
    skipped 4 lines
    665 618   try:
    666 619   for name in self.__joined_channels:
    667 620   joindata = self._t125_per_codec.encode('DomainMCSPDU', ('channelJoinRequest', {'initiator': self._initiator, 'channelId': self.__joined_channels[name].channel_id}))
    668  - await self._x224net.out_queue.put(bytes(joindata))
    669  - _, response, err = await self._x224net.in_queue.get()
    670  - if err is not None:
    671  - raise err
     621 + await self._x224net.write(bytes(joindata))
     622 + response = await self._x224net.read()
    672 623  
    673 624   x = self._t125_per_codec.decode('DomainMCSPDU', response.data)
    674 625   if x[0] != 'channelJoinConfirm':
    skipped 95 lines
    770 721   raise Exception('License error! tokenInhibitConfirm:result not successful')
    771 722   else:
    772 723   raise Exception('tokenInhibitConfirm did not show up in reply!')
    773  - #print('license ok')
    774 724   
    775 725   return True, None
    776 726   except Exception as e:
    skipped 14 lines
    791 741   data_start_offset = 4
    792 742   
    793 743   data = data[data_start_offset:]
    794  - # TEST
    795 744   shc = TS_SHARECONTROLHEADER.from_bytes(data)
    796 745   if shc.pduType != PDUTYPE.DEMANDACTIVEPDU:
    797 746   raise Exception('Unexpected reply! Expected DEMANDACTIVEPDU got "%s" instead!' % shc.pduType.name)
    798  - 
    799  - #### TEST END
    800 747  
    801 748   res = TS_DEMAND_ACTIVE_PDU.from_bytes(data)
    802 749   for cap in res.capabilitySets:
    803  - #print(cap)
    804 750   if cap.capabilitySetType == CAPSTYPE.GENERAL:
    805 751   cap = typing.cast(TS_GENERAL_CAPABILITYSET, cap.capability)
    806 752   if EXTRAFLAG.ENC_SALTED_CHECKSUM in cap.extraFlags and self.cryptolayer is not None:
    807 753   self.cryptolayer.use_encrypted_mac = True
    808  - #print(res)
    809  - #print('================================== SERVER IN ENDS HERE ================================================')
    810 754  
    811 755   caps = []
    812 756   # now we send our capabilities
    skipped 167 lines
    980 924   # dont activate it before this!!!!
    981 925  
    982 926   try:
    983  - while True:
    984  - is_fastpath, response, err = await self._x224net.in_queue.get()
    985  - if err is not None:
    986  - raise err
     927 + self.__connection.packetizer.packetizer_control("X224")
     928 +
     929 + async for is_fastpath, response in self.__connection.read():
     930 + #is_fastpath, response, err = await self._x224net.in_queue.get()
     931 + #if err is not None:
     932 + # raise err
    987 933   
    988 934   if response is None:
    989 935   raise Exception('Server terminated the connection!')
    990 936  
    991 937   if is_fastpath is False:
    992  - #print('__x224_reader data in -> %s' % response.data)
    993 938   x = self._t125_per_codec.decode('DomainMCSPDU', response.data)
    994  - #print('__x224_reader decoded data in -> %s' % str(x))
    995 939   if x[0] != 'sendDataIndication':
    996 940   #print('Unknown packet!')
    997 941   continue
    skipped 15 lines
    1013 957   print('Decrypted data: %s' % data)
    1014 958   print('Original MAC : %s' % sec_hdr.dataSignature)
    1015 959   print('Calculated MAC: %s' % mac)
    1016  - await self.__channel_id_lookup[x[1]['channelId']].raw_in_queue.put((data, None))
     960 + await self.__channel_id_lookup[x[1]['channelId']].process_channel_data(data)
    1017 961   else:
    1018 962   #print('fastpath data in -> %s' % len(response))
    1019 963   fpdu = TS_FP_UPDATE_PDU.from_bytes(response)
    skipped 12 lines
    1032 976   print('Calculated MAC: %s' % mac)
    1033 977   raise Exception('Signature mismatch')
    1034 978   fpdu.fpOutputUpdates = TS_FP_UPDATE.from_bytes(data)
    1035  - await self.__fastpath_in_queue.put((fpdu, None))
     979 + await self.__process_fastpath(fpdu)
    1036 980  
    1037 981   except asyncio.CancelledError:
    1038 982   return None, None
    skipped 3 lines
    1042 986   finally:
    1043 987   await self.terminate()
    1044 988   
    1045  - async def __fastpath_reader(self):
     989 + async def __process_fastpath(self, fpdu):
    1046 990   # Fastpath was introduced to the RDP specs to speed up data transmission
    1047 991   # by reducing 4 useless layers from the traffic.
    1048 992   # Transmission on this channel starts immediately after connection sequence
    skipped 1 lines
    1050 994   # interesting note: it seems newer servers (>=win2016) only support this protocol of sending
    1051 995   # high bandwith traffic. If you disable fastpath (during connection sequence) you won't
    1052 996   # get images at all
    1053  - try:
    1054  - while True:
    1055  - fpdu, err = await self.__fastpath_in_queue.get()
    1056  - if err is not None:
    1057  - raise err
    1058  - if fpdu is None:
    1059  - raise Exception('Server terminated the connection!')
    1060 997  
    1061  - try:
    1062  - if fpdu.fpOutputUpdates.fragmentation != FASTPATH_FRAGMENT.SINGLE:
    1063  - print('WARNING! FRAGMENTATION IS NOT IMPLEMENTED! %s' % fpdu.fpOutputUpdates.fragmentation)
    1064  - if fpdu.fpOutputUpdates.updateCode == FASTPATH_UPDATETYPE.BITMAP:
    1065  - for bitmapdata in fpdu.fpOutputUpdates.update.rectangles:
    1066  - self.desktop_buffer_has_data = True
    1067  - res, image = RDP_VIDEO.from_bitmapdata(bitmapdata, self.iosettings.video_out_format)
    1068  - self.__desktop_buffer.paste(image, [res.x, res.y, res.x+res.width, res.y+res.height])
    1069  - await self.ext_out_queue.put(res)
    1070  - #else:
    1071  - # #print(fpdu.fpOutputUpdates.updateCode)
    1072  - # #if fpdu.fpOutputUpdates.updateCode == FASTPATH_UPDATETYPE.CACHED:
    1073  - # # print(fpdu.fpOutputUpdates)
    1074  - # #if fpdu.fpOutputUpdates.updateCode not in [FASTPATH_UPDATETYPE.CACHED, FASTPATH_UPDATETYPE.POINTER]:
    1075  - # # print('notbitmap %s' % fpdu.fpOutputUpdates.updateCode.name)
    1076  - except Exception as e:
    1077  - # the decoder is not perfect yet, so it's better to keep this here...
    1078  - traceback.print_exc()
    1079  - return
    1080  -
    1081  - except asyncio.CancelledError:
    1082  - return None, None
    1083  - 
     998 + try:
     999 + if fpdu.fpOutputUpdates.fragmentation != FASTPATH_FRAGMENT.SINGLE:
     1000 + print('WARNING! FRAGMENTATION IS NOT IMPLEMENTED! %s' % fpdu.fpOutputUpdates.fragmentation)
     1001 + if fpdu.fpOutputUpdates.updateCode == FASTPATH_UPDATETYPE.BITMAP:
     1002 + for bitmapdata in fpdu.fpOutputUpdates.update.rectangles:
     1003 + self.desktop_buffer_has_data = True
     1004 + res, image = RDP_VIDEO.from_bitmapdata(bitmapdata, self.iosettings.video_out_format)
     1005 + self.__desktop_buffer.paste(image, [res.x, res.y, res.x+res.width, res.y+res.height])
     1006 + await self.ext_out_queue.put(res)
     1007 + #else:
     1008 + # #print(fpdu.fpOutputUpdates.updateCode)
     1009 + # #if fpdu.fpOutputUpdates.updateCode == FASTPATH_UPDATETYPE.CACHED:
     1010 + # # print(fpdu.fpOutputUpdates)
     1011 + # #if fpdu.fpOutputUpdates.updateCode not in [FASTPATH_UPDATETYPE.CACHED, FASTPATH_UPDATETYPE.POINTER]:
     1012 + # # print('notbitmap %s' % fpdu.fpOutputUpdates.updateCode.name)
    1084 1013   except Exception as e:
     1014 + # the decoder is not perfect yet, so it's better to keep this here...
    1085 1015   traceback.print_exc()
    1086  - return None, e
    1087  - finally:
    1088  - await self.terminate()
     1016 + return
     1017 +
     1018 + 
    1089 1019   
    1090 1020   async def send_key_virtualkey(self, vk, is_pressed, is_extended, scancode_hint = None, modifiers = VK_MODIFIERS(0)):
    1091 1021   try:
    skipped 163 lines
    1255 1185   if 'cliprdr' not in self.__joined_channels:
    1256 1186   logger.debug('Got clipboard data but no clipboard channel setup!')
    1257 1187   continue
    1258  - await self.__joined_channels['cliprdr'].in_queue.put(indata)
     1188 + await self.__joined_channels['cliprdr'].process_user_data(indata)
    1259 1189   
    1260 1190   except asyncio.CancelledError:
    1261 1191   return None, None
    skipped 5 lines
    1267 1197  
    1268 1198   async def handle_out_data(self, dataobj, sec_hdr, datacontrol_hdr, sharecontrol_hdr, channel_id, is_fastpath):
    1269 1199   try:
    1270  - #while True:
    1271  - # dataobj, sec_hdr, datacontrol_hdr, sharecontrol_hdr, channel_id, is_fastpath = await self.data_in_queue.get()
    1272 1200   if is_fastpath is False:
    1273 1201   #print('Sending data on channel "%s(%s)"' % (self.name, self.channel_id))
    1274 1202   data = dataobj.to_bytes()
    skipped 41 lines
    1316 1244   'userData': userdata
    1317 1245   }
    1318 1246   userdata_wrapped = self._t125_per_codec.encode('DomainMCSPDU', ('sendDataRequest', data_wrapper))
    1319  - await self._x224net.out_queue.put(userdata_wrapped)
     1247 + await self._x224net.write(userdata_wrapped)
    1320 1248  
    1321 1249   else:
    1322 1250   raise NotImplementedError("Fastpath output is not yet implemented")
    skipped 6 lines
    1329 1257  
    1330 1258  async def amain():
    1331 1259   try:
    1332  - from aardwolf.commons.url import RDPConnectionURL
     1260 + from aardwolf.commons.factory import RDPConnectionFactory
    1333 1261   from aardwolf.commons.iosettings import RDPIOSettings
    1334 1262   
    1335 1263   iosettings = RDPIOSettings()
    1336 1264   url = 'rdp+ntlm-password://TEST\\Administrator:[email protected]'
    1337  - rdpurl = RDPConnectionURL(url)
     1265 + rdpurl = RDPConnectionFactory.from_url(url)
    1338 1266   conn = rdpurl.get_connection(iosettings)
    1339 1267   _, err = await conn.connect()
    1340 1268   if err is not None:
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/aardpcapscan.py
    1  - 
    2  -import asyncio
    3  -import enum
    4  -import traceback
    5  -import uuid
    6  -import logging
    7  -import json
    8  - 
    9  -from aardwolf import logger
    10  -from aardwolf.examples.scancommons.targetgens import ListTargetGen, FileTargetGen
    11  -from aardwolf.examples.scancommons.internal import EnumResult, EnumResultStatus
    12  -from aardwolf.utils.univeraljson import UniversalEncoder
    13  - 
    14  -from aardwolf.network.selector import NetworkSelector
    15  -from aardwolf.commons.target import RDPTarget
    16  -from aardwolf.network.tpkt import TPKTNetwork
    17  -from aardwolf.network.x224 import X224Network
    18  - 
    19  -from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS, NEG_FLAGS
    20  -from aardwolf.protocol.x224.server.connectionconfirm import RDP_NEG_RSP
    21  - 
    22  -from tqdm import tqdm
    23  - 
    24  -RDPSCAPSCAN_TSV_HDR = ['target', 'target_id', 'restrictedadmin', 'restrictedauth', 'rdp', 'ssl', 'hybrid', 'hybridex']
    25  - 
    26  -class RDPCAPProgressResult:
    27  - def __init__(self, total_targets, total_finished, gens_finished, target):
    28  - self.total_targets = total_targets
    29  - self.total_finished = total_finished
    30  - self.gens_finished = gens_finished
    31  - self.target = target
    32  - 
    33  -def d2tsv(dd, hdrs = RDPSCAPSCAN_TSV_HDR, separator = '\t'):
    34  - data = []
    35  - for x in hdrs:
    36  - if x not in dd:
    37  - continue
    38  - data.append(str(dd[x]))
    39  - return separator.join(data)
    40  - 
    41  - 
    42  -class RDPCAPResult:
    43  - def __init__(self, obj, otype):
    44  - self.obj = obj
    45  - self.otype = otype
    46  - 
    47  - def __str__(self):
    48  - if self.otype == 'result':
    49  - return '[R] %s | %s' % (self.obj.target, d2tsv(self.obj.result))
    50  -
    51  - elif self.otype == 'error':
    52  - return '[E] %s | %s' % (self.obj.target, self.obj.error)
    53  - 
    54  - elif self.otype == 'progress':
    55  - return '[P][%s/%s][%s] %s' % (self.obj.total_targets, self.obj.total_finished, str(self.obj.gens_finished), self.obj.target)
    56  - 
    57  - else:
    58  - return '[UNK]'
    59  - 
    60  - def to_dict(self):
    61  - if self.otype == 'result':
    62  - t = self.obj.result
    63  - t['target'] = self.obj.target
    64  - t['target_id'] = self.obj.target_id
    65  - return t
    66  - return {}
    67  -
    68  - def to_json(self):
    69  - dd = self.to_dict()
    70  - return json.dumps(dd, cls = UniversalEncoder)
    71  - 
    72  - def to_tsv(self, hdrs = RDPSCAPSCAN_TSV_HDR, separator = '\t'):
    73  - if self.otype == 'result':
    74  - dd = self.to_dict()
    75  - data = [ str(dd[x]) for x in hdrs ]
    76  - return separator.join(data)
    77  - 
    78  - return ''
    79  -
    80  -class RDPCAPScan:
    81  - def __init__(self, restricted_only = False, worker_count = 100, timeout = 5, show_pbar = True, output_type = 'str', out_file = None, out_buffer_size = 1, ext_result_q = None):
    82  - self.target_gens = []
    83  - self.timeout = timeout
    84  - self.worker_count = worker_count
    85  - self.restricted_only = restricted_only
    86  - self.task_q = None
    87  - self.res_q = None
    88  - self.workers = []
    89  - self.protoflags = [SUPP_PROTOCOLS.RDP, SUPP_PROTOCOLS.SSL, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID_EX]
    90  - self.result_processing_task = None
    91  - self.show_pbar = show_pbar
    92  - self.output_type = output_type
    93  - self.out_file = out_file
    94  - self.out_buffer_size = out_buffer_size
    95  - self.ext_result_q = ext_result_q
    96  - self.__gens_finished = False
    97  - self.__total_targets = 0
    98  - self.__total_finished = 0
    99  - self.__total_errors = 0
    100  - 
    101  - async def __executor(self, tid, target):
    102  - async def scan_flags(target, negflag, protoflag):
    103  - try:
    104  - target = RDPTarget(ip = target)
    105  - transportnet, err = await NetworkSelector.select(target)
    106  - if err is not None:
    107  - raise err
    108  - 
    109  - _, err = await transportnet.connect()
    110  - if err is not None:
    111  - raise err
    112  - 
    113  - tpkgnet = TPKTNetwork(transportnet)
    114  - _, err = await tpkgnet.run()
    115  - if err is not None:
    116  - raise err
    117  -
    118  - x224net = X224Network(tpkgnet)
    119  - _, err = await x224net.run()
    120  - if err is not None:
    121  - raise err
    122  -
    123  - reply, err = await x224net.client_negotiate(negflag, protoflag, to_raise=False)
    124  - if err is not None:
    125  - raise err
    126  - 
    127  - await x224net.disconnect()
    128  - await tpkgnet.disconnect()
    129  -
    130  - if reply.rdpNegData.type == 3:
    131  - #server refused our flags
    132  - return False, reply, None
    133  - 
    134  - return True, reply, None
    135  - except Exception as e:
    136  - return None, None, e
    137  - try:
    138  - res = {
    139  - 'rdp' : None,
    140  - 'ssl' : None,
    141  - 'hybrid' : None,
    142  - 'hybridex' : None,
    143  - 'restrictedadmin': None,
    144  - 'restrictedauth': None,
    145  - }
    146  - 
    147  - if self.restricted_only is False:
    148  - for protoflag in self.protoflags:
    149  - negflag = 0
    150  - ok, reply, err = await scan_flags(target, negflag, protoflag)
    151  - if err is not None:
    152  - raise err
    153  - if ok is True:
    154  - if SUPP_PROTOCOLS.SSL == reply.rdpNegData.selectedProtocol:
    155  - res['ssl'] = True
    156  - if SUPP_PROTOCOLS.HYBRID in reply.rdpNegData.selectedProtocol:
    157  - res['hybrid'] = True
    158  - if SUPP_PROTOCOLS.HYBRID_EX in reply.rdpNegData.selectedProtocol:
    159  - res['hybridex'] = True
    160  - if SUPP_PROTOCOLS.RDP == reply.rdpNegData.selectedProtocol:
    161  - res['rdp'] = True
    162  - 
    163  - ok, reply, err = await scan_flags(target, NEG_FLAGS.RESTRICTED_ADMIN_MODE_REQUIRED, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID)
    164  - if err is not None:
    165  - raise err
    166  - res['restrictedadmin'] = ok
    167  - ok, reply, err = await scan_flags(target, NEG_FLAGS.REDIRECTED_AUTHENTICATION_MODE_REQUIRED, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID)
    168  - if err is not None:
    169  - raise err
    170  - res['restrictedauth'] = ok
    171  - 
    172  - er = EnumResult(tid, target, res)
    173  - await self.res_q.put(er)
    174  - except asyncio.CancelledError:
    175  - return
    176  - except Exception as e:
    177  - await self.res_q.put(EnumResult(tid, target, None, error = e, status = EnumResultStatus.ERROR))
    178  - finally:
    179  - await self.res_q.put(EnumResult(tid, target, None, status = EnumResultStatus.FINISHED))
    180  - 
    181  - async def worker(self):
    182  - try:
    183  - while True:
    184  - indata = await self.task_q.get()
    185  - if indata is None:
    186  - return
    187  -
    188  - tid, target = indata
    189  - try:
    190  - await asyncio.wait_for(self.__executor(tid, target), timeout=10)
    191  - except asyncio.CancelledError:
    192  - return
    193  - except Exception as e:
    194  - pass
    195  - except asyncio.CancelledError:
    196  - return
    197  -
    198  - except Exception as e:
    199  - return e
    200  - 
    201  - async def result_processing(self):
    202  - try:
    203  - pbar = None
    204  - if self.show_pbar is True:
    205  - pbar = {}
    206  - pbar['targets'] = tqdm(desc='Targets ', unit='', position=0)
    207  - pbar['results'] = tqdm(desc='Results ', unit='', position=1)
    208  - pbar['errors'] = tqdm(desc='Errors ', unit='', position=2)
    209  -
    210  - if self.out_file is None and self.show_pbar is False and self.ext_result_q is None:
    211  - print('[TYPE] HOST | restrictedadmin\trestrictedauth\trdp\tssl\thybrid\thybridex')
    212  - 
    213  - out_buffer = []
    214  - final_iter = False
    215  - while True:
    216  - try:
    217  - if self.__gens_finished is True and self.show_pbar is True and pbar['targets'].total is None:
    218  - pbar['targets'].total = self.__total_targets
    219  - for key in pbar:
    220  - pbar[key].refresh()
    221  -
    222  - if self.ext_result_q is not None:
    223  - out_buffer = []
    224  - 
    225  - if len(out_buffer) >= self.out_buffer_size or final_iter and self.ext_result_q is None:
    226  - out_data = ''
    227  - if self.output_type == 'str':
    228  - out_data = '\r\n'.join([str(x) for x in out_buffer])
    229  - elif self.output_type == 'tsv':
    230  - for res in out_buffer:
    231  - temp = res.to_tsv()
    232  - if temp is None or temp == '':
    233  - continue
    234  - out_data += '%s\r\n' % temp
    235  - elif self.output_type == 'json':
    236  - for res in out_buffer:
    237  - temp = res.to_json()
    238  - if temp is None or len(temp) <= 2:
    239  - continue
    240  - out_data += '%s\r\n' % temp
    241  -
    242  - else:
    243  - out_data = '\r\n'.join(out_buffer)
    244  - 
    245  - if self.out_file is not None:
    246  - with open(self.out_file, 'a+', newline = '') as f:
    247  - f.write(out_data)
    248  -
    249  - else:
    250  - if len(out_data) != 0:
    251  - print(out_data)
    252  - 
    253  - if self.show_pbar is True:
    254  - for key in pbar:
    255  - pbar[key].refresh()
    256  -
    257  - out_buffer = []
    258  - out_data = ''
    259  -
    260  - if final_iter:
    261  - asyncio.create_task(self.terminate())
    262  - return
    263  - 
    264  - try:
    265  - er = await asyncio.wait_for(self.res_q.get(), timeout = 5)
    266  - except asyncio.TimeoutError:
    267  - if self.show_pbar is True:
    268  - for key in pbar:
    269  - pbar[key].refresh()
    270  - 
    271  - if self.__total_finished == self.__total_targets and self.__gens_finished is True:
    272  - final_iter = True
    273  - continue
    274  - 
    275  - if er.result is not None:
    276  - if self.ext_result_q is not None:
    277  - await self.ext_result_q.put(RDPCAPResult(er, 'result'))
    278  - out_buffer.append(RDPCAPResult(er, 'result'))
    279  - if self.show_pbar is True:
    280  - pbar['results'].update(1)
    281  -
    282  -
    283  - if er.status == EnumResultStatus.ERROR:
    284  - self.__total_errors += 1
    285  - if self.ext_result_q is not None:
    286  - await self.ext_result_q.put(RDPCAPResult(er, 'error'))
    287  - out_buffer.append(RDPCAPResult(er, 'error'))
    288  - if self.show_pbar is True:
    289  - pbar['errors'].update(1)
    290  -
    291  -
    292  - if er.status == EnumResultStatus.FINISHED:
    293  - self.__total_finished += 1
    294  - res = RDPCAPProgressResult(self.__total_targets, self.__total_finished, self.__gens_finished, er.target)
    295  - if self.ext_result_q is not None:
    296  - await self.ext_result_q.put(RDPCAPResult(res, 'progress'))
    297  - out_buffer.append(RDPCAPResult(res, 'progress'))
    298  - if self.show_pbar is True:
    299  - pbar['targets'].update(1)
    300  -
    301  - if self.__total_finished == self.__total_targets and self.__gens_finished is True:
    302  - final_iter = True
    303  - continue
    304  - 
    305  - except asyncio.CancelledError:
    306  - return
    307  - except Exception as e:
    308  - logger.exception('result_processing inner')
    309  - asyncio.create_task(self.terminate())
    310  - return
    311  - 
    312  - except asyncio.CancelledError:
    313  - return
    314  - except Exception as e:
    315  - logger.exception('result_processing')
    316  - asyncio.create_task(self.terminate())
    317  - return
    318  - finally:
    319  - if self.ext_result_q is not None:
    320  - await self.ext_result_q.put(RDPCAPResult(None, 'finished'))
    321  - 
    322  - async def terminate(self):
    323  - for worker in self.workers:
    324  - worker.cancel()
    325  - if self.result_processing_task is not None:
    326  - self.result_processing_task.cancel()
    327  - 
    328  - async def setup(self):
    329  - try:
    330  - if self.res_q is None:
    331  - self.res_q = asyncio.Queue(self.worker_count)
    332  - self.result_processing_task = asyncio.create_task(self.result_processing())
    333  - if self.task_q is None:
    334  - self.task_q = asyncio.Queue()
    335  - 
    336  - for _ in range(self.worker_count):
    337  - self.workers.append(asyncio.create_task(self.worker()))
    338  - 
    339  - return True, None
    340  - except Exception as e:
    341  - return None, e
    342  - 
    343  - async def __generate_targets(self):
    344  - for target_gen in self.target_gens:
    345  - async for uid, target, err in target_gen.generate():
    346  - if err is not None:
    347  - print('Target gen error! %s' % err)
    348  - break
    349  -
    350  - self.__total_targets += 1
    351  - await self.task_q.put((uid, target))
    352  - await asyncio.sleep(0)
    353  - 
    354  - self.__gens_finished = True
    355  -
    356  - async def run(self):
    357  - try:
    358  - _, err = await self.setup()
    359  - if err is not None:
    360  - raise err
    361  - 
    362  - gen_task = asyncio.create_task(self.__generate_targets())
    363  -
    364  - await asyncio.gather(*self.workers)
    365  - await self.result_processing_task
    366  - return True, None
    367  - except Exception as e:
    368  - logger.exception('run')
    369  - return None, e
    370  - 
    371  -async def amain():
    372  - import argparse
    373  - import sys
    374  - 
    375  - parser = argparse.ArgumentParser(description='RDP capabilities scanner')
    376  - parser.add_argument('-v', '--verbose', action='count', default=0)
    377  - parser.add_argument('-w', '--worker-count', type=int, default=100, help='Parallell count')
    378  - parser.add_argument('-t', '--timeout', type=int, default=50, help='Timeout for each connection')
    379  - parser.add_argument('-s', '--stdin', action='store_true', help='Read targets from stdin')
    380  - parser.add_argument('--json', action='store_true', help='Output in JSON format')
    381  - parser.add_argument('--tsv', action='store_true', help='Output in TSV format. (TAB Separated Values)')
    382  - parser.add_argument('--progress', action='store_true', help='Show progress bar')
    383  - parser.add_argument('-o', '--out-file', help='Output file path.')
    384  - parser.add_argument('targets', nargs='*', help = 'Hostname or IP address or file with a list of targets')
    385  - args = parser.parse_args()
    386  - 
    387  - if args.verbose >=1:
    388  - logger.setLevel(logging.DEBUG)
    389  - 
    390  - if args.verbose > 2:
    391  - print('setting deepdebug')
    392  - logger.setLevel(1) #enabling deep debug
    393  - asyncio.get_event_loop().set_debug(True)
    394  - logging.basicConfig(level=logging.DEBUG)
    395  - 
    396  -
    397  - output_type = 'str'
    398  - if args.json is True:
    399  - output_type = 'json'
    400  - if args.tsv is True:
    401  - output_type = 'tsv'
    402  - 
    403  - enumerator = RDPCAPScan(
    404  - worker_count = args.worker_count,
    405  - timeout = args.timeout,
    406  - show_pbar = args.progress,
    407  - output_type = output_type,
    408  - out_file = args.out_file,
    409  - )
    410  - 
    411  - notfile = []
    412  - if len(args.targets) == 0 and args.stdin is True:
    413  - enumerator.target_gens.append(ListTargetGen(sys.stdin))
    414  - else:
    415  - for target in args.targets:
    416  - try:
    417  - f = open(target, 'r')
    418  - f.close()
    419  - enumerator.target_gens.append(FileTargetGen(target))
    420  - except:
    421  - notfile.append(target)
    422  -
    423  - if len(notfile) > 0:
    424  - enumerator.target_gens.append(ListTargetGen(notfile))
    425  - 
    426  - if len(enumerator.target_gens) == 0:
    427  - print('[-] No suitable targets were found!')
    428  - return
    429  -
    430  - await enumerator.run()
    431  - print('[+] Done!')
    432  - 
    433  -def main():
    434  - asyncio.run(amain())
    435  - 
    436  -if __name__ == '__main__':
    437  - main()
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/aardploginscan.py
    1  -import traceback
    2  -import uuid
    3  -import asyncio
    4  -import logging
    5  -import copy
    6  -import json
    7  - 
    8  -from aardwolf import logger
    9  -from aardwolf.commons.url import RDPConnectionURL
    10  -from aardwolf.commons.iosettings import RDPIOSettings
    11  -from aardwolf.examples.scancommons.targetgens import ListTargetGen, FileTargetGen
    12  -from aardwolf.examples.scancommons.internal import EnumResult, EnumResultStatus, EnumProgress
    13  -from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS
    14  -from aardwolf.commons.queuedata.constants import VIDEO_FORMAT
    15  -from tqdm import tqdm
    16  - 
    17  -class EnumResultFinal:
    18  - def __init__(self, obj, otype, err, target, target_id):
    19  - self.obj = obj
    20  - self.otype = otype
    21  - self.err = err
    22  - self.target = target
    23  - self.target_id = target_id
    24  - 
    25  - 
    26  - def __str__(self):
    27  - if self.err is not None:
    28  - return '[E] %s | %s | %s' % (self.target, self.err)
    29  - 
    30  - elif self.otype == 'result':
    31  - return '[R] %s | %s | %s' % (self.target_id, self.target, self.obj)
    32  - 
    33  - elif self.otype == 'progress':
    34  - return '[P][%s/%s][%s] %s' % (self.obj.total_finished, self.obj.total_targets, str(self.obj.gens_finished), self.obj.current_finished)
    35  - 
    36  - else:
    37  - return '[UNK]'
    38  -
    39  - def to_dict(self):
    40  - if self.otype == 'result':
    41  - return {
    42  - 'target' : self.target,
    43  - 'target_id' : self.target_id,
    44  - 'result': str(self.obj),
    45  - 'otype': self.otype,
    46  - }
    47  -
    48  - def to_tsv(self):
    49  - if self.otype == 'result':
    50  - return '%s\t%s\t%s' % (self.target, self.target_id, self.obj)
    51  -
    52  - def to_json(self):
    53  - t = self.to_dict()
    54  - if t is not None:
    55  - return json.dumps(self.to_dict())
    56  - 
    57  - 
    58  -class RDPLoginScanner:
    59  - def __init__(self, rdp_url, iosettings:RDPIOSettings, worker_count = 10, out_file = None, out_format = 'str', show_pbar = True, task_q = None, ext_result_q = None):
    60  - self.target_gens = []
    61  - self.rdp_mgr = rdp_url
    62  - if isinstance(rdp_url, RDPConnectionURL) is False:
    63  - self.rdp_mgr = RDPConnectionURL(rdp_url)
    64  - self.worker_count = worker_count
    65  - self.task_q = task_q
    66  - self.res_q = None
    67  - self.workers = []
    68  - self.result_processing_task = None
    69  - self.out_file = out_file
    70  - self.show_pbar = show_pbar
    71  - self.ext_result_q = ext_result_q
    72  - self.enum_url = False
    73  - self.out_format = out_format
    74  - 
    75  - self.iosettings = iosettings
    76  - self.flags_test = [SUPP_PROTOCOLS.HYBRID_EX, None]
    77  - 
    78  - self.__gens_finished = False
    79  - self.__total_targets = 0
    80  - self.__total_finished = 0
    81  - self.__total_errors = 0
    82  - 
    83  - async def __executor(self, tid, target):
    84  - try:
    85  - for i, proto in enumerate(self.flags_test):
    86  - result = 'YES'
    87  - if proto == None:
    88  - result = 'MAYBE'
    89  - ios = copy.deepcopy(self.iosettings)
    90  - ios.supported_protocols = proto
    91  - async with self.rdp_mgr.create_connection_newtarget(target, ios) as connection:
    92  - _, err = await connection.connect()
    93  - if err is not None:
    94  - result = 'NO'
    95  -
    96  - if result == 'NO' and i != (len(self.flags_test)-1):
    97  - # avoid printing NO multiple times
    98  - continue
    99  - 
    100  - await self.res_q.put(EnumResult(tid, target, result, status = EnumResultStatus.RESULT))
    101  - if result == 'YES':
    102  - break
    103  - 
    104  - except asyncio.CancelledError:
    105  - return
    106  - except Exception as e:
    107  - traceback.print_exc()
    108  - await self.res_q.put(EnumResult(tid, target, None, error = e, status = EnumResultStatus.ERROR))
    109  - finally:
    110  - await self.res_q.put(EnumResult(tid, target, None, status = EnumResultStatus.FINISHED))
    111  -
    112  - 
    113  - async def worker(self):
    114  - try:
    115  - while True:
    116  - indata = await self.task_q.get()
    117  - if indata is None:
    118  - return
    119  -
    120  - tid, target = indata
    121  - try:
    122  - await asyncio.wait_for(self.__executor(tid, target), timeout=10)
    123  - except asyncio.CancelledError:
    124  - return
    125  - except asyncio.TimeoutError as e:
    126  - await self.res_q.put(EnumResult(tid, target, None, error = e, status = EnumResultStatus.ERROR))
    127  - await self.res_q.put(EnumResult(tid, target, None, status = EnumResultStatus.FINISHED))
    128  - continue
    129  - except Exception as e:
    130  - logger.exception('worker')
    131  - continue
    132  - except asyncio.CancelledError:
    133  - return
    134  -
    135  - except Exception as e:
    136  - return e
    137  - 
    138  - async def result_processing(self):
    139  - try:
    140  - pbar = None
    141  - if self.show_pbar is True:
    142  - pbar = {}
    143  - pbar['targets'] = tqdm(desc='Targets ', unit='', position=0)
    144  - pbar['yes'] = tqdm(desc='Yes ', unit='', position=1)
    145  - pbar['no'] = tqdm(desc='No ', unit='', position=2)
    146  - pbar['maybe'] = tqdm(desc='Maybe ', unit='', position=3)
    147  - pbar['errors'] = tqdm(desc='Errors ', unit='', position=4)
    148  - pbar['finished'] = tqdm(desc='Finished', unit='', position=5)
    149  - 
    150  - out_buffer = []
    151  - final_iter = False
    152  - while True:
    153  - try:
    154  - if self.__gens_finished is True and self.show_pbar is True and pbar['targets'].total is None:
    155  - pbar['targets'].total = self.__total_targets
    156  - for key in pbar:
    157  - pbar[key].refresh()
    158  - 
    159  - if self.ext_result_q is not None:
    160  - out_buffer = []
    161  - 
    162  - if len(out_buffer) >= 100 or final_iter and self.ext_result_q is None:
    163  - out_data = ''
    164  - if self.out_format == 'str':
    165  - out_data = '\r\n'.join([str(x) for x in out_buffer])
    166  - elif self.out_format == 'tsv':
    167  - for line in [x.to_tsv() for x in out_buffer]:
    168  - if line is not None:
    169  - out_data += line + '\r\n'
    170  - elif self.out_format == 'json':
    171  - for line in [x.to_json() for x in out_buffer]:
    172  - if line is not None:
    173  - out_data += line + '\r\n'
    174  - if self.out_file is None:
    175  - print(out_data)
    176  - else:
    177  - with open(self.out_file, 'a', newline = '') as f:
    178  - f.write(out_data)
    179  - if self.show_pbar is True:
    180  - for key in pbar:
    181  - pbar[key].refresh()
    182  -
    183  - out_buffer = []
    184  - out_data = ''
    185  - 
    186  - if final_iter:
    187  - asyncio.create_task(self.terminate())
    188  - return
    189  - try:
    190  - er = await asyncio.wait_for(self.res_q.get(), timeout = 5)
    191  - except asyncio.TimeoutError:
    192  - if self.show_pbar is True:
    193  - for key in pbar:
    194  - pbar[key].refresh()
    195  - 
    196  - if self.__total_finished == self.__total_targets and self.__gens_finished is True:
    197  - final_iter = True
    198  - continue
    199  -
    200  - if er.status == EnumResultStatus.FINISHED:
    201  - self.__total_finished += 1
    202  - if self.show_pbar is True:
    203  - pbar['finished'].update(1)
    204  - 
    205  - obj = EnumProgress(self.__total_targets, self.__total_finished, self.__gens_finished, er.target)
    206  - if self.ext_result_q is not None:
    207  - await self.ext_result_q.put(EnumResultFinal(obj, 'progress', None, er.target, er.target_id))
    208  - out_buffer.append(EnumResultFinal(obj, 'progress', None, er.target, er.target_id))
    209  - if self.__total_finished == self.__total_targets and self.__gens_finished is True:
    210  - final_iter = True
    211  - continue
    212  -
    213  - if er.status == EnumResultStatus.RESULT:
    214  - if self.ext_result_q is not None:
    215  - await self.ext_result_q.put(EnumResultFinal(er.result, 'result', None, er.target, er.target_id))
    216  - out_buffer.append(EnumResultFinal(er.result, 'result', None, er.target, er.target_id))
    217  - if self.show_pbar is True:
    218  - if er.result in ['YES', 'NO', 'MAYBE']:
    219  - pbar[er.result.lower()].update(1)
    220  -
    221  - 
    222  - if er.status == EnumResultStatus.ERROR:
    223  - self.__total_errors += 1
    224  - if self.show_pbar is True:
    225  - pbar['errors'].update(1)
    226  - 
    227  - 
    228  - except asyncio.CancelledError:
    229  - return
    230  - except Exception as e:
    231  - logger.exception('result_processing inner')
    232  - asyncio.create_task(self.terminate())
    233  - return
    234  - except asyncio.CancelledError:
    235  - return
    236  - except Exception as e:
    237  - logger.exception('result_processing')
    238  - asyncio.create_task(self.terminate())
    239  - finally:
    240  - if self.ext_result_q is not None:
    241  - await self.ext_result_q.put(EnumResultFinal(None, 'finished', None, None, None))
    242  - 
    243  - async def terminate(self):
    244  - for worker in self.workers:
    245  - worker.cancel()
    246  - if self.result_processing_task is not None:
    247  - self.result_processing_task.cancel()
    248  - 
    249  - async def setup(self):
    250  - try:
    251  - if self.res_q is None:
    252  - self.res_q = asyncio.Queue(self.worker_count)
    253  - self.result_processing_task = asyncio.create_task(self.result_processing())
    254  - if self.task_q is None:
    255  - self.task_q = asyncio.Queue(self.worker_count)
    256  - 
    257  - for _ in range(self.worker_count):
    258  - self.workers.append(asyncio.create_task(self.worker()))
    259  - 
    260  - return True, None
    261  - except Exception as e:
    262  - return None, e
    263  - 
    264  - async def __generate_targets(self):
    265  - if self.enum_url is True:
    266  - self.__total_targets += 1
    267  - await self.task_q.put((str(uuid.uuid4()), self.rdp_mgr.get_target().get_hostname_or_ip()))
    268  -
    269  - for target_gen in self.target_gens:
    270  - async for uid, target, err in target_gen.generate():
    271  - if err is not None:
    272  - print('Target gen error! %s' % err)
    273  - break
    274  -
    275  - self.__total_targets += 1
    276  - await self.task_q.put((uid, target))
    277  - await asyncio.sleep(0)
    278  - 
    279  - self.__gens_finished = True
    280  -
    281  - async def run(self):
    282  - try:
    283  - _, err = await self.setup()
    284  - if err is not None:
    285  - raise err
    286  -
    287  - gen_task = asyncio.create_task(self.__generate_targets())
    288  -
    289  - await asyncio.gather(*self.workers)
    290  - await self.result_processing_task
    291  - return True, None
    292  - except Exception as e:
    293  - logger.exception('run')
    294  - return None, e
    295  - 
    296  -async def amain():
    297  - import argparse
    298  - import sys
    299  - 
    300  - parser = argparse.ArgumentParser(description='RDP login scanner', formatter_class=argparse.RawDescriptionHelpFormatter)
    301  - parser.add_argument('-v', '--verbose', action='count', default=0)
    302  - parser.add_argument('-w', '--worker-count', type=int, default=50, help='Parallell count')
    303  - parser.add_argument('-o', '--out-file', help='Output file path.')
    304  - parser.add_argument('--progress', action='store_true', help='Show progress bar. Pleasue use out-file with this!!')
    305  - parser.add_argument('--tsv', action='store_true', help='Output results in TSV format')
    306  - parser.add_argument('--json', action='store_true', help='Output results in JSON format')
    307  - parser.add_argument('-s', '--stdin', action='store_true', help='Read targets from stdin')
    308  - parser.add_argument('url', help='Connection URL base, target can be set to anything. Example: "rdp+ntlm-password://TEST\\victim:[email protected]"')
    309  - parser.add_argument('targets', nargs='*', help = 'Hostname or IP address or file with a list of targets')
    310  - 
    311  - args = parser.parse_args()
    312  - 
    313  - if args.verbose >=1:
    314  - logger.setLevel(logging.WARNING)
    315  - 
    316  - if args.verbose > 2:
    317  - print('setting deepdebug')
    318  - logger.setLevel(1) #enabling deep debug
    319  - asyncio.get_event_loop().set_debug(True)
    320  - logging.basicConfig(level=logging.DEBUG)
    321  - 
    322  - oformat = 'str'
    323  - if args.tsv is True:
    324  - oformat = 'tsv'
    325  - if args.json is True:
    326  - oformat = 'json'
    327  - 
    328  - rdp_url = args.url
    329  - 
    330  - iosettings = RDPIOSettings()
    331  - iosettings.channels = []
    332  - iosettings.video_out_format = VIDEO_FORMAT.RAW
    333  - iosettings.clipboard_use_pyperclip = False
    334  - 
    335  - enumerator = RDPLoginScanner(
    336  - rdp_url,
    337  - iosettings,
    338  - worker_count = args.worker_count,
    339  - out_file = args.out_file,
    340  - show_pbar = args.progress,
    341  - out_format=oformat,
    342  - )
    343  -
    344  - notfile = []
    345  - if len(args.targets) == 0 and args.stdin is True:
    346  - enumerator.target_gens.append(ListTargetGen(sys.stdin))
    347  - else:
    348  - for target in args.targets:
    349  - try:
    350  - f = open(target, 'r')
    351  - f.close()
    352  - enumerator.target_gens.append(FileTargetGen(target))
    353  - except:
    354  - notfile.append(target)
    355  -
    356  - if len(notfile) > 0:
    357  - enumerator.target_gens.append(ListTargetGen(notfile))
    358  - 
    359  - if len(enumerator.target_gens) == 0:
    360  - enumerator.enum_url = True
    361  - 
    362  - await enumerator.run()
    363  - 
    364  -def main():
    365  - asyncio.run(amain())
    366  - 
    367  -if __name__ == '__main__':
    368  - main()
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/aardpscreenshot.py
    1  -import traceback
    2  -import uuid
    3  -import asyncio
    4  -import logging
    5  - 
    6  -from aardwolf import logger
    7  -from aardwolf.commons.url import RDPConnectionURL
    8  -from aardwolf.commons.iosettings import RDPIOSettings
    9  -from aardwolf.examples.scancommons.targetgens import ListTargetGen, FileTargetGen
    10  -from aardwolf.examples.scancommons.internal import EnumResult, EnumResultStatus, EnumProgress
    11  -from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT
    12  -from tqdm import tqdm
    13  - 
    14  -class EnumResultFinal:
    15  - def __init__(self, obj, otype, err, target, target_id):
    16  - self.obj = obj
    17  - self.otype = otype
    18  - self.err = err
    19  - self.target = target
    20  - self.target_id = target_id
    21  - 
    22  - 
    23  - def __str__(self):
    24  - if self.err is not None:
    25  - return '[E] %s | %s | %s' % (self.target, self.err)
    26  - 
    27  - elif self.otype == 'result':
    28  - return '[S] %s ' % (self.target)
    29  - 
    30  - elif self.otype == 'progress':
    31  - return '[P][%s/%s][%s] %s' % (self.obj.total_finished, self.obj.total_targets, str(self.obj.gens_finished), self.obj.current_finished)
    32  - 
    33  - else:
    34  - return '[UNK]'
    35  - 
    36  - 
    37  -class RDPScreenGrabberScanner:
    38  - def __init__(self, rdp_url, iosettings, worker_count = 10, out_dir = None, screentime = 5, show_pbar = True, task_q = None, res_q = None, ext_result_q = None):
    39  - self.target_gens = []
    40  - self.rdp_mgr = rdp_url
    41  - if isinstance(rdp_url, RDPConnectionURL) is False:
    42  - self.rdp_mgr = RDPConnectionURL(rdp_url)
    43  - self.worker_count = worker_count
    44  - self.task_q = task_q
    45  - self.res_q = res_q
    46  - self.screentime = screentime
    47  - self.workers = []
    48  - self.result_processing_task = None
    49  - self.out_dir = out_dir
    50  - self.show_pbar = show_pbar
    51  - self.ext_result_q = ext_result_q
    52  - self.enum_url = False
    53  - 
    54  - self.iosettings = iosettings
    55  - 
    56  - self.__gens_finished = False
    57  - self.__total_targets = 0
    58  - self.__total_finished = 0
    59  - self.__total_errors = 0
    60  - 
    61  - async def __executor(self, tid, target):
    62  - connection = None
    63  - try:
    64  - connection = self.rdp_mgr.create_connection_newtarget(target, self.iosettings)
    65  - _, err = await connection.connect()
    66  - if err is not None:
    67  - raise err
    68  -
    69  - await asyncio.sleep(self.screentime)
    70  - 
    71  - except asyncio.CancelledError:
    72  - return
    73  - except Exception as e:
    74  - traceback.print_exc()
    75  - await self.res_q.put(EnumResult(tid, target, None, error = e, status = EnumResultStatus.ERROR))
    76  - finally:
    77  - if connection is not None and connection.desktop_buffer_has_data is True:
    78  - buffer = connection.get_desktop_buffer(VIDEO_FORMAT.PIL)
    79  -
    80  - if self.ext_result_q is None:
    81  - filename = 'screen_%s_%s.png' % (target, tid)
    82  - buffer.save(filename,'png')
    83  - await self.res_q.put(EnumResult(tid, target, None, status = EnumResultStatus.RESULT))
    84  - else:
    85  - await self.res_q.put(EnumResult(tid, target, buffer, status = EnumResultStatus.RESULT))
    86  - 
    87  - await self.res_q.put(EnumResult(tid, target, None, status = EnumResultStatus.FINISHED))
    88  -
    89  - 
    90  - async def worker(self):
    91  - try:
    92  - while True:
    93  - indata = await self.task_q.get()
    94  - if indata is None:
    95  - return
    96  -
    97  - tid, target = indata
    98  - try:
    99  - await asyncio.wait_for(self.__executor(tid, target), timeout=self.screentime+5)
    100  - except asyncio.CancelledError:
    101  - return
    102  - except asyncio.TimeoutError as e:
    103  - await self.res_q.put(EnumResult(tid, target, None, error = e, status = EnumResultStatus.ERROR))
    104  - await self.res_q.put(EnumResult(tid, target, None, status = EnumResultStatus.FINISHED))
    105  - continue
    106  - except Exception as e:
    107  - logger.exception('worker')
    108  - continue
    109  - except asyncio.CancelledError:
    110  - return
    111  -
    112  - except Exception as e:
    113  - return e
    114  - 
    115  - async def result_processing(self):
    116  - try:
    117  - pbar = None
    118  - if self.show_pbar is True:
    119  - pbar = {}
    120  - pbar['targets'] = tqdm(desc='Targets ', unit='', position=0)
    121  - pbar['screencaps'] = tqdm(desc='Screencaps ', unit='', position=1)
    122  - pbar['errors'] = tqdm(desc='Conn Errors ', unit='', position=2)
    123  - 
    124  - out_buffer = []
    125  - final_iter = False
    126  - while True:
    127  - try:
    128  - if self.__gens_finished is True and self.show_pbar is True and pbar['targets'].total is None:
    129  - pbar['targets'].total = self.__total_targets
    130  - for key in pbar:
    131  - pbar[key].refresh()
    132  - 
    133  - if self.ext_result_q is not None:
    134  - out_buffer = []
    135  - 
    136  - if len(out_buffer) >= 10 or final_iter and self.ext_result_q is None:
    137  - out_data = ''
    138  - if self.show_pbar is False:
    139  - out_data = '\r\n'.join([str(x) for x in out_buffer])
    140  - print(out_data)
    141  - else:
    142  - for key in pbar:
    143  - pbar[key].refresh()
    144  -
    145  - out_buffer = []
    146  - out_data = ''
    147  - 
    148  - if final_iter:
    149  - asyncio.create_task(self.terminate())
    150  - return
    151  - try:
    152  - er = await asyncio.wait_for(self.res_q.get(), timeout = 5)
    153  - except asyncio.TimeoutError:
    154  - if self.show_pbar is True:
    155  - for key in pbar:
    156  - pbar[key].refresh()
    157  - 
    158  - if self.__total_finished == self.__total_targets and self.__gens_finished is True:
    159  - final_iter = True
    160  - continue
    161  -
    162  - if er.status == EnumResultStatus.FINISHED:
    163  - self.__total_finished += 1
    164  - if self.show_pbar is True:
    165  - pbar['finished'].update(1)
    166  - 
    167  - obj = EnumProgress(self.__total_targets, self.__total_finished, self.__gens_finished, er.target)
    168  - if self.ext_result_q is not None:
    169  - await self.ext_result_q.put(EnumResultFinal(obj, 'progress', None, er.target, er.target_id))
    170  - out_buffer.append(EnumResultFinal(obj, 'progress', None, er.target, er.target_id))
    171  - if self.__total_finished == self.__total_targets and self.__gens_finished is True:
    172  - final_iter = True
    173  - continue
    174  -
    175  - if er.status == EnumResultStatus.RESULT:
    176  - if self.ext_result_q is not None:
    177  - await self.ext_result_q.put(EnumResultFinal(er.result, 'result', None, er.target, er.target_id))
    178  - out_buffer.append(EnumResultFinal(er.result, 'result', None, er.target, er.target_id))
    179  - if self.show_pbar is True:
    180  - pbar['screencaps'].update(1)
    181  - 
    182  - if er.status == EnumResultStatus.ERROR:
    183  - self.__total_errors += 1
    184  - if self.show_pbar is True:
    185  - pbar['errors'].update(1)
    186  - 
    187  - 
    188  - except asyncio.CancelledError:
    189  - return
    190  - except Exception as e:
    191  - logger.exception('result_processing inner')
    192  - asyncio.create_task(self.terminate())
    193  - return
    194  - except asyncio.CancelledError:
    195  - return
    196  - except Exception as e:
    197  - logger.exception('result_processing')
    198  - asyncio.create_task(self.terminate())
    199  - finally:
    200  - if self.ext_result_q is not None:
    201  - await self.ext_result_q.put(EnumResultFinal(None, 'finished', None, None, None))
    202  - 
    203  - async def terminate(self):
    204  - for worker in self.workers:
    205  - worker.cancel()
    206  - if self.result_processing_task is not None:
    207  - self.result_processing_task.cancel()
    208  - 
    209  - async def setup(self):
    210  - try:
    211  - if self.res_q is None:
    212  - self.res_q = asyncio.Queue(self.worker_count)
    213  - self.result_processing_task = asyncio.create_task(self.result_processing())
    214  - if self.task_q is None:
    215  - self.task_q = asyncio.Queue(self.worker_count)
    216  - 
    217  - for _ in range(self.worker_count):
    218  - self.workers.append(asyncio.create_task(self.worker()))
    219  - 
    220  - return True, None
    221  - except Exception as e:
    222  - return None, e
    223  - 
    224  - async def __generate_targets(self):
    225  - if self.enum_url is True:
    226  - self.__total_targets += 1
    227  - await self.task_q.put((str(uuid.uuid4()), self.rdp_mgr.get_target().get_hostname_or_ip()))
    228  -
    229  - for target_gen in self.target_gens:
    230  - async for uid, target, err in target_gen.generate():
    231  - if err is not None:
    232  - print('Target gen error! %s' % err)
    233  - break
    234  -
    235  - self.__total_targets += 1
    236  - await self.task_q.put((uid, target))
    237  - await asyncio.sleep(0)
    238  - 
    239  - self.__gens_finished = True
    240  -
    241  - async def run(self):
    242  - try:
    243  - _, err = await self.setup()
    244  - if err is not None:
    245  - raise err
    246  -
    247  - gen_task = asyncio.create_task(self.__generate_targets())
    248  -
    249  - await asyncio.gather(*self.workers)
    250  - await self.result_processing_task
    251  - return True, None
    252  - except Exception as e:
    253  - logger.exception('run')
    254  - return None, e
    255  - 
    256  -async def amain():
    257  - import argparse
    258  - import sys
    259  - 
    260  - parser = argparse.ArgumentParser(description='RDP/VNC Screen grabber', formatter_class=argparse.RawDescriptionHelpFormatter)
    261  - parser.add_argument('-v', '--verbose', action='count', default=0)
    262  - parser.add_argument('--screentime', type=int, default=5, help='Time to wait for desktop image')
    263  - parser.add_argument('-w', '--worker-count', type=int, default=50, help='Parallell count')
    264  - parser.add_argument('-o', '--out-dir', help='Output directory path.')
    265  - parser.add_argument('--progress', action='store_true', help='Show progress bar')
    266  - parser.add_argument('-s', '--stdin', action='store_true', help='Read targets from stdin')
    267  - parser.add_argument('--res', default = '1024x768', help='Resolution in "WIDTHxHEIGHT" format. Default: "1024x768"')
    268  - parser.add_argument('url', help='Connection URL base, target can be set to anything. Example: "rdp+ntlm-password://TEST\\victim:[email protected]"')
    269  - parser.add_argument('targets', nargs='*', help = 'Hostname or IP address or file with a list of targets')
    270  - 
    271  - args = parser.parse_args()
    272  - 
    273  - if args.verbose >=1:
    274  - logger.setLevel(logging.DEBUG)
    275  - 
    276  - if args.verbose > 2:
    277  - print('setting deepdebug')
    278  - logger.setLevel(1) #enabling deep debug
    279  - asyncio.get_event_loop().set_debug(True)
    280  - logging.basicConfig(level=logging.DEBUG)
    281  - 
    282  - rdp_url = args.url
    283  - width, height = args.res.upper().split('X')
    284  - height = int(height)
    285  - width = int(width)
    286  - 
    287  - iosettings = RDPIOSettings()
    288  - iosettings.channels = []
    289  - iosettings.video_width = width
    290  - iosettings.video_height = height
    291  - iosettings.video_bpp_min = 15 #servers dont support 8 any more :/
    292  - iosettings.video_bpp_max = 32
    293  - iosettings.video_out_format = VIDEO_FORMAT.PNG #PIL produces incorrect picture for some reason?! TODO: check bug
    294  - iosettings.clipboard_use_pyperclip = False
    295  - 
    296  - enumerator = RDPScreenGrabberScanner(
    297  - rdp_url,
    298  - iosettings,
    299  - worker_count = args.worker_count,
    300  - out_dir = args.out_dir,
    301  - show_pbar = args.progress,
    302  - screentime = args.screentime,
    303  - )
    304  -
    305  - notfile = []
    306  - if len(args.targets) == 0 and args.stdin is True:
    307  - enumerator.target_gens.append(ListTargetGen(sys.stdin))
    308  - else:
    309  - for target in args.targets:
    310  - try:
    311  - f = open(target, 'r')
    312  - f.close()
    313  - enumerator.target_gens.append(FileTargetGen(target))
    314  - except:
    315  - notfile.append(target)
    316  -
    317  - if len(notfile) > 0:
    318  - enumerator.target_gens.append(ListTargetGen(notfile))
    319  - 
    320  - if len(enumerator.target_gens) == 0:
    321  - enumerator.enum_url = True
    322  - 
    323  - await enumerator.run()
    324  - 
    325  -def main():
    326  - asyncio.run(amain())
    327  - 
    328  -if __name__ == '__main__':
    329  - main()
  • ■ ■ ■ ■ ■
    aardwolf/examples/scanners/__init__.py
     1 + 
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/scanners/__main__.py
     1 + 
     2 +import asyncio
     3 +import logging
     4 +from aardwolf.commons.factory import RDPConnectionFactory
     5 +from aardwolf.commons.queuedata.constants import VIDEO_FORMAT
     6 +from aardwolf.commons.iosettings import RDPIOSettings
     7 +from asysocks.unicomm.common.scanner.targetgen import UniTargetGen
     8 +from asysocks.unicomm.common.scanner.scanner import UniScanner
     9 +from aiosmb import logger
     10 + 
     11 +from aardwolf.examples.scanners.rdplogin import RDPLoginScanner
     12 +from aardwolf.examples.scanners.rdpscaps import RDPCapabilitiesScanner
     13 +from aardwolf.examples.scanners.rdpscreen import RDPScreenshotScanner
     14 + 
     15 +rdpscan_options = {
     16 + 'login' : (RDPLoginScanner,'Checks if user can log in to hosts'),
     17 + 'caps' : (RDPCapabilitiesScanner,'Lists RDP connection flags available'),
     18 + 'screen': (RDPScreenshotScanner,'Takes screenshot of the remote session'),
     19 +}
     20 + 
     21 +async def amain():
     22 + import argparse
     23 + 
     24 + scannertpes_usage = '\r\nall: Runs all scanners\r\n'
     25 + for k in rdpscan_options:
     26 + scannertpes_usage += '%s: %s\r\n' % (k, rdpscan_options[k][1])
     27 +
     28 + usage = """
     29 +Scanner types (-s param):
     30 + %s
     31 +"""% (scannertpes_usage,)
     32 + 
     33 + parser = argparse.ArgumentParser(description='RDP scanner', usage=usage)
     34 + parser.add_argument('-w', '--worker-count', type=int, default=100, help='Parallell count')
     35 + parser.add_argument('-t', '--timeout', type=int, default=10, help='Timeout for each connection')
     36 + parser.add_argument('--no-progress', action='store_false', help='Disable progress bar')
     37 + parser.add_argument('-o', '--out-file', help='Output file path.')
     38 + parser.add_argument('-s', '--scan', nargs='+', required=True, help='Scanner type')
     39 + parser.add_argument('-e', '--errors', action='store_true', help='Includes errors in output. It will mess up the formatting!')
     40 + parser.add_argument('url', help = 'Connection string in URL format')
     41 + parser.add_argument('targets', nargs='*', help = 'Hostname or IP address or file with a list of targets')
     42 + args = parser.parse_args()
     43 + 
     44 + if len(args.targets) == 0:
     45 + print('No targets defined!')
     46 + return
     47 +
     48 + logger.setLevel(logging.CRITICAL)
     49 + 
     50 + iosettings = RDPIOSettings()
     51 + iosettings.channels = []
     52 + iosettings.video_out_format = VIDEO_FORMAT.RAW
     53 + iosettings.clipboard_use_pyperclip = False
     54 +
     55 + connectionfactory = RDPConnectionFactory.from_url(args.url, iosettings)
     56 + scantypes = []
     57 + for x in args.scan:
     58 + scantypes.append(x.lower())
     59 + executors = []
     60 + if 'all' in scantypes:
     61 + for k in rdpscan_options:
     62 + executors.append(rdpscan_options[k][0](connectionfactory))
     63 + else:
     64 + for scantype in scantypes:
     65 + if scantype not in rdpscan_options:
     66 + print('Unknown scan type: "%s"' % scantype)
     67 + return
     68 + executors.append(rdpscan_options[scantype][0](connectionfactory))
     69 +
     70 + tgen = UniTargetGen.from_list(args.targets)
     71 + scanner = UniScanner('RDPScanner', executors, [tgen], worker_count=args.worker_count, host_timeout=args.timeout)
     72 + await scanner.scan_and_process(progress=args.no_progress, out_file=args.out_file, include_errors=args.errors)
     73 + 
     74 +def main():
     75 + asyncio.run(amain())
     76 + 
     77 +if __name__ == '__main__':
     78 + main()
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/scanners/rdplogin.py
     1 +import copy
     2 +from asysocks.unicomm.common.scanner.common import *
     3 +from aardwolf.commons.factory import RDPConnectionFactory
     4 +from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS
     5 + 
     6 + 
     7 +class RDPLoginRes:
     8 + def __init__(self, success):
     9 + self.success = success
     10 + 
     11 + def get_header(self):
     12 + return ['success']
     13 + 
     14 + def to_line(self, separator = '\t'):
     15 + return str(self.success)
     16 + 
     17 +class RDPLoginScanner:
     18 + def __init__(self, factory:RDPConnectionFactory):
     19 + self.factory:RDPConnectionFactory = factory
     20 + self.flags_test = [SUPP_PROTOCOLS.HYBRID_EX, None]
     21 + 
     22 + async def run(self, targetid, target, out_queue):
     23 + try:
     24 + for i, proto in enumerate(self.flags_test):
     25 + result = 'TRUE'
     26 + if proto == None:
     27 + result = 'MAYBE'
     28 + ios = self.factory.get_settings()
     29 + ios.supported_protocols = proto
     30 + async with self.factory.create_connection_newtarget(target, ios) as connection:
     31 + _, err = await connection.connect()
     32 + if err is not None:
     33 + result = 'FALSE'
     34 +
     35 + if result == 'FALSE' and i != (len(self.flags_test)-1):
     36 + # avoid printing NO multiple times
     37 + continue
     38 +
     39 + await out_queue.put(ScannerData(target, RDPLoginRes(result)))
     40 + if result == 'TRUE':
     41 + break
     42 + 
     43 + except Exception as e:
     44 + await out_queue.put(ScannerError(target, e))
     45 + 
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/scanners/rdpscaps.py
     1 +import copy
     2 +from asysocks.unicomm.common.scanner.common import *
     3 +from aardwolf.commons.factory import RDPConnectionFactory
     4 +from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS
     5 +from aardwolf.network.tpkt import TPKTPacketizer
     6 +from aardwolf.network.x224 import X224Network
     7 +from asysocks.unicomm.client import UniClient
     8 +from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS, NEG_FLAGS
     9 + 
     10 + 
     11 +class RDPCapabilitiesRes:
     12 + def __init__(self, restrictedadmin,restrictedauth,rdp,ssl,hybrid,hybridex):
     13 + self.restrictedadmin = restrictedadmin
     14 + self.restrictedauth = restrictedauth
     15 + self.rdp = rdp
     16 + self.ssl = ssl
     17 + self.hybrid = hybrid
     18 + self.hybridex = hybridex
     19 + 
     20 + def get_header(self):
     21 + return ['restrictedadmin', 'restrictedauth', 'rdp', 'ssl', 'hybrid', 'hybridex']
     22 + 
     23 + def to_line(self, separator = '\t'):
     24 + return separator.join([
     25 + str(self.restrictedadmin),
     26 + str(self.restrictedauth),
     27 + str(self.rdp),
     28 + str(self.ssl),
     29 + str(self.hybrid),
     30 + str(self.hybridex),
     31 + ])
     32 + 
     33 +class RDPCapabilitiesScanner:
     34 + def __init__(self, factory:RDPConnectionFactory):
     35 + self.factory:RDPConnectionFactory = factory
     36 + self.protoflags = [SUPP_PROTOCOLS.RDP, SUPP_PROTOCOLS.SSL, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID_EX]
     37 + 
     38 + 
     39 +
     40 + async def scan_flags(self, target, negflag, protoflag):
     41 + try:
     42 + packetizer = TPKTPacketizer()
     43 + client = UniClient(target, packetizer)
     44 + connection = await client.connect()
     45 + 
     46 + # X224 channel is on top of TPKT, performs the initial negotiation
     47 + # between the server and our client (restricted admin mode, authentication methods etc)
     48 + # are set here
     49 + x224net = X224Network(connection)
     50 + 
     51 + reply, err = await x224net.client_negotiate(negflag, protoflag, to_raise=False)
     52 + if err is not None:
     53 + raise err
     54 + 
     55 + if reply.rdpNegData.type == 3:
     56 + #server refused our flags
     57 + return False, reply, None
     58 + 
     59 + return True, reply, None
     60 + except Exception as e:
     61 + return False, None, e
     62 + 
     63 + async def run(self, targetid, target, out_queue):
     64 + try:
     65 + rdp = None
     66 + ssl = None
     67 + hybrid = None
     68 + hybridex = None
     69 + restrictedadmin = None
     70 + restrictedauth = None
     71 + 
     72 + rdptarget = self.factory.create_connection_newtarget(target, self.factory.get_settings()).target
     73 + 
     74 + for protoflag in self.protoflags:
     75 + negflag = 0
     76 + ok, reply, err = await self.scan_flags(rdptarget, negflag, protoflag)
     77 + if err is not None:
     78 + raise err
     79 + if ok is True:
     80 + if SUPP_PROTOCOLS.SSL == reply.rdpNegData.selectedProtocol:
     81 + ssl = True
     82 + if SUPP_PROTOCOLS.HYBRID in reply.rdpNegData.selectedProtocol:
     83 + hybrid = True
     84 + if SUPP_PROTOCOLS.HYBRID_EX in reply.rdpNegData.selectedProtocol:
     85 + hybridex = True
     86 + if SUPP_PROTOCOLS.RDP == reply.rdpNegData.selectedProtocol:
     87 + rdp = True
     88 + 
     89 + ok, reply, err = await self.scan_flags(rdptarget, NEG_FLAGS.RESTRICTED_ADMIN_MODE_REQUIRED, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID)
     90 + if err is not None:
     91 + raise err
     92 + restrictedadmin = ok
     93 + ok, reply, err = await self.scan_flags(rdptarget, NEG_FLAGS.REDIRECTED_AUTHENTICATION_MODE_REQUIRED, SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID)
     94 + if err is not None:
     95 + raise err
     96 + restrictedauth = ok
     97 + 
     98 + await out_queue.put(ScannerData(target, RDPCapabilitiesRes(rdp, ssl, hybrid, hybridex, restrictedadmin, restrictedauth)))
     99 + 
     100 + except Exception as e:
     101 + await out_queue.put(ScannerError(target, e))
     102 + 
  • ■ ■ ■ ■ ■ ■
    aardwolf/examples/scanners/rdpscreen.py
     1 +import asyncio
     2 +import io
     3 +import base64
     4 +import datetime
     5 +from asysocks.unicomm.common.scanner.common import *
     6 +from aardwolf.commons.factory import RDPConnectionFactory
     7 +from aardwolf.protocol.x224.constants import SUPP_PROTOCOLS
     8 +from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT
     9 +from aardwolf.commons.iosettings import RDPIOSettings
     10 + 
     11 + 
     12 +class RDPScreenshotRes:
     13 + def __init__(self, target, screendata):
     14 + self.target = target
     15 + self.screendata = screendata
     16 + 
     17 + def get_fname(self):
     18 + return 'rdpscreen_%s_%s.png' % (self.target, datetime.datetime.now().strftime("%Y%m%d_%H%M%S"))
     19 + 
     20 + def get_fdata(self):
     21 + return self.screendata
     22 + 
     23 + def get_header(self):
     24 + return ['screendata']
     25 + 
     26 + def to_line(self, separator = '\t'):
     27 + return base64.b64encode(self.screendata).decode()
     28 + 
     29 +class RDPScreenshotScanner:
     30 + def __init__(self, factory:RDPConnectionFactory):
     31 + self.factory:RDPConnectionFactory = factory
     32 + self.screentime = 5
     33 + self.iosettings = RDPIOSettings()
     34 + self.iosettings.channels = []
     35 + self.iosettings.video_width = 1024
     36 + self.iosettings.video_height = 768
     37 + self.iosettings.video_bpp_min = 15 #servers dont support 8 any more :/
     38 + self.iosettings.video_bpp_max = 32
     39 + self.iosettings.video_out_format = VIDEO_FORMAT.PNG #PIL produces incorrect picture for some reason?! TODO: check bug
     40 + self.iosettings.clipboard_use_pyperclip = False
     41 + 
     42 + 
     43 + async def run(self, targetid, target, out_queue):
     44 + connection = None
     45 + try:
     46 + connection = self.factory.create_connection_newtarget(target, self.iosettings)
     47 + _, err = await connection.connect()
     48 + if err is not None:
     49 + raise err
     50 +
     51 + await asyncio.sleep(self.screentime)
     52 + 
     53 + except asyncio.CancelledError:
     54 + return
     55 + except Exception as e:
     56 + raise
     57 + finally:
     58 + if connection is not None and connection.desktop_buffer_has_data is True:
     59 + buffer = connection.get_desktop_buffer(VIDEO_FORMAT.PIL)
     60 + data = io.BytesIO()
     61 + buffer.save(data,'png')
     62 + data.seek(0,0)
     63 + await out_queue.put(ScannerData(target, RDPScreenshotRes(target, data.read())))
     64 + 
  • ■ ■ ■ ■ ■ ■
    aardwolf/extensions/RDPECLIP/channel.py
    skipped 26 lines
    27 27   self.use_pyperclip = iosettings.clipboard_use_pyperclip
    28 28   self.status = CLIPBRDSTATUS.WAITING_SERVER_INIT
    29 29   self.compression_needed = False #TODO: tie it to flags
    30  - self.channel_data_out_q = asyncio.Queue()
    31  - self.channel_data_monitor_task = None
    32 30   self.supported_formats = [CLIPBRD_FORMAT.CF_UNICODETEXT] #, CLIPBRD_FORMAT.CF_HDROP
    33 31   self.server_caps = None
    34 32   self.server_general_caps = None
    skipped 15 lines
    50 48   if not pyperclip.is_available():
    51 49   logger.info("pyperclip - Copy functionality available!")
    52 50  
    53  - 
    54  - self.channel_data_monitor_task = asyncio.create_task(self.channel_data_monitor())
    55  - #self.process_msg_in_task = asyncio.create_task(self.process_msg_in_task())
    56 51   return True, None
    57 52   except Exception as e:
    58 53   return None, e
    59 54   
    60 55   async def stop(self):
    61 56   try:
    62  - if self.channel_data_monitor_task is not None:
    63  - self.channel_data_monitor_task.cancel()
    64  -
    65 57   return True, None
    66 58   except Exception as e:
    67 59   return None, e
    68 60   
    69  - async def channel_data_monitor(self):
    70  - try:
    71  - while True:
    72  - data = await self.channel_data_out_q.get()
    73  - if len(data) < 16000:
    74  - if self.compression_needed is False:
    75  - flags = CHANNEL_FLAG.CHANNEL_FLAG_FIRST|CHANNEL_FLAG.CHANNEL_FLAG_LAST|CHANNEL_FLAG.CHANNEL_FLAG_SHOW_PROTOCOL
    76  - packet = CHANNEL_PDU_HEADER.serialize_packet(flags, data)
    77  - 
    78  - else:
    79  - raise NotImplementedError('Compression not implemented!')
    80  - else:
    81  - raise NotImplementedError('Chunked send not implemented!')
    82  - 
    83  - sec_hdr = None
    84  - if self.connection.cryptolayer is not None:
    85  - sec_hdr = TS_SECURITY_HEADER()
    86  - sec_hdr.flags = SEC_HDR_FLAG.ENCRYPT
    87  - sec_hdr.flagsHi = 0
    88  - 
    89  - await self.connection.handle_out_data(packet, sec_hdr, None, None, self.channel_id, False)
    90  - 
    91  - return True, False
    92  - except Exception as e:
    93  - traceback.print_exc()
    94  - return None,e
    95  - 
    96 61   async def __send_capabilities(self):
    97 62   # server sent monitor ready, now we must send our capabilites
    98 63   try:
    skipped 5 lines
    104 69   caps.capabilitySets.append(gencap)
    105 70   
    106 71   msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_CLIP_CAPS, 0, caps)
    107  - await self.channel_data_out_q.put(msg)
     72 + await self.fragment_and_send(msg)
    108 73   
    109 74   ## if remote drive is attached this should be sent
    110 75   # sending tempdir location
    111 76   # tempdir = CLIPRDR_TEMP_DIRECTORY()
    112 77   # tempdir.wszTempDir = 'C:\\Windows\\Temp\\'
    113 78   # msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_TEMP_DIRECTORY, 0, tempdir)
    114  - # await self.channel_data_out_q.put(msg)
     79 + # await self.fragment_and_send(msg)
    115 80   
    116 81   # synchronizing formatlist
    117 82   fmtl = CLIPRDR_FORMAT_LIST()
    skipped 4 lines
    122 87   
    123 88   self.status = CLIPBRDSTATUS.CLIENT_INIT
    124 89   msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_FORMAT_LIST, 0, fmtl)
    125  - await self.channel_data_out_q.put(msg)
     90 + await self.fragment_and_send(msg)
    126 91  
    127 92   return True, None
    128 93   except Exception as e:
    skipped 12 lines
    141 106  
    142 107   # sending back an OK
    143 108   msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_FORMAT_LIST_RESPONSE, CB_FLAG.CB_RESPONSE_OK, None)
    144  - await self.channel_data_out_q.put(msg)
     109 + await self.fragment_and_send(msg)
    145 110   
    146 111   if CLIPBRD_FORMAT.CF_UNICODETEXT in self.current_server_formats.keys():
    147 112   # pyperclip is in use and server just notified us about a new text copied, so we request the text
    skipped 2 lines
    150 115   dreq = CLIPRDR_FORMAT_DATA_REQUEST()
    151 116   dreq.requestedFormatId = CLIPBRD_FORMAT.CF_UNICODETEXT
    152 117   msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_FORMAT_DATA_REQUEST, 0, dreq)
    153  - await self.channel_data_out_q.put(msg)
     118 + await self.fragment_and_send(msg)
    154 119   
    155 120   # notifying client that new data is available
    156 121   msg = RDP_CLIPBOARD_NEW_DATA_AVAILABLE()
    157  - await self.connection.ext_out_queue.put(msg)
     122 + await self.send_user_data(msg)
    158 123   
    159 124  
    160 125   elif hdr.msgType == CB_TYPE.CB_FORMAT_DATA_RESPONSE:
    skipped 10 lines
    171 136   msg = RDP_CLIPBOARD_DATA_TXT()
    172 137   msg.data = fmtdata.dataobj
    173 138   msg.datatype = self.__requested_format
    174  - await self.connection.ext_out_queue.put(msg)
     139 + await self.send_user_data(msg)
    175 140  
    176 141   except Exception as e:
    177 142   raise e
    skipped 9 lines
    187 152   resp = resp.to_bytes(self.__current_clipboard_data.datatype)
    188 153   
    189 154   msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_FORMAT_DATA_RESPONSE, CB_FLAG.CB_RESPONSE_OK, resp)
    190  - await self.channel_data_out_q.put(msg)
     155 + await self.fragment_and_send(msg)
    191 156  
    192 157   else:
    193 158   logger.info('Server requested a formatid which we dont have. %s' % fmtr.requestedFormatId)
    skipped 23 lines
    217 182   # also we have to notify the client that they can use the keyboard now
    218 183   self.status = CLIPBRDSTATUS.RUNNING
    219 184   msg = RDP_CLIPBOARD_READY()
    220  - await self.connection.ext_out_queue.put(msg)
     185 + await self.send_user_data(msg)
    221 186   
    222 187   elif CB_FLAG.CB_RESPONSE_FAIL in hdr.msgFlags:
    223 188   raise Exception('Server refused clipboard initialization!')
    skipped 7 lines
    231 196   except Exception as e:
    232 197   return None, e
    233 198   
    234  - async def monitor_in(self):
     199 + async def process_channel_data(self, data):
     200 + channeldata = CHANNEL_PDU_HEADER.from_bytes(data)
     201 + #print('channeldata %s' % channeldata)
     202 + self.__buffer += channeldata.data
     203 + if CHANNEL_FLAG.CHANNEL_FLAG_LAST in channeldata.flags:
     204 + _, err = await self.__process_in()
     205 + if err is not None:
     206 + raise err
     207 + 
     208 + async def fragment_and_send(self, data):
    235 209   try:
    236  - while True:
    237  - data, err = await self.raw_in_queue.get()
    238  - if err is not None:
    239  - await self.out_queue.put((data, err))
    240  - raise err
    241  - #print('Channel data in! "%s(%s)" <- %s' % (self.name, self.channel_id, data))
     210 + if len(data) < 16000:
     211 + if self.compression_needed is False:
     212 + flags = CHANNEL_FLAG.CHANNEL_FLAG_FIRST|CHANNEL_FLAG.CHANNEL_FLAG_LAST|CHANNEL_FLAG.CHANNEL_FLAG_SHOW_PROTOCOL
     213 + packet = CHANNEL_PDU_HEADER.serialize_packet(flags, data)
     214 + else:
     215 + raise NotImplementedError('Compression not implemented!')
     216 + else:
     217 + raise NotImplementedError('Chunked send not implemented!')
     218 +
     219 + sec_hdr = None
     220 + if self.connection.cryptolayer is not None:
     221 + sec_hdr = TS_SECURITY_HEADER()
     222 + sec_hdr.flags = SEC_HDR_FLAG.ENCRYPT
     223 + sec_hdr.flagsHi = 0
    242 224   
    243  - channeldata = CHANNEL_PDU_HEADER.from_bytes(data)
    244  - #print('channeldata %s' % channeldata)
    245  - self.__buffer += channeldata.data
    246  - if CHANNEL_FLAG.CHANNEL_FLAG_LAST in channeldata.flags:
    247  - _, err = await self.__process_in()
    248  - if err is not None:
    249  - raise err
     225 + await self.send_channel_data(packet, sec_hdr, None, None, self.channel_id, False)
    250 226   
    251  - except asyncio.CancelledError:
    252  - return None, None
     227 + return True, False
    253 228   except Exception as e:
    254 229   traceback.print_exc()
    255  - return None, e
     230 + return None,e
    256 231  
    257  - async def monitor_out(self):
    258  - try:
    259  - while True:
    260  - data = await self.in_queue.get()
    261  - #print('monitor out! %s' % data)
    262  - if data.type == RDPDATATYPE.CLIPBOARD_DATA_TXT:
    263  - # data in, informing the server that our clipboard has changed
    264  - if data == self.__current_clipboard_data:
    265  - #print('Data already in cache!')
    266  - continue
    267 232   
    268  - fmtl = CLIPRDR_FORMAT_LIST()
    269  - for fmtid in [CLIPBRD_FORMAT.CF_UNICODETEXT]: #CLIPBRD_FORMAT.CF_TEXT, CLIPBRD_FORMAT.CF_OEMTEXT
    270  - if CB_GENERAL_FALGS.USE_LONG_FORMAT_NAMES not in self.server_general_caps.generalFlags : #self.client_general_caps_flags:
    271  - name = CLIPRDR_LONG_FORMAT_NAME()
    272  - name.formatId = data.datatype
    273  - else:
    274  - name = CLIPRDR_SHORT_FORMAT_NAME()
    275  - name.formatId = data.datatype
    276  - fmtl.templist.append(name)
    277  - msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_FORMAT_LIST, 0, fmtl)
    278  - await self.channel_data_out_q.put(msg)
     233 + async def process_user_data(self, data):
     234 + data = await self.in_queue.get()
     235 + #print('monitor out! %s' % data)
     236 + if data.type == RDPDATATYPE.CLIPBOARD_DATA_TXT:
     237 + # data in, informing the server that our clipboard has changed
     238 + if data == self.__current_clipboard_data:
     239 + return
    279 240  
    280  - self.__current_clipboard_data = data
    281  -
     241 + fmtl = CLIPRDR_FORMAT_LIST()
     242 + for fmtid in [CLIPBRD_FORMAT.CF_UNICODETEXT]: #CLIPBRD_FORMAT.CF_TEXT, CLIPBRD_FORMAT.CF_OEMTEXT
     243 + if CB_GENERAL_FALGS.USE_LONG_FORMAT_NAMES not in self.server_general_caps.generalFlags:
     244 + name = CLIPRDR_LONG_FORMAT_NAME()
     245 + name.formatId = data.datatype
    282 246   else:
    283  - logger.debug('Unhandled data type in! %s' % data.type)
    284  - continue
    285  - 
     247 + name = CLIPRDR_SHORT_FORMAT_NAME()
     248 + name.formatId = data.datatype
     249 + fmtl.templist.append(name)
     250 + msg = CLIPRDR_HEADER.serialize_packet(CB_TYPE.CB_FORMAT_LIST, 0, fmtl)
     251 + await self.fragment_and_send(msg)
    286 252   
    287  - except asyncio.CancelledError:
    288  - return None, None
     253 + self.__current_clipboard_data = data
    289 254  
    290  - except Exception as e:
    291  - traceback.print_exc()
    292  - return None, e
     255 + else:
     256 + logger.debug('Unhandled data type in! %s' % data.type)
  • ■ ■ ■ ■ ■ ■
    aardwolf/network/selector.py
    1  -from aardwolf.commons.proxy import RDPProxyType
    2  -from aardwolf.commons.target import RDPConnectionProtocol, RDPTarget
    3  -from aardwolf.transport.tcp import TCPSocket
    4  -from aardwolf.network.socks import SocksProxyConnection
    5  - 
    6  - 
    7  -class NetworkSelector:
    8  - def __init__(self):
    9  - pass
    10  - 
    11  - @staticmethod
    12  - async def select(target: RDPTarget):
    13  - """Selects the appropriate network connection library based on the target"""
    14  - try:
    15  - if target.proxy is None:
    16  - if target.protocol == RDPConnectionProtocol.TCP:
    17  - return TCPSocket(target = target), None
    18  - else:
    19  - raise NotImplementedError()
    20  - elif target.proxy.type == RDPProxyType.ASYSOCKS:
    21  - return SocksProxyConnection(target = target), None
    22  - 
    23  - else:
    24  - raise Exception('Cant select correct connection type!')
    25  - except Exception as e:
    26  - return None, e
  • ■ ■ ■ ■ ■ ■
    aardwolf/network/socks.py
    1  -import enum
    2  -import asyncio
    3  -import ipaddress
    4  - 
    5  -from aardwolf import logger
    6  - 
    7  -from asysocks.client import SOCKSClient
    8  -from asysocks.common.comms import SocksQueueComms
    9  - 
    10  - 
    11  -class SocksProxyConnection:
    12  - """
    13  - Generic asynchronous TCP socket class, nothing SMB related.
    14  - Creates the connection and channels incoming/outgoing bytes via asynchonous queues.
    15  - """
    16  - def __init__(self, target):
    17  - self.target = target
    18  -
    19  - self.client = None
    20  - self.proxy_task = None
    21  - self.handle_in_task = None
    22  - 
    23  - self.out_queue = None#asyncio.Queue()
    24  - self.in_queue = None#asyncio.Queue()
    25  - self.disconnected = None #asyncio.Event()
    26  - 
    27  - self.proxy_in_queue = None#asyncio.Queue()
    28  - self.is_plain_msg = True
    29  -
    30  - async def disconnect(self):
    31  - """
    32  - Disconnects from the socket.
    33  - Stops the reader and writer streams.
    34  - """
    35  - if self.client is not None:
    36  - await self.client.terminate()
    37  - if self.proxy_task is not None:
    38  - self.proxy_task.cancel()
    39  - if self.handle_in_q is not None:
    40  - self.handle_in_task.cancel()
    41  - 
    42  - async def terminate(self):
    43  - await self.disconnect()
    44  -
    45  - async def handle_in_q(self):
    46  - try:
    47  - while True:
    48  - temp, err = await self.proxy_in_queue.get()
    49  - #print(temp)
    50  - if err is not None:
    51  - raise err
    52  - 
    53  - if temp == b'' or temp is None:
    54  - logger.debug('Server finished!')
    55  - return
    56  - 
    57  - await self.in_queue.put((temp, None))
    58  - continue
    59  -
    60  - except asyncio.CancelledError:
    61  - return
    62  - except Exception as e:
    63  - logger.exception('handle_in_q')
    64  - await self.in_queue.put((None, e))
    65  - 
    66  - finally:
    67  - self.proxy_task.cancel()
    68  - 
    69  - 
    70  -
    71  - async def connect(self):
    72  - """
    73  -
    74  - """
    75  - try:
    76  - self.out_queue = asyncio.Queue()
    77  - self.in_queue = asyncio.Queue()
    78  - self.disconnected = asyncio.Event()
    79  - self.proxy_in_queue = asyncio.Queue()
    80  - comms = SocksQueueComms(self.out_queue, self.proxy_in_queue)
    81  - 
    82  - self.target.proxy.target[-1].endpoint_ip = self.target.get_hostname_or_ip() if self.target.serverip is None else self.target.serverip
    83  - self.target.proxy.target[-1].endpoint_port = int(self.target.port)
    84  - self.target.proxy.target[-1].endpoint_timeout = None #TODO: maybe implement endpoint timeout?
    85  - self.target.proxy.target[-1].timeout = self.target.timeout
    86  - self.client = SOCKSClient(comms, self.target.proxy.target)
    87  - self.proxy_task = asyncio.create_task(self.client.run())
    88  - self.handle_in_task = asyncio.create_task(self.handle_in_q())
    89  - return True, None
    90  - except Exception as e:
    91  - return False, e
    92  - 
    93  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/network/tpkt.py
    skipped 2 lines
    3 3  import logging
    4 4  import traceback
    5 5  from aardwolf.protocol.tpkt import TPKT
     6 +from aardwolf.protocol.x224 import X224Packet
     7 +from aardwolf.protocol.x224.data import Data
     8 +from asysocks.unicomm.common.packetizers import Packetizer
    6 9   
    7  -class TPKTNetwork:
    8  - def __init__(self, transport):
    9  - self.transport = transport
     10 +class CredSSPPacketizer(Packetizer):
     11 + def __init__(self):
     12 + Packetizer.__init__(self, 65535)
     13 + self.in_buffer = b''
    10 14  
    11  - self.out_queue = asyncio.Queue()
    12  - self.in_queue = asyncio.Queue()
     15 + def flush_buffer(self):
     16 + data = self.in_buffer
     17 + self.in_buffer = b''
     18 + return data
    13 19   
    14  - self.disconnected = asyncio.Event()
     20 + @staticmethod
     21 + def calcualte_length(data):
     22 + if data[1] <= 127:
     23 + return data[1] + 2
     24 + else:
     25 + bcount = data[1] - 128
     26 + return int.from_bytes(data[2:2+bcount], byteorder = 'big', signed = False) + bcount + 2
    15 27  
    16  - self.incoming_task = None
    17  - self.outgoing_task = None
     28 + def process_buffer(self):
     29 + preread = 6
     30 + remaining_length = -1
     31 + while True:
     32 + if len(self.in_buffer) < preread:
     33 + break
     34 + lb = self.in_buffer[:preread]
     35 + remaining_length = CredSSPPacketizer.calcualte_length(lb) - preread
     36 + if len(self.in_buffer) >= remaining_length+preread:
     37 + data = self.in_buffer[:remaining_length+preread]
     38 + self.in_buffer = self.in_buffer[remaining_length+preread:]
     39 + yield data
     40 + continue
     41 + break
    18 42  
    19  - self.__buffer = b''
    20  - self.__soft_switch = False
    21 43   
    22  - async def disconnect(self):
    23  - if self.outgoing_task is not None:
    24  - self.outgoing_task.cancel()
    25  - if self.incoming_task is not None:
    26  - self.incoming_task.cancel()
    27  - if self.transport is not None:
    28  - self.transport.out_queue.put_nowait(b'')
    29  - await self.transport.disconnect()
    30  - self.disconnected.set()
     44 + async def data_out(self, data):
     45 + yield data
    31 46   
    32  - async def handle_incoming(self):
    33  - """
    34  - Reads data bytes from the socket and dispatches it to the incoming queue
    35  - """
    36  - try:
    37  - lasterror = None
    38  - msgsize = None
    39  - is_fastpath = False
    40  - while not self.disconnected.is_set():
    41  - try:
    42  - if msgsize is None:
    43  - if len(self.__buffer) >= 4:
    44  - if self.__buffer[0] == 3:
    45  - msgsize = int.from_bytes(self.__buffer[2:4], byteorder='big', signed=False)
    46  - else:
    47  - is_fastpath = True
    48  - msgsize = self.__buffer[1]
    49  - if msgsize >> 7 == 1:
    50  - msgsize = int.from_bytes(self.__buffer[1:3], byteorder='big', signed=False)
    51  - msgsize = msgsize & 0x7fff
    52  - 
    53  - 
    54  - if msgsize is not None:
    55  - if len(self.__buffer)>=msgsize:
    56  - if is_fastpath is False:
    57  - msg = TPKT.from_bytes(self.__buffer[:msgsize])
    58  - await self.in_queue.put( (is_fastpath, msg.tpdu, None) )
    59  - self.__buffer = self.__buffer[msgsize:]
    60  - msgsize = None
    61  - else:
    62  - await self.in_queue.put( (is_fastpath, self.__buffer[:msgsize], None) )
    63  - self.__buffer = self.__buffer[msgsize:]
    64  - msgsize = None
    65  - is_fastpath = False
    66  -
    67  - if len(self.__buffer) > 0:
    68  - continue
    69  - 
    70  - data, err = await self.transport.in_queue.get()
    71  - if err is not None:
    72  - raise err
    73  - if data is None:
    74  - return
    75  - 
    76  - self.__buffer += data
    77  - except asyncio.CancelledError as e:
    78  - lasterror = e
    79  - break
    80  - except Exception as e:
    81  - logging.exception('[TPKTNetwork] handle_incoming %s' % str(e))
    82  - lasterror = e
    83  - break
    84  -
    85  -
    86  - except asyncio.CancelledError:
    87  - return
    88  -
    89  - except Exception as e:
    90  - lasterror = e
    91  - 
    92  - finally:
    93  - if self.__soft_switch is False:
    94  - if self.in_queue is not None:
    95  - await self.in_queue.put( (None, None, lasterror) )
    96  - await self.disconnect()
     47 + async def data_in(self, data):
     48 + if data is None:
     49 + yield data
     50 + self.in_buffer += data
     51 + for packet in self.process_buffer():
     52 + yield packet
    97 53   
    98  - async def handle_outgoing(self):
    99  - """
    100  - Reads data bytes from the outgoing queue and dispatches it to the socket
    101  - """
    102  - try:
    103  - while not self.disconnected.is_set():
    104  - data = await self.out_queue.get()
    105  - if data is None:
    106  - return
    107  - msg = TPKT()
    108  - msg.tpdu = data
    109  - #print('TPKT -> %s' % msg.to_bytes())
    110  - await self.transport.out_queue.put(msg.to_bytes())
    111  - except asyncio.CancelledError:
    112  - return
    113  - except Exception as e:
    114  - logging.exception('[TPKTNetwork] handle_outgoing %s' % str(e))
    115  - finally:
    116  - if self.__soft_switch is False:
    117  - await self.disconnect()
     54 +class TPKTPacketizer(Packetizer):
     55 + def __init__(self):
     56 + Packetizer.__init__(self)
     57 + self.wrap_x224 = False
     58 + self.in_buffer = b''
    118 59  
    119  - async def suspend_read(self):
    120  - try:
    121  - self.__soft_switch = True
    122  - self.incoming_task.cancel()
    123  - self.outgoing_task.cancel()
    124  - return True, None
    125  - except Exception as e:
    126  - return None, e
     60 + def flush_buffer(self):
     61 + data = self.in_buffer
     62 + self.in_buffer = b''
     63 + return data
    127 64   
    128  - async def conitnue_read(self):
    129  - try:
    130  - self.__soft_switch = False
    131  - self.incoming_task = asyncio.create_task(self.handle_incoming())
    132  - self.outgoing_task = asyncio.create_task(self.handle_outgoing())
    133  - return True, None
    134  - except Exception as e:
    135  - return None, e
     65 + def packetizer_control(self, cmd):
     66 + if cmd == 'X224':
     67 + self.wrap_x224 = True
    136 68  
     69 + def process_buffer(self):
     70 + msgsize = None
     71 + is_fastpath = False
     72 + while True:
     73 + if len(self.in_buffer) < 4:
     74 + break
     75 + if self.in_buffer[0] == 3:
     76 + msgsize = int.from_bytes(self.in_buffer[2:4], byteorder='big', signed=False)
     77 + else:
     78 + is_fastpath = True
     79 + msgsize = self.in_buffer[1]
     80 + if msgsize >> 7 == 1:
     81 + msgsize = int.from_bytes(self.in_buffer[1:3], byteorder='big', signed=False)
     82 + msgsize = msgsize & 0x7fff
    137 83  
    138  - async def switch_transport(self, newtransportobj):
    139  - #most cases this means that we are switching to SSL from TCP
    140  - try:
    141  - self.__soft_switch = True #indicating that no errors will be sent to lower/upper layers
    142  - self.incoming_task.cancel()
    143  - self.outgoing_task.cancel()
    144  - self.transport, err = await newtransportobj.from_transport(self.transport)
    145  - if err is not None:
    146  - raise err
    147  - self.__soft_switch = False
    148  - self.incoming_task = asyncio.create_task(self.handle_incoming())
    149  - self.outgoing_task = asyncio.create_task(self.handle_outgoing())
     84 + if len(self.in_buffer)>=msgsize:
     85 + if is_fastpath is False:
     86 + msg = TPKT.from_bytes(self.in_buffer[:msgsize])
     87 + self.in_buffer = self.in_buffer[msgsize:]
     88 + msgsize = None
     89 + if self.wrap_x224 is True:
     90 + yield (False, X224Packet.from_bytes(msg.tpdu))
     91 + else:
     92 + yield (False, msg.tpdu)
    150 93  
    151  - return True, None
    152  - except Exception as e:
    153  - #traceback.print_exc()
    154  - return None, e
     94 + else:
     95 + res = self.in_buffer[:msgsize]
     96 + self.in_buffer = self.in_buffer[msgsize:]
     97 + msgsize = None
     98 + is_fastpath = False
     99 + yield (True, res)
    155 100   
    156  - async def run(self):
    157  - try:
    158  - self.incoming_task = asyncio.create_task(self.handle_incoming())
    159  - self.outgoing_task = asyncio.create_task(self.handle_outgoing())
    160  - return True, None
    161  - except Exception as e:
    162  - return False, e
     101 + if len(self.in_buffer) > 0:
     102 + continue
     103 + break
    163 104   
     105 + async def data_out(self, data):
     106 + tpkt = TPKT()
     107 + tpkt.tpdu = data
     108 + #print('TPKT -> %s' % tpkt.to_bytes())
     109 + yield tpkt.to_bytes()
    164 110   
     111 + async def data_in(self, data):
     112 + if data is None:
     113 + yield data
     114 + self.in_buffer += data
     115 + for packet in self.process_buffer():
     116 + yield packet
    165 117   
  • ■ ■ ■ ■ ■
    aardwolf/network/x224.py
    1  -import asyncio
    2  -import os
    3  - 
    4  -import logging
    5 1  import typing
    6  -from aardwolf.network.tpkt import TPKTNetwork
     2 + 
     3 + 
     4 +from asysocks.unicomm.common.connection import UniConnection
     5 + 
    7 6  from aardwolf.protocol.x224.constants import TPDU_TYPE, NEG_FLAGS, SUPP_PROTOCOLS
    8 7  from aardwolf.protocol.x224.client.connectionrequest import ConnectionRequest
    9 8  from aardwolf.protocol.x224.server.connectionconfirm import ConnectionConfirm
    skipped 5 lines
    15 14   
    16 15   
    17 16  class X224Network:
    18  - """
    19  - IMPORTANT! The transpo
    20  - """
    21  - def __init__(self, transport:TPKTNetwork):
    22  - self.transport:TPKTNetwork = transport
    23  - 
    24  - self.out_queue = asyncio.Queue()
    25  - self.in_queue = asyncio.Queue()
    26  -
    27  - self.disconnected = asyncio.Event()
    28  - 
    29  - self.incoming_task = None
    30  - self.outgoing_task = None
    31  -
    32  - async def disconnect(self):
    33  - if self.outgoing_task is not None:
    34  - self.outgoing_task.cancel()
    35  - if self.incoming_task is not None:
    36  - self.incoming_task.cancel()
    37  - self.disconnected.set()
     17 + def __init__(self, connection:UniConnection):
     18 + self.connection = connection
    38 19  
    39 20   async def client_negotiate(self, flags:NEG_FLAGS = 0, supported_protocols:SUPP_PROTOCOLS = SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.HYBRID_EX, to_raise = True):
    40 21   reply = None
    skipped 7 lines
    48 29   conreq.cookie = b'Cookie: mstshash=devel\r\n'
    49 30   conreq.rdpNegReq = negreq
    50 31   
    51  - #print(X224Packet.from_bytes(conreq.to_bytes()))
    52  - 
    53  - await self.transport.out_queue.put(conreq.to_bytes())
    54  - is_fastpath, reply, err = await self.in_queue.get()
    55  - if err is not None:
    56  - raise err
     32 + await self.connection.write(conreq.to_bytes())
     33 + reply = await self.read()
    57 34   if reply.CR != TPDU_TYPE.CONNECTION_CONFIRM:
    58 35   raise Exception('Server sent back unknown TPDU type! %s' % reply.CR)
    59 36   reply = typing.cast(ConnectionConfirm, reply)
    skipped 8 lines
    68 45   except Exception as e:
    69 46   return reply, e
    70 47   
    71  - async def handle_incoming(self):
    72  - """
    73  - Reads data bytes from the socket and dispatches it to the incoming queue
    74  - """
    75  - try:
    76  - lasterror = None
    77  - msgsize = None
    78  - buffer = b''
    79  - while not self.disconnected.is_set():
    80  - try:
    81  - is_fastpath, data, err = await self.transport.in_queue.get()
    82  - if err is not None:
    83  - raise err
    84  - if data is None:
    85  - return
    86  - 
    87  - packet = data
    88  - if is_fastpath is False:
    89  - #print('X224Net <- %s' % data.hex())
    90  - packet = X224Packet.from_bytes(data)
    91  - #print('X224Net (packet) <- %s' % packet)
    92  - await self.in_queue.put((is_fastpath, packet, None))
    93  - except asyncio.CancelledError as e:
    94  - lasterror = e
    95  - break
    96  - except Exception as e:
    97  - logging.exception('[X224Net] handle_incoming %s' % str(e))
    98  - lasterror = e
    99  - break
    100  -
    101  -
    102  - except asyncio.CancelledError:
    103  - return
    104  -
    105  - except Exception as e:
    106  - lasterror = e
    107  - 
    108  - finally:
    109  - if self.in_queue is not None:
    110  - await self.in_queue.put( (None, None, lasterror) )
    111  - await self.disconnect()
    112  -
    113  - async def handle_outgoing(self):
    114  - """
    115  - Reads data bytes from the outgoing queue and dispatches it to the socket
    116  - """
    117  - try:
    118  - while not self.disconnected.is_set():
    119  - data = await self.out_queue.get()
    120  - if data is None:
    121  - return
    122  - msg = Data()
    123  - msg.data = data
    124  - #print('X224Net -> %s' % msg.to_bytes().hex())
    125  - await self.transport.out_queue.put(msg.to_bytes())
    126  - except asyncio.CancelledError:
    127  - return
    128  - except Exception as e:
    129  - logging.debug('[X224Net] handle_outgoing %s' % str(e))
    130  - finally:
    131  - await self.disconnect()
     48 + async def read(self):
     49 + is_fastpath, tpktdata = await self.connection.read_one()
     50 + if is_fastpath is True:
     51 + raise Exception('Fastpath packet should never be here!')
     52 + return X224Packet.from_bytes(tpktdata)
    132 53   
    133  - async def run(self):
    134  - try:
    135  - self.incoming_task = asyncio.create_task(self.handle_incoming())
    136  - self.outgoing_task = asyncio.create_task(self.handle_outgoing())
    137  - return True, None
    138  - except Exception as e:
    139  - return False, e
     54 + async def write(self, data):
     55 + msg = Data()
     56 + msg.data = data
     57 + await self.connection.write(msg.to_bytes())
    140 58   
    141 59  
    142 60  
  • ■ ■ ■ ■ ■ ■
    aardwolf/transport/mbedtlsssl.py
    1  -import asyncio
    2  - 
    3  -import platform
    4  -import traceback
    5  - 
    6  - 
    7  -import logging
    8  -from asn1crypto.x509 import Certificate, PublicKeyInfo
    9  -from mbedtls import tls
    10  - 
    11  -# Experimental as heck! Errors don't propagate, miracle it works
    12  - 
    13  -class MBEDTLSClientTunnel:
    14  - def __init__(self):
    15  - self.in_queue = None
    16  - self.out_queue = None
    17  - self.ssl_ctx = None
    18  - self.transport = None
    19  - self.ssl_obj = None
    20  - self.__ssl_in_q = None
    21  - self.stop_evt = None
    22  - self.server_certificate = None
    23  - 
    24  - self.__enc_task = None
    25  - self.__dec_task = None
    26  - self.__process_task = None
    27  -
    28  - async def disconnect(self):
    29  - try:
    30  - if self.__enc_task is not None:
    31  - self.__enc_task.cancel()
    32  - if self.__dec_task is not None:
    33  - self.__dec_task.cancel()
    34  - if self.__process_task is not None:
    35  - self.__process_task.cancel()
    36  - 
    37  - if self.transport is not None:
    38  - self.transport.out_queue.put_nowait(b'')
    39  - await self.transport.disconnect()
    40  - except Exception as e:
    41  - pass
    42  -
    43  - async def get_server_pubkey(self):
    44  - try:
    45  - if self.server_certificate is None:
    46  - return None
    47  - cert = Certificate.load(self.server_certificate)
    48  - return cert['tbs_certificate']['subject_public_key_info']['public_key'].dump()[5:] #why?
    49  - except Exception as e:
    50  - traceback.print_exc()
    51  - return e
    52  -
    53  - async def setup(self):
    54  - try:
    55  - self.in_queue = asyncio.Queue()
    56  - self.out_queue = asyncio.Queue()
    57  - self.stop_evt = asyncio.Event()
    58  - self.__ssl_in_q = asyncio.Queue()
    59  - self.ssl_obj = self.ssl_ctx.wrap_buffers(None)
    60  - _, err = await self.__do_handshake()
    61  - if err is not None:
    62  - raise err
    63  - #print('Handshake ok!')
    64  - self.server_certificate = self.ssl_ctx.getpeercert(binary_form=True)
    65  - self.__enc_task = asyncio.create_task(self.__encrypt_ssl_rec())
    66  - self.__dec_task = asyncio.create_task(self.__decrypt_ssl_rec())
    67  - self.__process_task = asyncio.create_task(self.__read_ssl_record())
    68  - return True, None
    69  - 
    70  - except Exception as e:
    71  - return None, e
    72  -
    73  - async def __encrypt_ssl_rec(self):
    74  - try:
    75  - while True:
    76  - try:
    77  - raw_data = await self.out_queue.get()
    78  - if raw_data == b'':
    79  - await self.in_queue.put(None)
    80  - #print('Connection terminated')
    81  - return
    82  -
    83  - self.ssl_obj.write(raw_data)
    84  - while True:
    85  - try:
    86  - enc_data = self.ssl_obj.peek_outgoing(16384)
    87  - if enc_data == b'':
    88  - break
    89  - await self.transport.out_queue.put(enc_data)
    90  - self.ssl_obj.consume_outgoing(len(enc_data))
    91  - except Exception as e:
    92  - raise e
    93  - except Exception as e:
    94  - raise e
    95  - 
    96  - except asyncio.CancelledError:
    97  - return
    98  - except Exception as e:
    99  - #print('__encrypt_ssl_rec %s' % e)
    100  - logging.debug('__encrypt_ssl_rec %s' % e)
    101  -
    102  - async def __decrypt_ssl_rec(self):
    103  - """
    104  - Decrypting the encrypted SSL records from the internal queue and putting them in the queue for the uppr layers to use
    105  - """
    106  - try:
    107  - while not self.stop_evt.is_set():
    108  - ssl_data = await self.__ssl_in_q.get()
    109  - if ssl_data == b'':
    110  - await self.in_queue.put((ssl_data, None))
    111  - #print('Connection terminated')
    112  - return
    113  - self.ssl_obj.receive_from_network(ssl_data) # write encrypted data to ssl_obj
    114  - 
    115  - data_buff = b''
    116  - while not self.stop_evt.is_set():
    117  - try:
    118  - data_read = self.ssl_obj.read(16384) # read decrypted data
    119  - if data_read == b'':
    120  - break
    121  - data_buff += data_read
    122  - #except tls.WantReadError:
    123  - # break
    124  - except Exception as e:
    125  - raise e
    126  - #print('data_buff %s' % data_buff)
    127  - if data_buff != b'':
    128  - await self.in_queue.put((data_buff, None))
    129  - 
    130  - except asyncio.CancelledError:
    131  - return
    132  - except Exception as e:
    133  - #print('__decrypt_ssl_rec %s' % e)
    134  - logging.debug('__decrypt_ssl_rec %s' % e)
    135  - 
    136  - async def __read_ssl_record(self):
    137  - try:
    138  - """
    139  - buffering the incoming SSL (encrypted) recodrs and putting them in an internal queue one record at a time
    140  - """
    141  - buffer = b''
    142  - length = None
    143  - while not self.stop_evt.is_set():
    144  - if length is None and len(buffer) >= 6:
    145  - length = int.from_bytes(buffer[3:5], byteorder = 'big', signed = False)
    146  -
    147  - if length is not None and len(buffer) >= length + 5:
    148  - #print('LB raw %s' % len(buffer[:length+5]))
    149  - await self.__ssl_in_q.put(buffer[:length+5])
    150  - buffer = buffer[length+5:]
    151  - length = None
    152  - continue
    153  -
    154  - data, err = await self.transport.in_queue.get()
    155  - if err is not None:
    156  - raise err
    157  - if data == b'':
    158  - await self.__ssl_in_q.put(b'')
    159  - return
    160  - buffer+= data
    161  -
    162  - 
    163  - except asyncio.CancelledError:
    164  - return
    165  - 
    166  - except Exception as e:
    167  - logging.debug('__read_ssl_record %s' % e)
    168  - await self.__ssl_in_q.put(b'')
    169  - 
    170  - #finally:
    171  - # self.stop_evt.set()
    172  -
    173  - async def __do_handshake(self):
    174  - try:
    175  - ctr = 0
    176  - while not self.stop_evt.is_set() and self.ssl_obj.context._state is not tls.HandshakeStep.HANDSHAKE_OVER:
    177  - ctr += 1
    178  - if ctr == 1000:
    179  - raise Exception('Handshake broke :(')
    180  - #print('DST Performing handshake!')
    181  - try:
    182  - self.ssl_obj.do_handshake()
    183  - data = self.ssl_obj.peek_outgoing(1024)
    184  - if len(data) == 0:
    185  - continue
    186  - await self.transport.out_queue.put(data)
    187  - self.ssl_obj.consume_outgoing(len(data))
    188  - 
    189  - except tls.WantReadError:
    190  - data = self.ssl_obj.peek_outgoing(1024)
    191  - if len(data) == 0:
    192  - continue
    193  - await self.transport.out_queue.put(data)
    194  - self.ssl_obj.consume_outgoing(len(data))
    195  - continue
    196  - 
    197  - except tls.WantWriteError:
    198  - server_hello, err = await self.transport.in_queue.get()
    199  - if err is not None:
    200  - raise err
    201  - if server_hello == b'':
    202  - raise Exception('Server closed the connection!')
    203  - self.ssl_obj.receive_from_network(server_hello)
    204  - 
    205  - continue
    206  - except:
    207  - raise
    208  - 
    209  - return True, None
    210  - 
    211  - except Exception as e:
    212  - logging.debug('__do_handshake %s' % e)
    213  - return None, e
    214  - 
    215  - @staticmethod
    216  - async def from_transport(transport, ssl_ctx = None, ssl_cert = None, ssl_key = None):
    217  - try:
    218  - tunnel = MBEDTLSClientTunnel()
    219  - tunnel.transport = transport
    220  - tunnel.ssl_ctx = tls.ClientContext(tls.TLSConfiguration(
    221  - #trust_store=tls.TrustStore(),
    222  - validate_certificates=False,
    223  - ))
    224  - _, err = await tunnel.setup()
    225  - if err is not None:
    226  - raise err
    227  - 
    228  - return tunnel, None
    229  - except Exception as e:
    230  - return None, e
  • ■ ■ ■ ■ ■ ■
    aardwolf/transport/ssl.py
    1  -import asyncio
    2  - 
    3  -import platform
    4  -import traceback
    5  -try:
    6  - import ssl
    7  -except:
    8  - if platform.system() == 'Emscripten':
    9  - #pyodide doesnt support SSL for now.
    10  - pass
    11  - 
    12  - 
    13  -import logging
    14  -from asn1crypto.x509 import Certificate, PublicKeyInfo
    15  - 
    16  -class SSLClientTunnel:
    17  - def __init__(self):
    18  - self.in_queue = None
    19  - self.out_queue = None
    20  - self.ssl_ctx = None
    21  - self.transport = None
    22  - self.ssl_in_buff = None
    23  - self.ssl_out_buff = None
    24  - self.ssl_obj = None
    25  - self.__ssl_in_q = None
    26  - self.stop_evt = None
    27  - self.server_certificate = None
    28  - 
    29  - self.__enc_task = None
    30  - self.__dec_task = None
    31  - self.__process_task = None
    32  -
    33  - async def disconnect(self):
    34  - try:
    35  - if self.__enc_task is not None:
    36  - self.__enc_task.cancel()
    37  - if self.__dec_task is not None:
    38  - self.__dec_task.cancel()
    39  - if self.__process_task is not None:
    40  - self.__process_task.cancel()
    41  - 
    42  - if self.transport is not None:
    43  - self.transport.out_queue.put_nowait(b'')
    44  - await self.transport.disconnect()
    45  - except Exception as e:
    46  - pass
    47  -
    48  - async def get_server_pubkey(self):
    49  - try:
    50  - if self.server_certificate is None:
    51  - return None
    52  - cert = Certificate.load(self.server_certificate)
    53  - return cert['tbs_certificate']['subject_public_key_info']['public_key'].dump()[5:] #why?
    54  - except Exception as e:
    55  - return e
    56  -
    57  - async def setup(self):
    58  - try:
    59  - self.in_queue = asyncio.Queue()
    60  - self.out_queue = asyncio.Queue()
    61  - self.stop_evt = asyncio.Event()
    62  - self.__ssl_in_q = asyncio.Queue()
    63  - self.ssl_in_buff = ssl.MemoryBIO()
    64  - self.ssl_out_buff = ssl.MemoryBIO()
    65  - self.ssl_obj = self.ssl_ctx.wrap_bio(self.ssl_in_buff, self.ssl_out_buff, server_side=False, server_hostname = None)
    66  - _, err = await self.__do_handshake()
    67  - if err is not None:
    68  - raise err
    69  - #print('Handshake ok!')
    70  - self.server_certificate = self.ssl_obj.getpeercert(binary_form=True)
    71  - self.__enc_task = asyncio.create_task(self.__encrypt_ssl_rec())
    72  - self.__dec_task = asyncio.create_task(self.__decrypt_ssl_rec())
    73  - self.__process_task = asyncio.create_task(self.__read_ssl_record())
    74  - return True, None
    75  - 
    76  - except Exception as e:
    77  - return None, e
    78  -
    79  - async def __encrypt_ssl_rec(self):
    80  - try:
    81  - while True:
    82  - try:
    83  - raw_data = await self.out_queue.get()
    84  - if raw_data == b'':
    85  - await self.in_queue.put(None)
    86  - #print('Connection terminated')
    87  - return
    88  -
    89  - self.ssl_obj.write(raw_data)
    90  - while True:
    91  - try:
    92  - enc_data = self.ssl_out_buff.read()
    93  - if enc_data == b'':
    94  - break
    95  - await self.transport.out_queue.put(enc_data)
    96  - except Exception as e:
    97  - raise e
    98  - except Exception as e:
    99  - raise e
    100  - 
    101  - except asyncio.CancelledError:
    102  - return
    103  - except Exception as e:
    104  - logging.debug('__encrypt_ssl_rec %s' % e)
    105  -
    106  - async def __decrypt_ssl_rec(self):
    107  - """
    108  - Decrypting the encrypted SSL records from the internal queue and putting them in the queue for the uppr layers to use
    109  - """
    110  - try:
    111  - while not self.stop_evt.is_set():
    112  - ssl_data = await self.__ssl_in_q.get()
    113  - if ssl_data == b'':
    114  - await self.in_queue.put((ssl_data, None))
    115  - #print('Connection terminated')
    116  - return
    117  - self.ssl_in_buff.write(ssl_data)
    118  - 
    119  - data_buff = b''
    120  - while not self.stop_evt.is_set():
    121  - try:
    122  - data_buff += self.ssl_obj.read()
    123  - except ssl.SSLWantReadError:
    124  - break
    125  - except Exception as e:
    126  - raise e
    127  - #print('data_buff %s' % data_buff)
    128  - if data_buff != b'':
    129  - await self.in_queue.put((data_buff, None))
    130  - 
    131  - except asyncio.CancelledError:
    132  - return
    133  - except Exception as e:
    134  - logging.debug('__decrypt_ssl_rec %s' % e)
    135  - 
    136  - async def __read_ssl_record(self):
    137  - try:
    138  - """
    139  - buffering the incoming SSL (encrypted) recodrs and putting them in an internal queue one record at a time
    140  - """
    141  - buffer = b''
    142  - length = None
    143  - while not self.stop_evt.is_set():
    144  - if length is None and len(buffer) >= 6:
    145  - length = int.from_bytes(buffer[3:5], byteorder = 'big', signed = False)
    146  -
    147  - if length is not None and len(buffer) >= length + 5:
    148  - #print('LB raw %s' % len(buffer[:length+5]))
    149  - await self.__ssl_in_q.put(buffer[:length+5])
    150  - buffer = buffer[length+5:]
    151  - length = None
    152  - continue
    153  -
    154  - data, err = await self.transport.in_queue.get()
    155  - if err is not None:
    156  - raise err
    157  - if data == b'':
    158  - await self.__ssl_in_q.put(b'')
    159  - return
    160  - buffer+= data
    161  -
    162  - 
    163  - except asyncio.CancelledError:
    164  - return
    165  - 
    166  - except Exception as e:
    167  - logging.debug('__read_ssl_record %s' % e)
    168  - await self.__ssl_in_q.put(b'')
    169  - 
    170  - #finally:
    171  - # self.stop_evt.set()
    172  -
    173  - async def __do_handshake(self):
    174  - try:
    175  - ctr = 0
    176  - while not self.stop_evt.is_set():
    177  - ctr += 1
    178  - #print('DST Performing handshake!')
    179  - try:
    180  - self.ssl_obj.do_handshake()
    181  - except ssl.SSLWantReadError:
    182  - #print('DST want %s' % ctr)
    183  - while True:
    184  - client_hello = self.ssl_out_buff.read()
    185  - if client_hello != b'':
    186  - #print('DST client_hello %s' % len(client_hello))
    187  - await self.transport.out_queue.put(client_hello)
    188  - else:
    189  - break
    190  -
    191  - #print('DST wating server hello %s' % ctr)
    192  - server_hello, err = await self.transport.in_queue.get()
    193  - if err is not None:
    194  - raise err
    195  - if server_hello == b'':
    196  - raise Exception('Server closed the connection!')
    197  - #print('DST server_hello %s' % len(server_hello))
    198  - self.ssl_in_buff.write(server_hello)
    199  - 
    200  - continue
    201  - except:
    202  - raise
    203  - else:
    204  - #print('DST handshake ok %s' % ctr)
    205  - #server_fin = tls_out_buff.read()
    206  - #print('DST server_fin %s ' % server_fin)
    207  - #await out_q.put(server_fin)
    208  - break
    209  - 
    210  - return True, None
    211  - 
    212  - except Exception as e:
    213  - logging.debug('__do_handshake %s' % e)
    214  - return None, e
    215  - 
    216  - @staticmethod
    217  - async def from_transport(transport, ssl_ctx = None, ssl_cert = None, ssl_key = None):
    218  - try:
    219  - tunnel = SSLClientTunnel()
    220  - tunnel.transport = transport
    221  - if ssl_ctx is not None:
    222  - tunnel.ssl_ctx = ssl_ctx
    223  - elif ssl_key is not None:
    224  - raise NotImplementedError()
    225  - else:
    226  - tunnel.ssl_ctx = ssl.create_default_context()
    227  - tunnel.ssl_ctx.check_hostname = False
    228  - tunnel.ssl_ctx.verify_mode = ssl.CERT_NONE
    229  - _, err = await tunnel.setup()
    230  - if err is not None:
    231  - raise err
    232  - 
    233  - return tunnel, None
    234  - except Exception as e:
    235  - return None, e
  • ■ ■ ■ ■ ■ ■
    aardwolf/transport/tcp.py
    1  -import asyncio
    2  - 
    3  -import logging
    4  - 
    5  -class TCPSocket:
    6  - """
    7  - Generic asynchronous TCP socket class, nothing RDP related.
    8  - Creates the connection and channels incoming/outgoing bytes via asynchonous queues.
    9  - """
    10  - def __init__(self, target):
    11  - self.settings = target
    12  - self.reader = None
    13  - self.writer = None
    14  -
    15  - self.out_queue = asyncio.Queue()
    16  - self.in_queue = asyncio.Queue()
    17  -
    18  - self.disconnected = asyncio.Event()
    19  - 
    20  - self.incoming_task = None
    21  - self.outgoing_task = None
    22  - self.disconnect_monitor_task = None
    23  - 
    24  - 
    25  - async def disconnect_monitor(self):
    26  - await self.disconnected.wait()
    27  - if self.incoming_task is not None:
    28  - self.incoming_task.cancel()
    29  - await self.disconnect()
    30  -
    31  - 
    32  -
    33  - async def disconnect(self):
    34  - """
    35  - Disconnects from the socket.
    36  - Stops the reader and writer streams.
    37  - """
    38  - if self.disconnected.is_set():
    39  - return
    40  - if self.outgoing_task is not None:
    41  - self.outgoing_task.cancel()
    42  - if self.incoming_task is not None:
    43  - self.incoming_task.cancel()
    44  - 
    45  - if self.writer is not None:
    46  - try:
    47  - self.writer.close()
    48  - except:
    49  - pass
    50  - self.writer = None
    51  - self.reader = None
    52  - self.disconnected.set()
    53  - 
    54  - async def handle_incoming(self):
    55  - """
    56  - Reads data bytes from the socket and dispatches it to the incoming queue
    57  - """
    58  - try:
    59  - lasterror = None
    60  - while not self.disconnected.is_set():
    61  - try:
    62  - await asyncio.sleep(0)
    63  - data = await self.reader.read(1073741824)
    64  - #print('TCP <- %s' % data.hex())
    65  - await self.in_queue.put( (data, None) )
    66  - if data == b'':
    67  - return
    68  -
    69  - except asyncio.CancelledError as e:
    70  - lasterror = e
    71  - break
    72  - except Exception as e:
    73  - #logging.exception('[TCPSocket] handle_incoming %s' % str(e))
    74  - lasterror = e
    75  - break
    76  -
    77  -
    78  - except asyncio.CancelledError:
    79  - return
    80  -
    81  - except Exception as e:
    82  - lasterror = e
    83  - 
    84  - finally:
    85  - #print('??????????')
    86  - if self.in_queue is not None:
    87  - await self.in_queue.put( (None, lasterror) )
    88  - await self.disconnect()
    89  -
    90  - async def handle_outgoing(self):
    91  - """
    92  - Reads data bytes from the outgoing queue and dispatches it to the socket
    93  - """
    94  - try:
    95  - while not self.disconnected.is_set():
    96  - data = await self.out_queue.get()
    97  - if data == b'':
    98  - return
    99  - #print('TCP -> %s' % data.hex())
    100  - self.writer.write(data)
    101  - await self.writer.drain()
    102  - except asyncio.CancelledError:
    103  - #the RDP connection is terminating
    104  - return
    105  -
    106  - except Exception as e:
    107  - logging.exception('[TCPSocket] handle_outgoing %s' % str(e))
    108  - finally:
    109  - await self.disconnect()
    110  -
    111  - async def connect(self):
    112  - """
    113  - Main function to be called, connects to the target specified in settings, and starts reading/writing.
    114  - """
    115  -
    116  - try:
    117  - con = asyncio.open_connection(self.settings.get_ip(), self.settings.get_port())
    118  -
    119  - try:
    120  - self.reader, self.writer = await asyncio.wait_for(con, int(self.settings.timeout))
    121  - except asyncio.TimeoutError:
    122  - logging.debug('[TCPSocket] Connection timeout')
    123  - raise Exception('[TCPSocket] Connection timeout')
    124  -
    125  - except ConnectionRefusedError:
    126  - logging.debug('[TCPSocket] Connection refused')
    127  - raise Exception('[TCPSocket] Connection refused')
    128  -
    129  - except asyncio.CancelledError:
    130  - #the RDP connection is terminating
    131  - raise asyncio.CancelledError
    132  -
    133  - except Exception as e:
    134  - logging.debug('[TCPSocket] connect generic exception')
    135  - raise e
    136  -
    137  - self.incoming_task = asyncio.create_task(self.handle_incoming())
    138  - self.outgoing_task = asyncio.create_task(self.handle_outgoing())
    139  - self.disconnect_monitor_task = asyncio.create_task(self.disconnect_monitor())
    140  - 
    141  - 
    142  - return True, None
    143  - except Exception as e:
    144  - try:
    145  - self.writer.close()
    146  - except:
    147  - pass
    148  - return False, e
    149  - 
    150  -
    151  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/vncconnection.py
    skipped 12 lines
    13 13  from typing import cast
    14 14   
    15 15  from aardwolf import logger
    16  -from aardwolf.network.selector import NetworkSelector
    17 16  from aardwolf.transport.tcpstream import TCPStream
    18 17  from unicrypto.symmetric import DES, MODE_ECB
    19 18   
    20 19  from aardwolf.commons.target import RDPTarget
    21  -from aardwolf.commons.credential import RDPCredential, RDPAuthProtocol
    22 20  from aardwolf.commons.queuedata import RDPDATATYPE, RDP_KEYBOARD_SCANCODE, RDP_KEYBOARD_UNICODE, \
    23 21   RDP_MOUSE, RDP_VIDEO, RDP_CLIPBOARD_READY, RDP_CLIPBOARD_DATA_TXT, RDP_CLIPBOARD_NEW_DATA_AVAILABLE, \
    24 22   RDP_BEEP
    skipped 3 lines
    28 26  from aardwolf.keyboard.layoutmanager import KeyboardLayoutManager
    29 27  from aardwolf.commons.queuedata.constants import MOUSEBUTTON, VIDEO_FORMAT
    30 28  from aardwolf.commons.iosettings import RDPIOSettings
     29 +from uniauth.common.credentials import UniCredential
     30 +from uniauth.common.constants import UniAuthSecret
    31 31   
    32 32  from PIL import Image
    33 33  try:
    skipped 17 lines
    51 51  ZRLE_ENCODING = 16
    52 52   
    53 53  class VNCConnection:
    54  - def __init__(self, target:RDPTarget, credentials:RDPCredential, iosettings:RDPIOSettings):
     54 + def __init__(self, target:RDPTarget, credentials:UniCredential, iosettings:RDPIOSettings):
    55 55   self.target = target
    56 56   self.credentials = credentials
    57 57   self.authapi = None
    skipped 4 lines
    62 62   self.server_name = None
    63 63   self.disconnected_evt = asyncio.Event() #this will be set if we disconnect for whatever reason
    64 64   self.server_supp_security_types = []
    65  - self.__selected_security_type = 1 if self.credentials.authentication_type == RDPAuthProtocol.NONE else 2 #currently we only support these 2
     65 + self.__selected_security_type = 1 if self.credentials.stype == UniAuthSecret.NONE else 2 #currently we only support these 2
    66 66   self.__refresh_screen_task = None
    67 67   self.__reader_loop_task = None
    68 68   self.__external_reader_task = None
    skipped 748 lines
    817 817  async def amain():
    818 818   try:
    819 819   logger.setLevel(1)
    820  - from aardwolf.commons.url import RDPConnectionURL
     820 + from aardwolf.commons.factory import RDPConnectionFactory
    821 821   from aardwolf.commons.iosettings import RDPIOSettings
    822 822   
    823 823   iosettings = RDPIOSettings()
    824 824   iosettings.video_out_format = VIDEO_FORMAT.RAW
    825 825  
    826 826   url = 'vnc+plain://alma:[email protected]:5900'
    827  - url = RDPConnectionURL(url)
     827 + url = RDPConnectionFactory.from_url(url)
    828 828   connection = url.get_connection(iosettings)
    829 829   
    830 830   _, err = await connection.connect()
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    setup.py
    skipped 62 lines
    63 63   'arc4>=0.0.4', #faster than cryptodome
    64 64   'Pillow>=9.0.0',
    65 65   'unicrypto>=0.0.4',
     66 + 'uniauth>=0.0.1'
    66 67   ],
    67 68  
    68 69  
    skipped 3 lines
    72 73   ],
    73 74   entry_points={
    74 75   'console_scripts': [
    75  - 'aardpcapsscan = aardwolf.examples.aardpcapscan:main',
    76  - 'aardploginscan = aardwolf.examples.aardploginscan:main',
    77  - 'aardpscreenshot = aardwolf.examples.aardpscreenshot:main',
     76 + 'ardpscan = aardwolf.examples.scanners.__main__:main',
    78 77   ],
    79  - 
    80 78   }
    81 79  )
    82 80   
Please wait...
Page is in error, reload to recover