Move serviceinstall to satori

This change is to remove the dependency on a custom version of impacket.
This dependency was necessary because the serviceinstall example inside
impacket did not support custom service/executable names but always
generated a random name for both and a change of the example was very
unlikely to be merged upstream.

Further to that, two minor bugs were fixed
 - satori.smb.SMBClient.create_tunnel() was calling
   satori.tunnel.connect() which has been removed, this has been
   changed to now use the constructor: satori.tunnel.Tunnel()
 - satori.tunnel.Tunnel.serve_forever() was calling self.start
   instead of self.tunnel_thread.start()

Minor style fixes have also been made. Further to that, the
serviceinstall example is not provided under an Apache2 license and
therefore H102 needs to be ignored by tox.

Change-Id: I04f48bf08f9361833cb607faa74ece26a48ea02d
This commit is contained in:
Nico Engelen 2014-08-01 13:17:27 +01:00
parent 6cabc1773d
commit b778d8165a
7 changed files with 327 additions and 27 deletions

View File

@ -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

View File

@ -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,

303
satori/serviceinstall.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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