2022-07-18 23:59:14 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
2022-02-28 22:18:53 +00:00
import logging
import asyncio
2023-02-01 11:04:13 +00:00
from os import getenv
2022-02-28 22:18:53 +00:00
from cme . connection import *
from cme . helpers . logger import highlight
from cme . logger import CMEAdapter
2022-10-20 18:53:36 +00:00
2022-07-06 15:17:24 +00:00
from aardwolf import logger
2023-02-05 20:23:40 +00:00
from aardwolf . connection import RDPConnection
2022-10-20 18:53:36 +00:00
from aardwolf . commons . queuedata . constants import VIDEO_FORMAT
2022-07-06 15:17:24 +00:00
from aardwolf . commons . iosettings import RDPIOSettings
2023-02-05 20:23:40 +00:00
from aardwolf . commons . target import RDPTarget , RDPConnectionDialect
2022-07-06 15:17:24 +00:00
from aardwolf . protocol . x224 . constants import SUPP_PROTOCOLS
2022-02-28 22:18:53 +00:00
2023-02-05 20:23:40 +00:00
from asyauth . common . credentials . ntlm import NTLMCredential
from asyauth . common . credentials . kerberos import KerberosCredential
from asyauth . common . constants import asyauthSecret
from asysocks . unicomm . common . target import UniTarget , UniProto
2022-04-01 14:02:34 +00:00
logger . setLevel ( logging . CRITICAL )
2022-02-28 22:18:53 +00:00
2022-03-12 11:54:40 +00:00
rdp_error_status = {
2022-10-27 20:43:52 +00:00
' 0xc0000071 ' : ' STATUS_PASSWORD_EXPIRED ' ,
' 0xc0000234 ' : ' STATUS_ACCOUNT_LOCKED_OUT ' ,
' 0xc0000072 ' : ' STATUS_ACCOUNT_DISABLED ' ,
' 0xc0000193 ' : ' STATUS_ACCOUNT_EXPIRED ' ,
' 0xc000006E ' : ' STATUS_ACCOUNT_RESTRICTION ' ,
' 0xc000006F ' : ' STATUS_INVALID_LOGON_HOURS ' ,
' 0xc0000070 ' : ' STATUS_INVALID_WORKSTATION ' ,
' 0xc000015B ' : ' STATUS_LOGON_TYPE_NOT_GRANTED ' ,
' 0xc0000224 ' : ' STATUS_PASSWORD_MUST_CHANGE ' ,
' 0xc0000022 ' : ' STATUS_ACCESS_DENIED ' ,
' 0xc000006d ' : ' STATUS_LOGON_FAILURE ' ,
2023-02-01 11:04:13 +00:00
' 0xc000006a ' : ' STATUS_WRONG_PASSWORD ' ,
' KDC_ERR_CLIENT_REVOKED ' : ' KDC_ERR_CLIENT_REVOKED ' ,
' KDC_ERR_PREAUTH_FAILED ' : ' KDC_ERR_PREAUTH_FAILED '
2022-03-12 11:54:40 +00:00
}
2022-02-28 22:18:53 +00:00
class rdp ( connection ) :
def __init__ ( self , args , db , host ) :
self . domain = None
self . server_os = None
self . iosettings = RDPIOSettings ( )
2022-03-13 12:01:04 +00:00
self . iosettings . channels = [ ]
2022-10-20 18:53:36 +00:00
self . iosettings . video_out_format = VIDEO_FORMAT . RAW
self . iosettings . clipboard_use_pyperclip = False
2022-11-02 19:39:14 +00:00
self . protoflags_nla = [ SUPP_PROTOCOLS . SSL | SUPP_PROTOCOLS . RDP , SUPP_PROTOCOLS . SSL , SUPP_PROTOCOLS . RDP ]
self . protoflags = [ SUPP_PROTOCOLS . SSL | SUPP_PROTOCOLS . RDP , SUPP_PROTOCOLS . SSL , SUPP_PROTOCOLS . RDP , SUPP_PROTOCOLS . SSL | SUPP_PROTOCOLS . HYBRID , SUPP_PROTOCOLS . SSL | SUPP_PROTOCOLS . HYBRID_EX ]
2022-03-13 12:01:04 +00:00
width , height = args . res . upper ( ) . split ( ' X ' )
height = int ( height )
width = int ( width )
self . iosettings . video_width = width
self . iosettings . video_height = height
self . iosettings . video_bpp_min = 15 #servers dont support 8 any more :/
self . iosettings . video_bpp_max = 32
self . iosettings . video_out_format = VIDEO_FORMAT . PNG #PIL produces incorrect picture for some reason?! TODO: check bug
2022-02-28 22:18:53 +00:00
self . output_filename = None
self . domain = None
self . server_os = None
2022-03-13 12:01:04 +00:00
self . url = None
2022-04-20 08:34:49 +00:00
self . nla = True
2022-04-01 14:02:34 +00:00
self . hybrid = False
2023-02-05 20:23:40 +00:00
self . target = None
self . auth = None
2022-02-28 22:18:53 +00:00
connection . __init__ ( self , args , db , host )
@staticmethod
def proto_args ( parser , std_parser , module_parser ) :
rdp_parser = parser . add_parser ( ' rdp ' , help = " own stuff using RDP " , parents = [ std_parser , module_parser ] )
rdp_parser . add_argument ( " -H " , ' --hash ' , metavar = " HASH " , dest = ' hash ' , nargs = ' + ' , default = [ ] , help = ' NTLM hash(es) or file(s) containing NTLM hashes ' )
rdp_parser . add_argument ( " --no-bruteforce " , action = ' store_true ' , help = ' No spray when using file for username and password (user1 => password1, user2 => password2 ' )
rdp_parser . add_argument ( " --continue-on-success " , action = ' store_true ' , help = " continues authentication attempts even after successes " )
rdp_parser . add_argument ( " --port " , type = int , default = 3389 , help = " Custom RDP port " )
rdp_parser . add_argument ( " --rdp-timeout " , type = int , default = 1 , help = " RDP timeout on socket connection " )
2022-11-02 17:47:32 +00:00
rdp_parser . add_argument ( " --nla-screenshot " , action = " store_true " , help = " Screenshot RDP login prompt if NLA is disabled " )
2022-02-28 22:18:53 +00:00
dgroup = rdp_parser . add_mutually_exclusive_group ( )
dgroup . add_argument ( " -d " , metavar = " DOMAIN " , dest = ' domain ' , type = str , default = None , help = " domain to authenticate to " )
dgroup . add_argument ( " --local-auth " , action = ' store_true ' , help = ' authenticate locally to each target ' )
2022-03-12 11:54:40 +00:00
egroup = rdp_parser . add_argument_group ( " Screenshot " , " Remote Desktop Screenshot " )
egroup . add_argument ( " --screenshot " , action = " store_true " , help = " Screenshot RDP if connection success " )
2022-03-15 10:43:31 +00:00
egroup . add_argument ( ' --screentime ' , type = int , default = 10 , help = ' Time to wait for desktop image ' )
2022-03-13 12:01:04 +00:00
egroup . add_argument ( ' --res ' , default = ' 1024x768 ' , help = ' Resolution in " WIDTHxHEIGHT " format. Default: " 1024x768 " ' )
2022-03-12 11:54:40 +00:00
2022-02-28 22:18:53 +00:00
return parser
2023-01-04 17:26:37 +00:00
# def proto_flow(self):
# if self.create_conn_obj():
# self.proto_logger()
# self.print_host_info()
# if self.login() or (self.username == '' and self.password == ''):
# if hasattr(self.args, 'module') and self.args.module:
# self.call_modules()
# else:
# self.call_cmd_args()
2022-02-28 22:18:53 +00:00
def proto_logger ( self ) :
self . logger = CMEAdapter ( extra = { ' protocol ' : ' RDP ' ,
' host ' : self . host ,
2022-06-23 13:16:36 +00:00
' port ' : self . args . port ,
2022-03-06 15:55:24 +00:00
' hostname ' : self . hostname } )
2022-02-28 22:18:53 +00:00
def print_host_info ( self ) :
2022-04-01 14:02:34 +00:00
if self . domain == None :
self . logger . info ( u " Probably old, doesn ' t not support HYBRID or HYBRID_EX (nla: {} ) " . format ( self . nla ) )
else :
self . logger . info ( u " {} (name: {} ) (domain: {} ) (nla: {} ) " . format ( self . server_os ,
self . hostname ,
self . domain ,
self . nla ) )
2023-01-04 17:26:37 +00:00
return True
2022-02-28 22:18:53 +00:00
def create_conn_obj ( self ) :
2023-02-05 20:23:40 +00:00
self . target = RDPTarget ( ip = self . host , domain = " FAKE " )
self . auth = NTLMCredential ( secret = " pass " , username = " user " , domain = " FAKE " , stype = asyauthSecret . PASS )
2022-11-02 19:39:14 +00:00
self . check_nla ( )
2023-02-05 20:23:40 +00:00
2022-11-02 19:39:14 +00:00
for proto in reversed ( self . protoflags ) :
2022-04-01 14:02:34 +00:00
try :
self . iosettings . supported_protocols = proto
2023-02-05 20:23:40 +00:00
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
asyncio . run ( self . connect_rdp ( ) )
2022-04-01 14:02:34 +00:00
except OSError as e :
if " Errno 104 " not in str ( e ) :
return False
except Exception as e :
2022-04-20 07:41:15 +00:00
if " TCPSocket " in str ( e ) :
return False
2022-04-01 14:02:34 +00:00
if " Reason: " not in str ( e ) :
info_domain = self . conn . get_extra_info ( )
self . domain = info_domain [ ' dnsdomainname ' ]
self . hostname = info_domain [ ' computername ' ]
self . server_os = info_domain [ ' os_guess ' ] + " Build " + str ( info_domain [ ' os_build ' ] )
self . output_filename = os . path . expanduser ( ' ~/.cme/logs/ {} _ {} _ {} ' . format ( self . hostname , self . host , datetime . now ( ) . strftime ( " % Y- % m- %d _ % H % M % S " ) ) )
self . output_filename = self . output_filename . replace ( " : " , " - " )
break
if self . args . domain :
self . domain = self . args . domain
if self . args . local_auth :
2022-06-21 19:38:25 +00:00
self . domain = self . hostname
2022-03-06 15:55:24 +00:00
2023-02-05 20:23:40 +00:00
self . target = RDPTarget ( ip = self . host , hostname = self . hostname , domain = self . domain , dc_ip = self . domain )
2022-04-01 14:02:34 +00:00
return True
2022-02-28 22:18:53 +00:00
2022-11-02 19:39:14 +00:00
def check_nla ( self ) :
for proto in self . protoflags_nla :
try :
self . iosettings . supported_protocols = proto
2023-02-05 20:23:40 +00:00
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
asyncio . run ( self . connect_rdp ( ) )
2022-11-02 19:39:14 +00:00
if str ( proto ) == " SUPP_PROTOCOLS.RDP " or str ( proto ) == " SUPP_PROTOCOLS.SSL " or str ( proto ) == " SUPP_PROTOCOLS.SSL|SUPP_PROTOCOLS.RDP " :
self . nla = False
return
2023-02-05 20:23:40 +00:00
except Exception as e :
2022-11-02 19:39:14 +00:00
pass
2023-02-05 20:23:40 +00:00
async def connect_rdp ( self ) :
2022-02-28 22:18:53 +00:00
_ , err = await self . conn . connect ( )
if err is not None :
raise err
2023-01-02 11:37:37 +00:00
def kerberos_login ( self , domain , username , password = ' ' , ntlm_hash = ' ' , aesKey = ' ' , kdcHost = ' ' , useCache = False ) :
try :
2023-02-05 20:23:40 +00:00
lmhash = ' '
nthash = ' '
2023-01-02 11:37:37 +00:00
#This checks to see if we didn't provide the LM Hash
if ntlm_hash . find ( ' : ' ) != - 1 :
lmhash , nthash = ntlm_hash . split ( ' : ' )
self . hash = nthash
else :
nthash = ntlm_hash
self . hash = ntlm_hash
if lmhash : self . lmhash = lmhash
if nthash : self . nthash = nthash
2023-02-01 11:04:13 +00:00
if not all ( ' ' == s for s in [ nthash , password , aesKey ] ) :
kerb_pass = next ( s for s in [ nthash , password , aesKey ] if s )
else :
kerb_pass = ' '
2023-01-02 11:37:37 +00:00
fqdn_host = self . hostname + " . " + self . domain
password = password if password else nthash
2023-02-05 20:23:40 +00:00
2023-02-01 11:04:13 +00:00
if useCache :
2023-02-05 20:23:40 +00:00
stype = asyauthSecret . CCACHE
2023-02-01 11:04:13 +00:00
if not password :
password = getenv ( ' KRB5CCNAME ' ) if not password else password
if " / " in password :
self . logger . error ( " Kerberos ticket need to be on the local directory " )
return False
ccache = CCache . loadFile ( getenv ( ' KRB5CCNAME ' ) )
ticketCreds = ccache . credentials [ 0 ]
username = ticketCreds [ ' client ' ] . prettyPrint ( ) . decode ( ) . split ( ' @ ' ) [ 0 ]
2023-02-05 20:23:40 +00:00
else :
stype = asyauthSecret . PASS if not nthash else asyauthSecret . NT
kerberos_target = UniTarget ( self . domain , 88 , UniProto . CLIENT_TCP , proxies = None , dns = None , dc_ip = self . domain )
self . auth = KerberosCredential ( target = kerberos_target , secret = password , username = username , domain = domain , stype = stype )
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
asyncio . run ( self . connect_rdp ( ) )
2023-02-01 11:04:13 +00:00
2023-01-02 11:37:37 +00:00
self . admin_privs = True
2023-02-01 11:04:13 +00:00
self . logger . success ( u ' {} \\ {} {} {} ' . format ( domain ,
2023-01-02 11:37:37 +00:00
username ,
2023-02-01 11:04:13 +00:00
# Show what was used between cleartext, nthash, aesKey and ccache
" from ccache " if useCache
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2023-01-02 11:37:37 +00:00
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) ) )
if not self . args . local_auth :
add_user_bh ( username , domain , self . logger , self . config )
if not self . args . continue_on_success :
return True
except Exception as e :
2023-02-01 11:04:13 +00:00
if " KDC_ERR " in str ( e ) :
reason = None
for word in rdp_error_status . keys ( ) :
if word in str ( e ) :
reason = rdp_error_status [ word ]
self . logger . error ( u ' {} \\ {} {} {} ' . format ( domain ,
username ,
# Show what was used between cleartext, nthash, aesKey and ccache
" from ccache " if useCache
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
' ( {} ) ' . format ( reason ) if reason else str ( e ) ) ,
color = ' magenta ' if ( ( reason or " CredSSP " in str ( e ) ) and reason != " KDC_ERR_C_PRINCIPAL_UNKNOWN " ) else ' red ' )
elif " Authentication failed! " in str ( e ) :
2023-01-02 11:37:37 +00:00
self . logger . success ( u ' {} \\ {} : {} {} ' . format ( domain ,
username ,
password ,
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) ) )
2023-02-01 11:04:13 +00:00
elif " No such file " in str ( e ) :
self . logger . error ( str ( e ) )
2023-01-02 11:37:37 +00:00
else :
reason = None
for word in rdp_error_status . keys ( ) :
if word in str ( e ) :
reason = rdp_error_status [ word ]
if " cannot unpack non-iterable NoneType object " == str ( e ) :
reason = " User valid but cannot connect "
2023-02-01 11:04:13 +00:00
self . logger . error ( u ' {} \\ {} {} {} ' . format ( domain ,
2023-01-02 11:37:37 +00:00
username ,
2023-02-01 11:04:13 +00:00
# Show what was used between cleartext, nthash, aesKey and ccache
" from ccache " if useCache
else " : %s " % ( kerb_pass if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ) ,
2023-01-02 11:37:37 +00:00
' ( {} ) ' . format ( reason ) if reason else ' ' ) ,
color = ' magenta ' if ( ( reason or " CredSSP " in str ( e ) ) and reason != " STATUS_LOGON_FAILURE " ) else ' red ' )
return False
2022-04-01 14:02:34 +00:00
def plaintext_login ( self , domain , username , password ) :
2022-02-28 22:18:53 +00:00
try :
2023-02-05 20:23:40 +00:00
self . auth = NTLMCredential ( secret = password , username = username , domain = domain , stype = asyauthSecret . PASS )
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
asyncio . run ( self . connect_rdp ( ) )
2022-02-28 22:18:53 +00:00
self . admin_privs = True
2022-04-01 14:02:34 +00:00
self . logger . success ( u ' {} \\ {} : {} {} ' . format ( domain ,
2022-02-28 22:18:53 +00:00
username ,
password ,
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) ) )
2022-03-06 15:55:24 +00:00
if not self . args . local_auth :
add_user_bh ( username , domain , self . logger , self . config )
2022-02-28 22:18:53 +00:00
if not self . args . continue_on_success :
return True
except Exception as e :
2022-11-02 19:39:14 +00:00
if " Authentication failed! " in str ( e ) :
self . logger . success ( u ' {} \\ {} : {} {} ' . format ( domain ,
username ,
password ,
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) ) )
else :
reason = None
for word in rdp_error_status . keys ( ) :
if word in str ( e ) :
reason = rdp_error_status [ word ]
2022-11-08 08:25:59 +00:00
if " cannot unpack non-iterable NoneType object " == str ( e ) :
reason = " User valid but cannot connect "
2022-11-02 19:39:14 +00:00
self . logger . error ( u ' {} \\ {} : {} {} ' . format ( domain ,
username ,
password ,
' ( {} ) ' . format ( reason ) if reason else ' ' ) ,
color = ' magenta ' if ( ( reason or " CredSSP " in str ( e ) ) and reason != " STATUS_LOGON_FAILURE " ) else ' red ' )
2022-02-28 22:18:53 +00:00
return False
def hash_login ( self , domain , username , ntlm_hash ) :
try :
2023-02-05 20:23:40 +00:00
self . auth = NTLMCredential ( secret = ntlm_hash , username = username , domain = domain , stype = asyauthSecret . NT )
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
asyncio . run ( self . connect_rdp ( ) )
2022-02-28 22:18:53 +00:00
self . admin_privs = True
self . logger . success ( u ' {} \\ {} : {} {} ' . format ( self . domain ,
username ,
ntlm_hash ,
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) ) )
2022-03-06 15:55:24 +00:00
if not self . args . local_auth :
add_user_bh ( username , domain , self . logger , self . config )
2022-02-28 22:18:53 +00:00
if not self . args . continue_on_success :
return True
except Exception as e :
2022-11-02 19:39:14 +00:00
if " Authentication failed! " in str ( e ) :
self . logger . success ( u ' {} \\ {} : {} {} ' . format ( domain ,
username ,
ntlm_hash ,
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' ) ) )
else :
reason = None
for word in rdp_error_status . keys ( ) :
if word in str ( e ) :
reason = rdp_error_status [ word ]
if " cannot unpack non-iterable NoneType object " == str ( e ) :
reason = " User valid but cannot connect "
self . logger . error ( u ' {} \\ {} : {} {} ' . format ( domain ,
username ,
ntlm_hash ,
' ( {} ) ' . format ( reason ) if reason else ' ' ) ,
color = ' magenta ' if ( ( reason or " CredSSP " in str ( e ) ) and reason != " STATUS_LOGON_FAILURE " ) else ' red ' )
2022-02-28 22:18:53 +00:00
return False
2022-03-12 11:54:40 +00:00
2022-03-13 12:01:04 +00:00
async def screen ( self ) :
2023-01-04 17:26:37 +00:00
try :
2023-02-05 20:23:40 +00:00
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
await self . connect_rdp ( )
except Exception as e :
2023-01-04 17:26:37 +00:00
return
2022-03-13 12:01:04 +00:00
2023-02-05 20:23:40 +00:00
await asyncio . sleep ( int ( 5 ) )
2022-03-13 12:01:04 +00:00
if self . conn is not None and self . conn . desktop_buffer_has_data is True :
buffer = self . conn . get_desktop_buffer ( VIDEO_FORMAT . PIL )
2022-10-12 13:05:21 +00:00
filename = os . path . expanduser ( ' ~/.cme/screenshots/ {} _ {} _ {} .png ' . format ( self . hostname , self . host , datetime . now ( ) . strftime ( " % Y- % m- %d _ % H % M % S " ) ) )
2022-03-13 12:01:04 +00:00
buffer . save ( filename , ' png ' )
2022-10-12 13:05:21 +00:00
self . logger . highlight ( " Screenshot saved {} " . format ( filename ) )
2022-03-13 12:01:04 +00:00
2022-03-12 11:54:40 +00:00
def screenshot ( self ) :
2022-03-13 12:01:04 +00:00
asyncio . run ( self . screen ( ) )
2022-06-23 13:16:36 +00:00
2022-11-02 17:47:32 +00:00
async def nla_screen ( self ) :
# Otherwise it crash
self . iosettings . supported_protocols = None
2023-02-05 20:23:40 +00:00
self . auth = NTLMCredential ( secret = ' ' , username = ' ' , domain = ' ' , stype = asyauthSecret . PASS )
self . conn = RDPConnection ( iosettings = self . iosettings , target = self . target , credentials = self . auth )
await self . connect_rdp_old ( self . url )
2022-11-02 17:47:32 +00:00
await asyncio . sleep ( int ( self . args . screentime ) )
if self . conn is not None and self . conn . desktop_buffer_has_data is True :
buffer = self . conn . get_desktop_buffer ( VIDEO_FORMAT . PIL )
filename = os . path . expanduser ( ' ~/.cme/screenshots/ {} _ {} _ {} .png ' . format ( self . hostname , self . host , datetime . now ( ) . strftime ( " % Y- % m- %d _ % H % M % S " ) ) )
buffer . save ( filename , ' png ' )
self . logger . highlight ( " NLA Screenshot saved {} " . format ( filename ) )
def nla_screenshot ( self ) :
if not self . nla :
2022-11-08 08:25:59 +00:00
asyncio . run ( self . nla_screen ( ) )