Add module for trivial SMB operations

This commit is contained in:
Oleksii Shevchuk 2017-07-05 00:00:56 +03:00
parent a2f58cc33f
commit 2bcef8df65
3 changed files with 369 additions and 17 deletions

View File

@ -33,6 +33,7 @@ class PSExec(PupyModule):
self.arg_parser.add_argument("-H", metavar="HASH", dest='hash', default='', help='NTLM hash')
self.arg_parser.add_argument("-d", metavar="DOMAIN", dest='domain', default="WORKGROUP", help="Domain name (default WORKGROUP)")
self.arg_parser.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default C$)")
self.arg_parser.add_argument("-S", dest='noout', action='store_true', help="Do not wait for command output")
self.arg_parser.add_argument("-T", metavar="TIMEOUT", dest='timeout', default=30, type=int,
help="Try to set this timeout")
self.arg_parser.add_argument("--port", dest='port', type=int, choices={139, 445}, default=445, help="SMB port (default 445)")
@ -148,7 +149,7 @@ class PSExec(PupyModule):
host, args.port, args.user, args.passwd, args.hash,
args.share, file_to_upload, remote_path, dst_folder,
args.command, args.domain, args.execm, args.codepage,
args.timeout
args.timeout, args.noout
)
if args.ps1_oneliner:

208
pupy/modules/smb.py Normal file
View File

@ -0,0 +1,208 @@
# -*- coding: utf-8 -*-
from pupylib.PupyModule import *
import os
import ntpath
from pupylib.utils.rpyc_utils import obtain
__class_name__="SMB"
@config(cat="admin")
class SMB(PupyModule):
''' Copy files via SMB protocol '''
max_clients = 1
dependencies = [ 'impacket', 'pupyutils.psexec' ]
def init_argparse(self):
self.arg_parser = PupyArgumentParser(prog='smbcp', description=self.__doc__)
self.arg_parser.add_argument('-u', '--username', default='', help='Username')
self.arg_parser.add_argument('-P', '--port', default=445, type=int, help='Port')
self.arg_parser.add_argument('-p', '--password', default='', help='Password')
self.arg_parser.add_argument('-d', '--domain', default='', help='Domain')
self.arg_parser.add_argument('-H', '--hash', default='', help='NTLM hash')
self.arg_parser.add_argument('-T', '--timeout', default=30, type=int, help='Timeout')
commands = self.arg_parser.add_subparsers(dest="command")
cp = commands.add_parser('cp')
cp.add_argument('src', help='Source')
cp.add_argument('dst', help='Destination')
cp.set_defaults(func=self.cp)
ls = commands.add_parser('ls')
ls.add_argument('dst', help='Destination')
ls.set_defaults(func=self.ls)
rm = commands.add_parser('rm')
rm.add_argument('dst', help='Destination')
rm.set_defaults(func=self.rm)
mkdir = commands.add_parser('mkdir')
mkdir.add_argument('dst', help='Destination')
mkdir.set_defaults(func=self.mkdir)
rmdir = commands.add_parser('rmdir')
rmdir.add_argument('dst', help='Destination')
rmdir.set_defaults(func=self.rmdir)
shares = commands.add_parser('shares')
shares.add_argument('host', help='Host')
shares.set_defaults(func=self.shares)
def run(self, args):
args.func(args)
def get_ft(self, args, host):
return self.client.conn.modules['pupyutils.psexec'].FileTransfer(
host,
port=args.port, hash=args.hash,
username=args.username, password=args.password, domain=args.domain,
timeout=args.timeout
)
def shares(self, args):
host = args.host
host = host.replace('\\', '//')
if host.startswith('//'):
host = host[2:]
ft = self.get_ft(args, host)
if not ft.ok:
self.error(ft.error)
return
for share in obtain(ft.shares()):
self.log(share)
if not ft.ok:
self.error(ft.error)
def parse_netloc(self, line):
line = line.replace('\\', '/')
if not line.startswith('//'):
raise ValueError('Invalid network format')
remote = line[2:].split('/')
if len(remote) < 3 or not all(remote[:2]):
raise ValueError('Invalid network format')
return remote[0], remote[1], ntpath.normpath('\\'.join(remote[2:]))
def ls(self, args):
try:
host, share, path = self.parse_netloc(args.dst)
except Exception, e:
self.error(str(e))
return
if not path or path == '.':
path = '\\*'
ft = self.get_ft(args, host)
if not ft.ok:
self.error(ft.error)
return
for name, directory, size, ctime in obtain(ft.ls(share, path)):
self.log('%crw-rw-rw- %10d %s %s' % (
'd' if directory > 0 else '-', size, ctime, name
))
if not ft.ok:
self.error(ft.error)
def rm(self, args):
try:
host, share, path = self.parse_netloc(args.dst)
except Exception, e:
self.error(str(e))
return
ft = self.get_ft(args, host)
if not ft.ok:
self.error(ft.error)
return
ft.rm(share, path)
if not ft.ok:
self.error(ft.error)
def mkdir(self, args):
try:
host, share, path = self.parse_netloc(args.dst)
except Exception, e:
self.error(str(e))
return
ft = self.get_ft(args, host)
if not ft.ok:
self.error(ft.error)
return
ft.mkdir(share, path)
if not ft.ok:
self.error(ft.error)
def rmdir(self, args):
try:
host, share, path = self.parse_netloc(args.dst)
except Exception, e:
self.error(str(e))
return
ft = self.get_ft(args, host)
if not ft.ok:
self.error(ft.error)
return
ft.rmdir(share, path)
if not ft.ok:
self.error(ft.error)
def cp(self, args):
upload = False
src = args.src.replace('\\', '/')
dst = args.dst.replace('\\', '/')
if dst.startswith('//'):
upload = True
remote = dst
local = src
elif src.startswith('//'):
remote = src
local = dst
else:
self.error('Either src or dst should be network share in (\\HOST\SHARE\PATH) format')
return
try:
host, share, path = self.parse_netloc(remote)
except Exception, e:
self.error(e)
return
local = os.path.expandvars(local)
local = os.path.expanduser(local)
if upload and not os.path.isfile(local):
self.error('Source file {} not found'.format(local))
return
ft = self.get_ft(args, host)
if not ft.ok:
self.error(ft.error)
return
interval, timeout = self.client.conn._conn.root.getconn().get_pings()
self.client.conn._conn.root.getconn().set_pings(0, 0)
if upload:
with open(local, 'r+b') as source:
ft.put(source.read, share, path)
else:
with open(local, 'w+b') as destination:
ft.get(share, path, destination.write)
self.client.conn._conn.root.getconn().set_pings(interval, timeout)
if not ft.ok:
self.error(ft.error)

