diff --git a/requirements.txt b/requirements.txt index 779e2c6..6849dbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,4 @@ -# slightly improved version of impacket that allows you to use impacket.examples.serviceinstall -# to create services that are not randomly named --e git://github.com/nick-o/impacket.git@e4fcac42975fd2f20d9ae6e8643c0fd9fab33c7a#egg=impacket - +impacket>=0.9.11 ipaddress>=1.0.6 # in stdlib as of python3.3 iso8601>=0.1.5 Jinja2>=2.7.1 # bug resolve @2.7.1 diff --git a/satori/contrib/psexec.py b/satori/contrib/psexec.py index fd335dc..82c5447 100644 --- a/satori/contrib/psexec.py +++ b/satori/contrib/psexec.py @@ -8,7 +8,7 @@ # $Id: psexec.py 712 2012-09-06 04:26:22Z bethus@gmail.com $ # # PSEXEC like functionality example using -#RemComSvc (https://github.com/kavika13/RemCom) +# RemComSvc (https://github.com/kavika13/RemCom) # # Author: # beto (bethus@gmail.com) @@ -21,32 +21,31 @@ OK """ - import cmd import os import re import sys -#from impacket.smbconnection import * from impacket.dcerpc import dcerpc from impacket.dcerpc import transport from impacket.examples import remcomsvc -from impacket.examples import serviceinstall from impacket import smbconnection from impacket import structure as im_structure from impacket import version -#from impacket.dcerpc import dcerpc_v4 -#from impacket.dcerpc import srvsvc -#from impacket.dcerpc import svcctl -#from impacket.smbconnection import smb -#from impacket.smbconnection import SMB_DIALECT -#from impacket.smbconnection import SMBConnection + +from satori import serviceinstall import argparse import random import string -import threading -import time + +try: + import eventlet + threading = eventlet.patcher.original('threading') + time = eventlet.patcher.original('time') +except ImportError: + import threading + import time class RemComMessage(im_structure.Structure): @@ -279,9 +278,9 @@ class Pipes(threading.Thread): global dialect remoteHost = self.transport.get_smb_connection().getRemoteHost() - #self.server = SMBConnection('*SMBSERVER', - #self.transport.get_smb_connection().getRemoteHost(), - #sess_port = self.port, preferredDialect = SMB_DIALECT) + # self.server = SMBConnection('*SMBSERVER', + # self.transport.get_smb_connection().getRemoteHost(), + # sess_port = self.port, preferredDialect = SMB_DIALECT) self.server = smbconnection.SMBConnection('*SMBSERVER', remoteHost, sess_port=self.port, preferredDialect=dialect) # noqa @@ -380,9 +379,9 @@ class RemoteShell(cmd.Cmd): def connect_transferClient(self): """.""" - #self.transferClient = SMBConnection('*SMBSERVER', - #self.server.getRemoteHost(), sess_port = self.port, - #preferredDialect = SMB_DIALECT) + # self.transferClient = SMBConnection('*SMBSERVER', + # self.server.getRemoteHost(), sess_port = self.port, + # preferredDialect = SMB_DIALECT) self.transferClient = smbconnection.SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, diff --git a/satori/serviceinstall.py b/satori/serviceinstall.py new file mode 100644 index 0000000..c82b86e --- /dev/null +++ b/satori/serviceinstall.py @@ -0,0 +1,303 @@ +# Copyright (c) 2003-2012 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. +# +# $Id: serviceinstall.py 1141 2014-02-12 16:39:51Z bethus@gmail.com $ +# +# Service Install Helper library used by psexec and smbrelayx +# You provide an already established connection and an exefile +# (or class that mimics a file class) and this will install and +# execute the service, and then uninstall (install(), uninstall(). +# It tries to take care as much as possible to leave everything clean. +# +# Author: +# Alberto Solino (bethus@gmail.com) +# +"""This module has been copied from impacket.examples.serviceinstall. + +It exposes a class that can be used to install services on Windows devices +""" + +import random +import string + +from impacket.dcerpc import dcerpc +from impacket.dcerpc import srvsvc +from impacket.dcerpc import svcctl +from impacket.dcerpc import transport +from impacket import smb +from impacket import smb3 +from impacket import smbconnection + + +class ServiceInstall(): + + """Class to manage Services on a remote windows server. + + This class is slightly improved from the example in the impacket package + in a way that it allows to specify a service and executable name during + instantiation rather than using a random name by default + """ + + def __init__(self, SMBObject, exeFile, serviceName=None, + binaryServiceName=None): + """Contructor of the class. + + :param SMBObject: existing SMBObject + :param exeFile: file handle or class that mimics a file + class, this will be used to create the + service + :param serviceName: name of the service to be created, will be + random if not set + :param binaryServiceName name of the uploaded file, wil be random if + not set + """ + print("In constructor now!!!") + self._rpctransport = 0 + if not serviceName: + self.__service_name = ''.join( + [random.choice(string.letters) for i in range(4)]) + else: + self.__service_name = serviceName + if not binaryServiceName: + self.__binary_service_name = ''.join( + [random.choice(string.letters) for i in range(8)]) + '.exe' + else: + self.__binary_service_name = binaryServiceName + self.__exeFile = exeFile + + # We might receive two different types of objects, always end up + # with a SMBConnection one + if isinstance(SMBObject, smb.SMB) or isinstance(SMBObject, smb3.SMB3): + self.connection = smbconnection.SMBConnection( + existingConnection=SMBObject) + else: + self.connection = SMBObject + + self.share = '' + + def getShare(self): + """Return the writable share that has been used to upload the file.""" + return self.share + + def getShares(self): + """Return a list of shares on the remote windows server.""" + # Setup up a DCE SMBTransport with the connection already in place + print("[*] Requesting shares on %s....." % ( + self.connection.getRemoteHost())) + try: + self._rpctransport = transport.SMBTransport( + '', '', filename=r'\srvsvc', smb_connection=self.connection) + self._dce = dcerpc.DCERPC_v5(self._rpctransport) + self._dce.connect() + + self._dce.bind(srvsvc.MSRPC_UUID_SRVSVC) + srv_svc = srvsvc.DCERPCSrvSvc(self._dce) + resp = srv_svc.get_share_enum_1(self._rpctransport.get_dip()) + return resp + except Exception: + print("[!] Error requesting shares on %s, aborting....." % ( + self.connection.getRemoteHost())) + raise + + def createService(self, handle, share, path): + """Install Service on the remote server. + + This method will connect to the SVCManager on the remote server and + install the service as specified in the constructor. + """ + print("[*] Creating service %s on %s....." % ( + self.__service_name, self.connection.getRemoteHost())) + + # First we try to open the service in case it exists. + # If it does, we remove it. + try: + resp = self.rpcsvc.OpenServiceW( + handle, self.__service_name.encode('utf-16le')) + except Exception as e: + if e.get_error_code() == svcctl.ERROR_SERVICE_DOES_NOT_EXISTS: + # We're good, pass the exception + pass + else: + raise + else: + # It exists, remove it + self.rpcsvc.DeleteService(resp['ContextHandle']) + self.rpcsvc.CloseServiceHandle(resp['ContextHandle']) + + # Create the service + command = '%s\\%s' % (path, self.__binary_service_name) + try: + resp = self.rpcsvc.CreateServiceW( + handle, self.__service_name.encode('utf-16le'), + self.__service_name.encode('utf-16le'), + command.encode('utf-16le')) + except Exception: + print("[!] Error creating service %s on %s" % ( + self.__service_name, self.connection.getRemoteHost())) + raise + else: + return resp['ContextHandle'] + + def openSvcManager(self): + """Connect to the SVCManager on the remote host.""" + print("[*] Opening SVCManager on %s...." + "." % self.connection.getRemoteHost()) + # Setup up a DCE SMBTransport with the connection already in place + self._rpctransport = transport.SMBTransport( + '', '', filename=r'\svcctl', smb_connection=self.connection) + self._dce = dcerpc.DCERPC_v5(self._rpctransport) + self._dce.connect() + self._dce.bind(svcctl.MSRPC_UUID_SVCCTL) + self.rpcsvc = svcctl.DCERPCSvcCtl(self._dce) + try: + resp = self.rpcsvc.OpenSCManagerW() + except Exception: + print("[!] Error opening SVCManager on %s...." + "." % self.connection.getRemoteHost()) + raise Exception('Unable to open SVCManager') + else: + return resp['ContextHandle'] + + def copy_file(self, src, tree, dst): + """Copy file to remote SMB share.""" + print("[*] Uploading file %s" % dst) + if isinstance(src, str): + # We have a filename + fh = open(src, 'rb') + else: + # We have a class instance, it must have a read method + fh = src + f = dst + pathname = string.replace(f, '/', '\\') + try: + self.connection.putFile(tree, pathname, fh.read) + except Exception: + print("[!] Error uploading file %s, aborting....." % dst) + raise + fh.close() + + def findWritableShare(self, shares): + """Retrieve a list of writable shares on the remote host.""" + # Check we can write a file on the shares, stop in the first one + for i in shares: + if (i['Type'] == smb.SHARED_DISK or + i['Type'] == smb.SHARED_DISK_HIDDEN): + share = i['NetName'].decode('utf-16le')[:-1] + try: + self.connection.createDirectory(share, 'BETO') + except Exception: + # Can't create, pass + print("[!] share '%s' is not writable." % share) + pass + else: + print('[*] Found writable share %s' % share) + self.connection.deleteDirectory(share, 'BETO') + return str(share) + return None + + def install(self): # noqa + """Install the service on the remote host.""" + if self.connection.isGuestSession(): + print("[!] Authenticated as Guest. Aborting") + self.connection.logoff() + del(self.connection) + else: + fileCopied = False + serviceCreated = False + # Do the stuff here + try: + # Let's get the shares + shares = self.getShares() + self.share = self.findWritableShare(shares) + self.copy_file(self.__exeFile, + self.share, + self.__binary_service_name) + fileCopied = True + svcManager = self.openSvcManager() + if svcManager != 0: + serverName = self.connection.getServerName() + if serverName != '': + path = '\\\\%s\\%s' % (serverName, self.share) + else: + path = '\\\\127.0.0.1\\' + self.share + service = self.createService(svcManager, self.share, path) + serviceCreated = True + if service != 0: + # Start service + print('[*] Starting service %s....' + '.' % self.__service_name) + try: + self.rpcsvc.StartServiceW(service) + except Exception: + pass + self.rpcsvc.CloseServiceHandle(service) + self.rpcsvc.CloseServiceHandle(svcManager) + return True + except Exception as e: + print("[!] Error performing the installation, cleaning up: " + "%s" % e) + try: + self.rpcsvc.StopService(service) + except Exception: + pass + if fileCopied is True: + try: + self.connection.deleteFile(self.share, + self.__binary_service_name) + except Exception: + pass + if serviceCreated is True: + try: + self.rpcsvc.DeleteService(service) + except Exception: + pass + return False + + def uninstall(self): + """Uninstall service from remote host and delete file from share.""" + fileCopied = True + serviceCreated = True + # Do the stuff here + try: + # Let's get the shares + svcManager = self.openSvcManager() + if svcManager != 0: + resp = self.rpcsvc.OpenServiceA(svcManager, + self.__service_name) + service = resp['ContextHandle'] + print('[*] Stoping service %s.....' % self.__service_name) + try: + self.rpcsvc.StopService(service) + except Exception: + pass + print('[*] Removing service %s.....' % self.__service_name) + self.rpcsvc.DeleteService(service) + self.rpcsvc.CloseServiceHandle(service) + self.rpcsvc.CloseServiceHandle(svcManager) + print('[*] Removing file %s.....' % self.__binary_service_name) + self.connection.deleteFile(self.share, self.__binary_service_name) + except Exception: + print("[!] Error performing the uninstallation, cleaning up") + try: + self.rpcsvc.StopService(service) + except Exception: + pass + if fileCopied is True: + try: + self.connection.deleteFile(self.share, + self.__binary_service_name) + except Exception: + try: + self.connection.deleteFile(self.share, + self.__binary_service_name) + except Exception: + pass + pass + if serviceCreated is True: + try: + self.rpcsvc.DeleteService(service) + except Exception: + pass diff --git a/satori/smb.py b/satori/smb.py index 6839ada..6914184 100644 --- a/satori/smb.py +++ b/satori/smb.py @@ -150,7 +150,7 @@ class SMBClient(object): # pylint: disable=R0902 This will tunnel a local ephemeral port to the host's port. This will preserve the original host and port """ - self.ssh_tunnel = tunnel.connect(self.host, self.port, self.gateway) + self.ssh_tunnel = tunnel.Tunnel(self.host, self.port, self.gateway) self._orig_host = self.host self._orig_port = self.port self.host, self.port = self.ssh_tunnel.address @@ -292,7 +292,7 @@ class SMBClient(object): # pylint: disable=R0902 % (self._process.pid, self._process.poll(), json.dumps(error))) - time.sleep(wait/1000) + time.sleep(wait / 1000) stdout = tmp_out while (not tmp_out == '' or (not self._prompt_pattern.findall(stdout) and @@ -310,7 +310,7 @@ class SMBClient(object): # pylint: disable=R0902 % (self._process.pid, self._process.poll(), json.dumps(error))) - time.sleep(wait/1000) + time.sleep(wait / 1000) self._output += stdout stdout = stdout.replace('\r', '').replace('\x08', '') return stdout diff --git a/satori/tunnel.py b/satori/tunnel.py index e40d583..43041b0 100644 --- a/satori/tunnel.py +++ b/satori/tunnel.py @@ -154,7 +154,7 @@ class Tunnel(object): # pylint: disable=R0902 else: self._tunnel_thread = threading.Thread( target=self._tunnel.serve_forever) - self.start() + self._tunnel_thread.start() # cooperative yield time.sleep(0) diff --git a/satori/utils.py b/satori/utils.py index 1cb8d74..42f9a0c 100644 --- a/satori/utils.py +++ b/satori/utils.py @@ -190,7 +190,7 @@ def get_source_definition(function, with_docstring=False): for line in definition.split('\n'): # pylint: disable=W0141 if not any(map(line.strip().startswith, ("@", "def"))): - line = " "*4 + line + line = " " * 4 + line definition_copy.append(line) return "\n".join(definition_copy).strip() diff --git a/tox.ini b/tox.ini index 34fb946..425cc46 100644 --- a/tox.ini +++ b/tox.ini @@ -42,6 +42,7 @@ commands = python setup.py build_sphinx downloadcache = ~/cache/pip [flake8] +ignore = H102 show-source = True exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,tools,*satori/contrib*,*.ropeproject,*satori/tests*,setup.py max-complexity = 12