diff --git a/pupy/modules/psexec.py b/pupy/modules/psexec.py index 29faf7e9..70ebab4c 100644 --- a/pupy/modules/psexec.py +++ b/pupy/modules/psexec.py @@ -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: diff --git a/pupy/modules/smb.py b/pupy/modules/smb.py new file mode 100644 index 00000000..090d6d5b --- /dev/null +++ b/pupy/modules/smb.py @@ -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) diff --git a/pupy/packages/all/pupyutils/psexec.py b/pupy/packages/all/pupyutils/psexec.py index 9ca68b02..1d1a7e52 100644 --- a/pupy/packages/all/pupyutils/psexec.py +++ b/pupy/packages/all/pupyutils/psexec.py @@ -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)