238 lines
8.3 KiB
Python
Executable File
238 lines
8.3 KiB
Python
Executable File
#!/usr/bin/python
|
|
# Copyright (c) 2003-2015 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.
|
|
#
|
|
# A similar approach to smbexec but executing commands through WMI.
|
|
# Main advantage here is it runs under the user (has to be Admin)
|
|
# account, not SYSTEM, plus, it doesn't generate noisy messages
|
|
# in the event log that smbexec.py does when creating a service.
|
|
# Drawback is it needs DCOM, hence, I have to be able to access
|
|
# DCOM ports at the target machine.
|
|
#
|
|
# Author:
|
|
# beto (@agsolino)
|
|
#
|
|
# Reference for:
|
|
# DCOM
|
|
#
|
|
|
|
import sys
|
|
import os
|
|
import cmd
|
|
import logging
|
|
import string
|
|
import random
|
|
import ntpath
|
|
import core.settings as settings
|
|
|
|
from gevent import sleep
|
|
from impacket import version
|
|
from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21
|
|
from impacket.dcerpc.v5.dcomrt import DCOMConnection
|
|
from impacket.dcerpc.v5.dcom import wmi
|
|
from impacket.dcerpc.v5.dtypes import NULL
|
|
from StringIO import StringIO
|
|
|
|
OUTPUT_FILENAME = ''.join(random.sample(string.ascii_letters, 10))
|
|
|
|
class WMIEXEC:
|
|
def __init__(self, logger, command = '', username = '', password = '', domain = '', hashes = None, aesKey = None, share = None, noOutput=False, doKerberos=False):
|
|
self.__logger = logger
|
|
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.shell = None
|
|
if hashes is not None:
|
|
self.__lmhash, self.__nthash = hashes.split(':')
|
|
if password is None:
|
|
self.__password = ''
|
|
|
|
def run(self, addr, smb):
|
|
if self.__noOutput is False:
|
|
smbConnection = smb
|
|
else:
|
|
logging.info('Output retrieval disabled')
|
|
smbConnection = None
|
|
|
|
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos)
|
|
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login)
|
|
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
|
|
iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
|
|
iWbemLevel1Login.RemRelease()
|
|
|
|
win32Process,_ = iWbemServices.GetObject('Win32_Process')
|
|
|
|
try:
|
|
self.shell = RemoteShell(self.__logger, self.__share, win32Process, smbConnection)
|
|
self.shell.onecmd(self.__command)
|
|
except (Exception, KeyboardInterrupt) as e:
|
|
logging.error(str(e))
|
|
dcom.disconnect()
|
|
|
|
dcom.disconnect()
|
|
|
|
class RemoteShell(cmd.Cmd):
|
|
def __init__(self, logger, share, win32Process, smbConnection):
|
|
cmd.Cmd.__init__(self)
|
|
self.__logger = logger
|
|
self.__share = share
|
|
self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
|
|
self.__outputBuffer = ''
|
|
self.__shell = 'cmd.exe /Q /c '
|
|
self.__win32Process = win32Process
|
|
self.__transferClient = smbConnection
|
|
self.__pwd = 'C:\\'
|
|
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)
|
|
put {src_file, dst_path} - uploads a local file to the dst_path (dst_path = default current directory)
|
|
get {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, e:
|
|
logging.error(str(e))
|
|
|
|
def do_get(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, e:
|
|
logging.error(str(e))
|
|
os.remove(filename)
|
|
pass
|
|
|
|
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))
|
|
pass
|
|
|
|
def do_exit(self, s):
|
|
return True
|
|
|
|
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:
|
|
self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s))
|
|
self.execute_remote('cd ')
|
|
self.__pwd = self.__outputBuffer.strip('\r\n')
|
|
self.prompt = self.__pwd + '>'
|
|
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 + '>'
|
|
self.__outputBuffer = ''
|
|
else:
|
|
if 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
|
|
sleep(1)
|
|
pass
|
|
else:
|
|
#print str(e)
|
|
pass
|
|
self.__transferClient.deleteFile(self.__share, self.__output)
|
|
|
|
def execute_remote(self, data):
|
|
command = self.__shell + data
|
|
if self.__noOutput is False:
|
|
command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1'
|
|
self.__win32Process.Create(command, self.__pwd, None)
|
|
self.get_output()
|
|
|
|
def send_data(self, data):
|
|
self.execute_remote(data)
|
|
self.__logger.success('Executed command via WMIEXEC')
|
|
|
|
if self.__noOutput is False:
|
|
buf = StringIO(self.__outputBuffer.strip()).readlines()
|
|
for line in buf:
|
|
self.__logger.results(u'{}'.format(line.strip()))
|
|
self.__outputBuffer = '' |