mirror of https://github.com/n1nj4sec/pupy.git
Add module for trivial SMB operations
This commit is contained in:
parent
a2f58cc33f
commit
2bcef8df65
|
@ -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:
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue