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 - The order and content of Monkey Island's initialization logging to give
clearer instructions to the user and avoid confusion. #1684 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 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 ### Removed
- VSFTPD exploiter. #1533 - VSFTPD exploiter. #1533
@ -48,6 +49,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
- ShellShock exploiter. #1733 - ShellShock exploiter. #1733
- ElasticGroovy exploiter. #1732 - ElasticGroovy exploiter. #1732
- T1082 attack technique report. #1754 - T1082 attack technique report. #1754
- 32-bit agents. #1675
### Fixed ### Fixed
- A bug in network map page that caused delay of telemetry log loading. #1545 - 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 ### 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 ## [1.13.0] - 2022-01-25
### Added ### Added
- A new exploiter that allows propagation via the Log4Shell vulnerability - A new exploiter that allows propagation via the Log4Shell vulnerability

View File

@ -42,9 +42,7 @@ download_monkey_agent_binaries() {
load_monkey_binary_config load_monkey_binary_config
mkdir -p "${island_binaries_path}" || handle_error 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}/${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}" 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_LATEST_RELEASE=$(get_latest_release "guardicore/monkey")
# Monkey binaries # 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_NAME="monkey-linux-64"
export LINUX_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/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_NAME="monkey-windows-64.exe"
export WINDOWS_64_BINARY_URL="https://github.com/guardicore/monkey/releases/download/$MONKEY_LATEST_RELEASE/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 # 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_URL = $MONKEY_DOWNLOAD_URL + "monkey-linux-64"
$LINUX_64_BINARY_PATH = "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_URL = $MONKEY_DOWNLOAD_URL + "monkey-windows-64.exe"
$WINDOWS_64_BINARY_PATH = "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"} branch=${2:-"develop"}
log_message "Branch selected: ${branch}" log_message "Branch selected: ${branch}"
if [[ ! -d "$monkey_home/monkey" ]]; then # If not already cloned 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 fi
# Create folders # Create folders
@ -161,20 +161,15 @@ agents=${3:-true}
if [ "$agents" = true ] ; then if [ "$agents" = true ] ; then
log_message "Downloading binaries" log_message "Downloading binaries"
if exists wget; then 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} ${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} wget -c -N -P ${ISLAND_BINARIES_PATH} ${WINDOWS_64_BINARY_URL}
else 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-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} curl -o ${ISLAND_BINARIES_PATH}\monkey-windows-64.exe ${WINDOWS_64_BINARY_URL}
fi fi
fi fi
# Allow them to be executed # 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" 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 # 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 fi
pushd "$ISLAND_PATH/cc/ui" || handle_error pushd "$ISLAND_PATH/cc/ui" || handle_error
npm install sass-loader node-sass webpack --save-dev npm ci
npm update
log_message "Generating front end" log_message "Generating front end"
npm run dist npm run dist

View File