View File

@ -19,6 +19,142 @@ BATCH_FILENAME = ''.join(random.sample(string.ascii_letters, 10)) + '.bat'
SMBSERVER_DIR = ''.join(random.sample(string.ascii_letters, 10))
DUMMY_SHARE = 'TMP'
class FileTransfer(object):
def __init__(self, host, port=445, hash='', username='', password='', domain='', timeout=30):
self.__host = host
self.__nthash, self.__lmhash = '', ''
if hash and ':' in hash:
self.__lmhash, self.__nthash = hash.strip().split(':')
self.__port = port
self.__username = username
self.__password = password
self.__domain = domain
self.__timeout = timeout
self.__exception = None
try:
self.__conn = SMBConnection(
self.__host, self.__host,
None,
self.__port, timeout=self.__timeout
)
self.__conn.login(
self.__username,
self.__password,
self.__domain,
self.__lmhash,
self.__nthash
)
except Exception, e:
self.__exception = e
@property
def error(self):
return str(self.__exception)
@property
def ok(self):
return self.__exception is None
def shares(self):
try:
return [
x['shi1_netname'][:-1] for x in self.__conn.listShares()
]
except Exception, e:
self.__exception = e
return []
def ls(self, share, path):
try:
listing = []
for f in self.__conn.listPath(share, path):
if f.get_longname() in ('.', '..'):
continue
listing.append((
f.get_longname(), f.is_directory() > 0,
f.get_filesize(), time.ctime(float(f.get_mtime_epoch()))
))
return listing
except Exception, e:
self.__exception = e
return []
def rm(self, share, path):
try:
self.__conn.deleteFile(share, path)
except Exception, e:
self.__exception = e
def mkdir(self, share, path):
try:
self.__conn.createDirectory(share, path)
except Exception, e:
self.__exception = e
def rmdir(self, share, path):
try:
self.__conn.deleteDirectory(share, path)
except Exception, e:
self.__exception = e
def get(self, share, remote, local):
if not self.ok:
raise ValueError('Connection was not established')
try:
if type(local) in (str, unicode):
local = os.path.expandvars(local)
local = os.path.expanduser(local)
with open(local, 'w+b') as destination:
self.__conn.getFile(
share,
remote,
destination.write
)
else:
self.__conn.getFile(share, remote, local)
except Exception, e:
self.__exception = e
def put(self, local, share, remote):
if not self.ok:
raise ValueError('Connection was not established')
try:
if type(local) in (str, unicode):
local = os.path.expandvars(local)
local = os.path.expanduser(local)
if not os.path.exists(local):
raise ValueError('Local file ({}) does not exists'.format(local))
with open(local, 'rb') as source:
self.__conn.putFile(
share,
remote,
source.read
)
else:
self.__conn.putFile(share, remote, local)
except Exception, e:
self.__exception = e
def __del__(self):
if self.__conn:
try:
self.__conn.logoff()
except:
pass
class RemoteShellsmbexec(object):
def __init__(self, share, rpc, mode, serviceName, command, timeout):
self.__share = share
@ -85,11 +221,14 @@ class RemoteShellsmbexec(object):
fd.close()
os.unlink(SMBSERVER_DIR + '/' + self.__output_filename)
def execute_remote(self, data):
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
if self.__mode == 'SERVER':
command += ' & ' + self.__copyBack
command += ' & ' + 'del ' + self.__batchFile
def execute_remote(self, data, nooutput=False):
if nooutput:
command = data
else:
command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
if self.__mode == 'SERVER':
command += ' & ' + self.__copyBack
command += ' & ' + 'del ' + self.__batchFile
try:
resp = scmr.hRCreateServiceW(self.__scmr, self.__scHandle, self.__serviceName, self.__serviceName, lpBinaryPathName=command)
@ -101,12 +240,15 @@ class RemoteShellsmbexec(object):
scmr.hRStartServiceW(self.__scmr, service)
except:
pass
scmr.hRDeleteService(self.__scmr, service)
scmr.hRCloseServiceHandle(self.__scmr, service)
self.get_output()
def send_data(self, data):
self.execute_remote(data)
if not nooutput:
self.get_output()
def send_data(self, data, nooutput=False):
self.execute_remote(data, nooutput=nooutput)
result = self.__outputBuffer
self.__outputBuffer = ''
return result
@ -143,7 +285,7 @@ class CMDEXEC(object):
def service_generator(self, size=6, chars=string.ascii_uppercase):
return ''.join(random.choice(chars) for _ in range(size))
def run(self, addr):
def run(self, addr, nooutput):
result = ''
for protocol in self.__protocols:
protodef = CMDEXEC.KNOWN_PROTOCOLS[protocol]
@ -169,7 +311,7 @@ class CMDEXEC(object):
self.__share, rpctransport, self.__mode, self.__serviceName,
self.__command, self.__timeout
)
result = self.shell.send_data(self.__command)
result = self.shell.send_data(self.__command, nooutput=nooutput)
except SessionError as e:
if 'STATUS_SHARING_VIOLATION' in str(e):
@ -200,7 +342,7 @@ class WMIEXEC:
if hashes:
self.__lmhash, self.__nthash = hashes.split(':')
def run(self, addr, smbConnection):
def run(self, addr, smbConnection, nooutput):
result = ''
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)
@ -212,7 +354,7 @@ class WMIEXEC:
try:
self.shell = RemoteShellwmi(self.__share, win32Process, smbConnection)
result = self.shell.send_data(self.__command)
result = self.shell.send_data(self.__command, nooutput=nooutput)
except (Exception, KeyboardInterrupt), e:
traceback.print_exc()
dcom.disconnect()
@ -268,7 +410,8 @@ class RemoteShellwmi():
obj = self.__win32Process.Create(command, self.__pwd, None)
self.get_output()
def send_data(self, data):
def send_data(self, data, nooutput=False):
self.__noOutput = nooutput
self.execute_remote(data)
result = self.__outputBuffer
self.__outputBuffer = ''
@ -301,7 +444,7 @@ def upload_file(smbconn, host, src, share, dst):
def connect(host, port, user, passwd, hash, share, file_to_upload,
src_folder, dst_folder, command,
domain='workgroup', execm='smbexec', codepage='cp437', timeout=30):
domain='workgroup', execm='smbexec', codepage='cp437', timeout=30, nooutput=False):
try:
lmhash = ''
nthash = ''
@ -358,11 +501,11 @@ def connect(host, port, user, passwd, hash, share, file_to_upload,
'{}/SMB'.format(port), user, passwd,
domain, hash, share, command, timeout
)
result = executer.run(host)
result = executer.run(host, nooutput)
elif execm == 'wmi':
executer = WMIEXEC(command, user, passwd, domain, hash, share)
result = executer.run(host, smb)
result = executer.run(host, smb, nooutput)
if result:
print result.decode(codepage)