@ -209,9 +209,7 @@ function Deploy-Windows([String] $monkey_home = (Get-Item -Path ".\").FullName,
"Adding binaries" "Adding binaries"
$binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries") $binaries = (Join-Path -Path $monkey_home -ChildPath $MONKEY_ISLAND_DIR | Join-Path -ChildPath "\cc\binaries")
New-Item -ItemType directory -path $binaries -ErrorAction SilentlyContinue 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($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)) $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. 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 ## The Monkey Island

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ class Mssql(ConfigTemplate):
config_values.update( config_values.update(
{ {
"basic.exploiters.exploiter_classes": ["MSSQLExploiter"], "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_network.scope.subnet_scan_list": ["10.2.2.16"],
"basic.credentials.exploit_password_list": [ "basic.credentials.exploit_password_list": [
"Password1!", "Password1!",

View File

@ -21,7 +21,7 @@ class PowerShell(ConfigTemplate):
"basic.credentials.exploit_password_list": ["Passw0rd!"], "basic.credentials.exploit_password_list": ["Passw0rd!"],
"basic_network.scope.depth": 2, "basic_network.scope.depth": 2,
"basic.credentials.exploit_user_list": ["m0nk3y", "m0nk3y-user"], "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.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [], "internal.network.tcp_scanner.tcp_target_ports": [],
"internal.exploits.exploit_ntlm_hash_list": [ "internal.exploits.exploit_ntlm_hash_list": [

View File

@ -14,7 +14,7 @@ class PowerShellCredentialsReuse(ConfigTemplate):
"10.2.3.46", "10.2.3.46",
], ],
"basic_network.scope.depth": 2, "basic_network.scope.depth": 2,
"internal.classes.finger_classes": ["PingScanner"], "internal.classes.finger_classes": [],
"internal.network.tcp_scanner.HTTP_PORTS": [], "internal.network.tcp_scanner.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_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_network.scope.subnet_scan_list": ["10.2.2.14", "10.2.2.15"],
"basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], "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.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [445], "internal.network.tcp_scanner.tcp_target_ports": [445],
"monkey.system_info.system_info_collector_classes": [ "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_network.scope.subnet_scan_list": ["10.2.2.15"],
"basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"], "basic.credentials.exploit_password_list": ["Password1!", "Ivrrw5zEzs"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], "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.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [445], "internal.network.tcp_scanner.tcp_target_ports": [445],
"internal.classes.exploits.exploit_ntlm_hash_list": [ "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.credentials.exploit_password_list": ["Password1!", "12345678", "^NgDvY59~8"],
"basic_network.scope.depth": 2, "basic_network.scope.depth": 2,
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], "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.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [22], "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"], "basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
"internal.classes.finger_classes": [ "internal.classes.finger_classes": [
"SSHFinger", "SSHFinger",
"PingScanner",
"HTTPFinger", "HTTPFinger",
"SMBFinger", "SMBFinger",
], ],

View File

@ -13,7 +13,7 @@ class WmiPth(ConfigTemplate):
"basic_network.scope.subnet_scan_list": ["10.2.2.15"], "basic_network.scope.subnet_scan_list": ["10.2.2.15"],
"basic.credentials.exploit_password_list": ["Password1!"], "basic.credentials.exploit_password_list": ["Password1!"],
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"], "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.HTTP_PORTS": [],
"internal.network.tcp_scanner.tcp_target_ports": [135], "internal.network.tcp_scanner.tcp_target_ports": [135],
"internal.exploits.exploit_ntlm_hash_list": [ "internal.exploits.exploit_ntlm_hash_list": [

View File

@ -89,7 +89,6 @@ class Configuration(object):
dropper_set_date = True dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll" dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = "/bin/sh" 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_win_64 = r"C:\Windows\temp\monkey64.exe"
dropper_target_path_linux = "/tmp/monkey" dropper_target_path_linux = "/tmp/monkey"

View File

@ -8,7 +8,6 @@ from urllib.parse import urljoin
import requests import requests
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
import infection_monkey.monkeyfs as monkeyfs
import infection_monkey.tunnel as tunnel import infection_monkey.tunnel as tunnel
from common.common_consts.api_url_consts import T1216_PBA_FILE_DOWNLOAD_PATH 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 from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
@ -259,22 +258,6 @@ class ControlClient(object):
ControlClient.load_control_config() ControlClient.load_control_config()
return not WormConfiguration.alive 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 @staticmethod
def spoof_host_os_info(is_windows, is_32bit): def spoof_host_os_info(is_windows, is_32bit):
if is_windows: if is_windows:
@ -292,70 +275,6 @@ class ControlClient(object):
return {"os": {"type": os, "machine": arch}} 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 @staticmethod
def create_control_tunnel(): def create_control_tunnel():
if not WormConfiguration.current_server: if not WormConfiguration.current_server:

View File

@ -19,7 +19,6 @@
"dropper_log_path_windows": "%temp%\\~df1562.tmp", "dropper_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_log_path_linux": "/tmp/user-1562", "dropper_log_path_linux": "/tmp/user-1562",
"dropper_set_date": true, "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_win_64": "C:\\Windows\\temp\\monkey64.exe",
"dropper_target_path_linux": "/tmp/monkey", "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.i_puppet import ExploiterResultData
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from . import IAgentRepository
logger = logging.getLogger(__name__) 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 # 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.host = host
self.telemetry_messenger = telemetry_messenger self.telemetry_messenger = telemetry_messenger
self.agent_repository = agent_repository
self.options = options self.options = options
self.pre_exploit() 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 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.model import VictimHost
from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger from infection_monkey.telemetry.messengers.i_telemetry_messenger import ITelemetryMessenger
from . import IAgentRepository
from .HostExploiter import HostExploiter from .HostExploiter import HostExploiter
@ -16,17 +17,28 @@ class ExploiterWrapper:
class Inner: class Inner:
def __init__( 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._exploit_class = exploit_class
self._telemetry_messenger = telemetry_messenger self._telemetry_messenger = telemetry_messenger
self._agent_repository = agent_repository
def exploit_host(self, host: VictimHost, options: Dict): def exploit_host(self, host: VictimHost, options: Dict):
exploiter = self._exploit_class() 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._telemetry_messenger = telemetry_messenger
self._agent_repository = agent_repository
def wrap(self, exploit_class: Type[HostExploiter]): 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) self.add_vulnerable_urls(urls, True)
if not self.vulnerable_urls: if not self.vulnerable_urls:
return self.exploit_result 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: 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): if self.exploit(self.vulnerable_urls[0], command):
self.add_executed_cmd(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 import os
from typing import List, Optional from typing import List, Optional
import infection_monkey.monkeyfs as monkeyfs
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.consts import WIN_ARCH_32 from infection_monkey.exploit.consts import WIN_ARCH_32
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
@ -22,7 +21,7 @@ from infection_monkey.exploit.powershell_utils.powershell_client import (
IPowerShellClient, IPowerShellClient,
PowerShellClient, 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.model import DROPPER_ARG, RUN_MONKEY, VictimHost
from infection_monkey.utils.commands import build_monkey_commandline from infection_monkey.utils.commands import build_monkey_commandline
from infection_monkey.utils.environment import is_windows_os from infection_monkey.utils.environment import is_windows_os
@ -186,11 +185,15 @@ class PowerShellExploiter(HostExploiter):
return is_monkey_copy_successful return is_monkey_copy_successful
def _write_virtual_file_to_local_path(self) -> None: 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) 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 monkeyfs.open(monkey_fs_path) as monkey_virtual_file:
with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file: with open(TEMP_MONKEY_BINARY_FILEPATH, "wb") as monkey_local_file:
monkey_local_file.write(monkey_virtual_file.read()) monkey_local_file.write(monkey_virtual_file.read())
"""
pass
def _run_monkey_executable_on_victim(self, executable_path) -> None: def _run_monkey_executable_on_victim(self, executable_path) -> None:
monkey_execution_command = build_monkey_execution_command( monkey_execution_command = build_monkey_execution_command(

View File

@ -4,12 +4,11 @@ import time
import paramiko import paramiko
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from common.utils.exploit_enum import ExploitType from common.utils.exploit_enum import ExploitType
from infection_monkey.exploit.HostExploiter import HostExploiter 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.i_puppet import ExploiterResultData
from infection_monkey.model import MONKEY_ARG from infection_monkey.model import MONKEY_ARG
from infection_monkey.network.tools import check_tcp_port, get_interface_to_target 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") _, stdout, _ = ssh.exec_command("uname -o")
uname_os = stdout.read().lower().strip().decode() uname_os = stdout.read().lower().strip().decode()
if "linux" in uname_os: if "linux" in uname_os:
self.host.os["type"] = "linux"
self.exploit_result.os = "linux" self.exploit_result.os = "linux"
else: else:
self.exploit_result.error_message = f"SSH Skipping unknown os: {uname_os}" 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) logger.error(self.exploit_result.error_message)
return self.exploit_result return self.exploit_result
if not self.host.os.get("machine"): agent_binary_file_object = self.agent_repository.get_agent_binary(self.exploit_result.os)
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)
src_path = get_target_monkey(self.host) if not agent_binary_file_object:
if not src_path:
self.exploit_result.error_message = ( self.exploit_result.error_message = (
f"Can't find suitable monkey executable for host {self.host}" f"Can't find suitable monkey executable for host {self.host}"
) )
@ -172,26 +158,17 @@ class SSHExploiter(HostExploiter):
return self.exploit_result return self.exploit_result
try: try:
ftp = ssh.open_sftp() with ssh.open_sftp() as ftp:
self._update_timestamp = time.time() self._update_timestamp = time.time()
with monkeyfs.open(src_path) as file_obj:
ftp.putfo( ftp.putfo(
file_obj, agent_binary_file_object,
self.options["dropper_target_path_linux"], self.options["dropper_target_path_linux"],
file_size=monkeyfs.getsize(src_path), file_size=len(agent_binary_file_object.getbuffer()),
callback=self.log_transfer, callback=self.log_transfer,
) )
ftp.chmod(self.options["dropper_target_path_linux"], 0o777) self._set_executable_bit_on_agent_binary(ftp)
status = ScanStatus.USED status = ScanStatus.USED
self.telemetry_messenger.send_telemetry(
T1222Telem(
ScanStatus.USED,
"chmod 0777 %s" % self.options["dropper_target_path_linux"],
self.host,
)
)
ftp.close()
except Exception as exc: except Exception as exc:
self.exploit_result.error_message = ( self.exploit_result.error_message = (
f"Error uploading file into victim {self.host}: ({exc})" f"Error uploading file into victim {self.host}: ({exc})"
@ -201,7 +178,10 @@ class SSHExploiter(HostExploiter):
self.telemetry_messenger.send_telemetry( self.telemetry_messenger.send_telemetry(
T1105Telem( 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: if status == ScanStatus.SCANNED:
@ -233,3 +213,13 @@ class SSHExploiter(HostExploiter):
logger.error(self.exploit_result.error_message) logger.error(self.exploit_result.error_message)
return self.exploit_result 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): def get_target_monkey(host):
import platform raise NotImplementedError("get_target_monkey() has been retired. Use IAgentRepository instead.")
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
def get_target_monkey_by_os(is_windows, is_32bit): def get_target_monkey_by_os(is_windows, is_32bit):
from infection_monkey.control import ControlClient raise NotImplementedError(
"get_target_monkey_by_os() has been retired. Use IAgentRepository instead."
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit) )
def get_monkey_depth(): def get_monkey_depth():
@ -54,7 +29,7 @@ def get_monkey_depth():
def get_monkey_dest_path(url_to_monkey): def get_monkey_dest_path(url_to_monkey):
""" """
Gets destination path from monkey's source url. 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 :return: Corresponding monkey path from configuration
""" """
from infection_monkey.config import WormConfiguration from infection_monkey.config import WormConfiguration
@ -65,8 +40,6 @@ def get_monkey_dest_path(url_to_monkey):
try: try:
if "linux" in url_to_monkey: if "linux" in url_to_monkey:
return WormConfiguration.dropper_target_path_linux 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: elif "windows-64" in url_to_monkey:
return WormConfiguration.dropper_target_path_win_64 return WormConfiguration.dropper_target_path_win_64
else: 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.firewall import app as firewall
from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.info import get_free_tcp_port
from infection_monkey.network.tools import get_interface_to_target 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__) logger = logging.getLogger(__name__)
class HTTPTools(object): 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 @staticmethod
def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None): def try_create_locked_transfer(host, src_path, local_ip=None, local_port=None):
http_path, http_thread = HTTPTools.create_locked_transfer( http_path, http_thread = HTTPTools.create_locked_transfer(
@ -49,7 +28,9 @@ class HTTPTools(object):
return http_path, http_thread return http_path, http_thread
@staticmethod @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 Create http server for file transfer with a lock
:param host: Variable with target's information :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") logger.error("Firewall is not allowed to listen for incomming ports. Aborting")
return None, None 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() httpd.start()
lock.acquire() lock.acquire()
return ( return (
"http://%s:%s/%s" "http://%s:%s/%s" % (local_ip, local_port, urllib.parse.quote(host.os["type"])),
% (local_ip, local_port, urllib.parse.quote(os.path.basename(src_path))),
httpd, 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.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMB_DIALECT, SMBConnection from impacket.smbconnection import SMB_DIALECT, SMBConnection
import infection_monkey.monkeyfs as monkeyfs
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from infection_monkey.config import Configuration from infection_monkey.config import Configuration
from infection_monkey.network.tools import get_interface_to_target from infection_monkey.network.tools import get_interface_to_target
@ -20,7 +19,8 @@ class SmbTools(object):
def copy_file( def copy_file(
host, src_path, dst_path, username, password, lm_hash="", ntlm_hash="", timeout=60 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( smb, dialect = SmbTools.new_smb_connection(
host, username, password, lm_hash, ntlm_hash, timeout 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)) remote_full_path = ntpath.join(share_path, remote_path.strip(ntpath.sep))
try: try:
# monkeyfs has been removed. Fix this in issue #1741
"""
with monkeyfs.open(src_path, "rb") as source_file: with monkeyfs.open(src_path, "rb") as source_file:
# make sure of the timeout # make sure of the timeout
smb.setTimeout(timeout) smb.setTimeout(timeout)
smb.putFile(share_name, remote_path, source_file.read) smb.putFile(share_name, remote_path, source_file.read)
"""
file_uploaded = True file_uploaded = True
T1105Telem( T1105Telem(

View File

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

View File

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

View File

@ -16,7 +16,7 @@ from infection_monkey.credential_collectors import (
MimikatzCredentialCollector, MimikatzCredentialCollector,
SSHCredentialCollector, 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.hadoop import HadoopExploiter
from infection_monkey.exploit.sshexec import SSHExploiter from infection_monkey.exploit.sshexec import SSHExploiter
from infection_monkey.i_puppet import IPuppet, PluginType 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_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.monkey_log_path import get_monkey_log_path
from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers from infection_monkey.utils.signal_handler import register_signal_handlers, reset_signal_handlers
from infection_monkey.windows_upgrader import WindowsUpgrader
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -101,11 +100,6 @@ class InfectionMonkey:
logger.info("The Monkey Island has instructed this agent to stop") logger.info("The Monkey Island has instructed this agent to stop")
return 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._setup()
self._master.start() self._master.start()
@ -147,16 +141,6 @@ class InfectionMonkey:
return False 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): def _setup(self):
logger.debug("Starting the setup phase.") logger.debug("Starting the setup phase.")
@ -216,7 +200,10 @@ class InfectionMonkey:
puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER) puppet.load_plugin("smb", SMBFingerprinter(), PluginType.FINGERPRINTER)
puppet.load_plugin("ssh", SSHFingerprinter(), 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( puppet.load_plugin(
"SSHExploiter", "SSHExploiter",
@ -252,10 +239,6 @@ class InfectionMonkey:
logger.info("Monkey cleanup started") logger.info("Monkey cleanup started")
self._wait_for_exploited_machine_connection() self._wait_for_exploited_machine_connection()
try: 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: if self._master:
self._master.cleanup() self._master.cleanup()

View File

@ -43,10 +43,6 @@ def is_windows():
return platform.system().find("Windows") >= 0 return platform.system().find("Windows") >= 0
def is_32_bit():
return sys.maxsize <= 2 ** 32
def get_bin_folder(): def get_bin_folder():
return os.path.join('.', 'bin') return os.path.join('.', 'bin')
@ -73,15 +69,10 @@ def get_hidden_imports():
def get_monkey_filename(): def get_monkey_filename():
name = 'monkey-' name = 'monkey-'
if is_windows(): if is_windows():
name = name + "windows-" name = name + "windows-64.exe"
else: else:
name = name + "linux-" name = name + "linux-64"
if is_32_bit():
name = name + "32"
else:
name = name + "64"
if is_windows():
name = name + ".exe"
return name 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: 1. To build the final exe:
- `cd monkey\infection_monkey` - `cd monkey\infection_monkey`
- `build_windows.bat` - `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 ## Linux
@ -51,7 +52,7 @@ Tested on Ubuntu 16.04.
- `chmod +x build_linux.sh` - `chmod +x build_linux.sh`
- `pipenv run ./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 ### Troubleshooting

View File

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

View File

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

View File

@ -1,18 +1,5 @@
import os
import struct
import sys 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(): def is_windows_os():
return sys.platform.startswith("win") 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(ConfigurationImport, "/api/configuration/import")
api.add_resource( api.add_resource(
MonkeyDownload, MonkeyDownload,
"/api/monkey/download", "/api/monkey/download/<string:host_os>",
"/api/monkey/download/<string:path>",
) )
api.add_resource(NetMap, "/api/netmap") api.add_resource(NetMap, "/api/netmap")
api.add_resource(Edge, "/api/netmap/edge") api.add_resource(Edge, "/api/netmap/edge")

View File

@ -1,101 +1,34 @@
import hashlib import hashlib
import json
import logging import logging
import os from pathlib import Path
import flask_restful 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 from monkey_island.cc.server_utils.consts import MONKEY_ISLAND_ABS_PATH
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
MONKEY_DOWNLOADS = [ AGENTS = {
{ "linux": "monkey-linux-64",
"type": "linux", "windows": "monkey-windows-64.exe",
"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",
},
]
def get_monkey_executable(host_os, machine): class UnsupportedOSError(Exception):
for download in MONKEY_DOWNLOADS: pass
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 MonkeyDownload(flask_restful.Resource): class MonkeyDownload(flask_restful.Resource):
# Used by monkey. can't secure. # Used by monkey. can't secure.
def get(self, path): def get(self, host_os):
return send_from_directory(os.path.join(MONKEY_ISLAND_ABS_PATH, "cc", "binaries"), path) try:
path = get_agent_executable_path(host_os)
# Used by monkey. can't secure. return send_from_directory(path.parent, path.name)
def post(self): except UnsupportedOSError as ex:
host_json = json.loads(request.data) logger.error(ex)
host_os = host_json.get("os") return make_response({"error": str(ex)}, 404)
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
@staticmethod @staticmethod
def log_executable_hashes(): 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 Logs all the hashes of the monkey executables for debugging ease (can check what Monkey
version you have etc.). version you have etc.).
""" """
filenames = set([x["filename"] for x in MONKEY_DOWNLOADS]) filenames = set(AGENTS.values())
for filename in filenames: for filename in filenames:
filepath = MonkeyDownload.get_executable_full_path(filename) filepath = get_executable_full_path(filename)
if os.path.isfile(filepath): if filepath.is_file():
with open(filepath, "rb") as monkey_exec_file: with open(filepath, "rb") as monkey_exec_file:
file_contents = monkey_exec_file.read() file_contents = monkey_exec_file.read()
logger.debug( file_sha256_hash = hashlib.sha256(file_contents).hexdigest()
"{} hashes:\nSHA-256 {}".format( logger.debug(f"{filename} SHA-256 hash: {file_sha256_hash}")
filename, hashlib.sha256(file_contents).hexdigest()
)
)
else: 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 [ for dropper_target in [
"dropper_target_path_linux", "dropper_target_path_linux",
"dropper_target_path_win_32",
"dropper_target_path_win_64", "dropper_target_path_win_64",
]: ]:
exploit_options[dropper_target] = config.get(dropper_target, "") 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 " "description": "Determines where should the dropper place the monkey on a "
"Linux machine", "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": { "dropper_target_path_win_64": {
"title": "Dropper target path on Windows (64bit)", "title": "Dropper target path on Windows (64bit)",
"type": "string", "type": "string",

View File

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

View File

@ -20,9 +20,7 @@ const getContents = (props) => {
const osTypes = { const osTypes = {
[OS_TYPES.WINDOWS_64]: 'Windows 64bit', [OS_TYPES.WINDOWS_64]: 'Windows 64bit',
[OS_TYPES.WINDOWS_32]: 'Windows 32bit', [OS_TYPES.LINUX_64]: 'Linux 64bit'
[OS_TYPES.LINUX_64]: 'Linux 64bit',
[OS_TYPES.LINUX_32]: 'Linux 32bit'
} }
const [osType, setOsType] = useState(OS_TYPES.WINDOWS_64); const [osType, setOsType] = useState(OS_TYPES.WINDOWS_64);
@ -48,11 +46,11 @@ const getContents = (props) => {
} }
function generateCommands() { function generateCommands() {
if (osType === OS_TYPES.WINDOWS_64 || osType === OS_TYPES.WINDOWS_32) { if (osType === OS_TYPES.WINDOWS_64) {
return [{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, osType, customUsername)}] return [{type: 'Powershell', command: GenerateLocalWindowsPowershell(selectedIp, customUsername)}]
} else { } else {
return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, osType, customUsername)}, return [{type: 'CURL', command: GenerateLocalLinuxCurl(selectedIp, customUsername)},
{type: 'WGET', command: GenerateLocalLinuxWget(selectedIp, osType, 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 {faLaptopCode} from '@fortawesome/free-solid-svg-icons/faLaptopCode';
import InlineSelection from '../../ui-components/inline-selection/InlineSelection'; import InlineSelection from '../../ui-components/inline-selection/InlineSelection';
import {cloneDeep} from 'lodash'; 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 RunOnIslandButton from './RunOnIslandButton';
import AWSRunButton from './RunOnAWS/AWSRunButton'; import AWSRunButton from './RunOnAWS/AWSRunButton';

View File

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

View File

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

View File

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

View File

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

View File

@ -46,7 +46,7 @@ function aggregateMultipleResultsPba(results) {
} }
function modifyProcessListCollectionResult(result) { 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 // 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) 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-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 monkey-windows-64.exe - monkey binary for windows 64bit
1. Install npm 1. Install npm
@ -95,15 +93,10 @@
monkey-linux-64 - monkey binary for linux 64bit 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 monkey-windows-64.exe - monkey binary for windows 64bit
Also, if you're going to run monkeys on local machine execute: 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-64`
- `chmod 755 ./monkey_island/cc/binaries/monkey-linux-32`
1. Setup MongoDB (Use one of the two following options): 1. Setup MongoDB (Use one of the two following options):
- Download MongoDB and extract it to monkey/monkey_island/bin/mongodb: - 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_log_path_windows": "%temp%\\~df1562.tmp",
"dropper_set_date": true, "dropper_set_date": true,
"dropper_target_path_linux": "/tmp/monkey", "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", "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe",
"exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"], "exploit_lm_hash_list": ["lm_hash_1", "lm_hash_2"],
"exploit_ntlm_hash_list": ["nt_hash_1", "nt_hash_2", "nt_hash_3"], "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_windows": "%windir%\\system32\\kernel32.dll",
"dropper_date_reference_path_linux": "/bin/sh", "dropper_date_reference_path_linux": "/bin/sh",
"dropper_target_path_linux": "/tmp/monkey", "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" "dropper_target_path_win_64": "C:\\Windows\\temp\\monkey64.exe"
}, },
"logging": { "logging": {

View File

@ -26,7 +26,6 @@ Config = namedtuple(
"exploit_password_list", "exploit_password_list",
"exploit_lm_hash_list", "exploit_lm_hash_list",
"exploit_ntlm_hash_list", "exploit_ntlm_hash_list",
"dropper_target_path_win_32",
"dropper_target_path_win_64", "dropper_target_path_win_64",
], ],
) )
@ -52,7 +51,8 @@ def powershell_exploiter(monkeypatch):
monkeypatch.setattr(powershell, "AuthenticationError", AuthenticationErrorForTests) monkeypatch.setattr(powershell, "AuthenticationError", AuthenticationErrorForTests)
monkeypatch.setattr(powershell, "is_windows_os", lambda: True) 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 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) monkeypatch.setattr(pe, "_write_virtual_file_to_local_path", lambda: None)
return pe return pe

View File

@ -16,7 +16,6 @@ HOST_AS_DICT = {
"os": {}, "os": {},
"services": {}, "services": {},
"icmp": False, "icmp": False,
"monkey_exe": None,
"default_tunnel": None, "default_tunnel": None,
"default_server": None, "default_server": None,
} }
@ -37,7 +36,13 @@ ERROR_MSG = "failed because yolo"
@pytest.fixture @pytest.fixture
def exploit_telem_test_instance(): 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): def test_exploit_telem_send(exploit_telem_test_instance, spy_send_telemetry):

View File

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

View File

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