forked from p15670423/monkey
Agent: Inject copy_file callable into RansomwarePayload
In order to test certain conditions, our options are to either monkeypatch shutil.copyfile(), or inject a callable into the RansomwarePayload. Monkeypatching shutil.copyfile() could lead to issues down the road. For example, if the implementation of `_leave_readme()` is changed to no longer use copyfile(), a test that asserts that copyfile() has not been called will pass, even though a file may have been copied.
This commit is contained in:
parent
e1b08079f1
commit
f0e9109f64
|
@ -1,6 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
@ -477,7 +478,7 @@ class InfectionMonkey(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RansomwarePayload(
|
RansomwarePayload(
|
||||||
WormConfiguration.ransomware, batching_telemetry_messenger
|
WormConfiguration.ransomware, batching_telemetry_messenger, shutil.copyfile
|
||||||
).run_payload()
|
).run_payload()
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}")
|
LOG.error(f"An unexpected error occurred while running the ransomware payload: {ex}")
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import shutil
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import List, Optional, Tuple
|
from typing import Callable, List, Optional, Tuple
|
||||||
|
|
||||||
from common.utils.file_utils import InvalidPath, expand_path
|
from common.utils.file_utils import InvalidPath, expand_path
|
||||||
from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor
|
from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor
|
||||||
|
@ -22,7 +21,12 @@ README_DEST = "README.txt"
|
||||||
|
|
||||||
|
|
||||||
class RansomwarePayload:
|
class RansomwarePayload:
|
||||||
def __init__(self, config: dict, telemetry_messenger: ITelemetryMessenger):
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: dict,
|
||||||
|
telemetry_messenger: ITelemetryMessenger,
|
||||||
|
copy_file: Callable[[str, str], None],
|
||||||
|
):
|
||||||
LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}")
|
LOG.debug(f"Ransomware payload configuration:\n{pformat(config)}")
|
||||||
|
|
||||||
self._encryption_enabled = config["encryption"]["enabled"]
|
self._encryption_enabled = config["encryption"]["enabled"]
|
||||||
|
@ -34,6 +38,7 @@ class RansomwarePayload:
|
||||||
self._valid_file_extensions_for_encryption.discard(self._new_file_extension)
|
self._valid_file_extensions_for_encryption.discard(self._new_file_extension)
|
||||||
|
|
||||||
self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE)
|
self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE)
|
||||||
|
self._copy_file = copy_file
|
||||||
self._telemetry_messenger = telemetry_messenger
|
self._telemetry_messenger = telemetry_messenger
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -97,6 +102,6 @@ class RansomwarePayload:
|
||||||
LOG.info(f"Leaving a ransomware README file at {readme_dest_path}")
|
LOG.info(f"Leaving a ransomware README file at {readme_dest_path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
shutil.copyfile(README_SRC, readme_dest_path)
|
self._copy_file(README_SRC, readme_dest_path)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}")
|
LOG.warning(f"An error occurred while attempting to leave a README.txt file: {ex}")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
from pathlib import Path, PurePosixPath
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -44,12 +45,20 @@ def ransomware_payload_config(ransomware_target):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ransomware_payload(ransomware_payload_config, telemetry_messenger_spy):
|
def ransomware_payload(build_ransomware_payload, ransomware_payload_config):
|
||||||
return RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy)
|
return build_ransomware_payload(ransomware_payload_config)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def build_ransomware_payload(telemetry_messenger_spy):
|
||||||
|
def inner(config):
|
||||||
|
return RansomwarePayload(config, telemetry_messenger_spy, shutil.copyfile)
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
def test_env_variables_in_target_dir_resolved_linux(
|
def test_env_variables_in_target_dir_resolved_linux(
|
||||||
ransomware_payload_config, ransomware_target, telemetry_messenger_spy, patched_home_env
|
ransomware_payload_config, build_ransomware_payload, ransomware_target, patched_home_env
|
||||||
):
|
):
|
||||||
path_with_env_variable = "$HOME/ransomware_target"
|
path_with_env_variable = "$HOME/ransomware_target"
|
||||||
|
|
||||||
|
@ -58,7 +67,7 @@ def test_env_variables_in_target_dir_resolved_linux(
|
||||||
] = ransomware_payload_config["encryption"]["directories"][
|
] = ransomware_payload_config["encryption"]["directories"][
|
||||||
"windows_target_dir"
|
"windows_target_dir"
|
||||||
] = path_with_env_variable
|
] = path_with_env_variable
|
||||||
RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy).run_payload()
|
build_ransomware_payload(ransomware_payload_config).run_payload()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF))
|
hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF))
|
||||||
|
@ -152,11 +161,11 @@ def test_skip_already_encrypted_file(ransomware_target, ransomware_payload):
|
||||||
|
|
||||||
|
|
||||||
def test_encryption_skipped_if_configured_false(
|
def test_encryption_skipped_if_configured_false(
|
||||||
ransomware_payload_config, ransomware_target, telemetry_messenger_spy
|
build_ransomware_payload, ransomware_payload_config, ransomware_target
|
||||||
):
|
):
|
||||||
ransomware_payload_config["encryption"]["enabled"] = False
|
ransomware_payload_config["encryption"]["enabled"] = False
|
||||||
|
|
||||||
ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy)
|
ransomware_payload = build_ransomware_payload(ransomware_payload_config)
|
||||||
ransomware_payload.run_payload()
|
ransomware_payload.run_payload()
|
||||||
|
|
||||||
assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256
|
assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256
|
||||||
|
@ -164,13 +173,13 @@ def test_encryption_skipped_if_configured_false(
|
||||||
|
|
||||||
|
|
||||||
def test_encryption_skipped_if_no_directory(
|
def test_encryption_skipped_if_no_directory(
|
||||||
ransomware_payload_config, telemetry_messenger_spy, monkeypatch
|
build_ransomware_payload, ransomware_payload_config, telemetry_messenger_spy
|
||||||
):
|
):
|
||||||
ransomware_payload_config["encryption"]["enabled"] = True
|
ransomware_payload_config["encryption"]["enabled"] = True
|
||||||
ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = ""
|
ransomware_payload_config["encryption"]["directories"]["linux_target_dir"] = ""
|
||||||
ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = ""
|
ransomware_payload_config["encryption"]["directories"]["windows_target_dir"] = ""
|
||||||
|
|
||||||
ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy)
|
ransomware_payload = build_ransomware_payload(ransomware_payload_config)
|
||||||
ransomware_payload.run_payload()
|
ransomware_payload.run_payload()
|
||||||
assert len(telemetry_messenger_spy.telemetries) == 0
|
assert len(telemetry_messenger_spy.telemetries) == 0
|
||||||
|
|
||||||
|
@ -205,17 +214,17 @@ def test_telemetry_failure(monkeypatch, ransomware_payload, telemetry_messenger_
|
||||||
assert "No such file or directory" in telem_1.get_data()["files"][0]["error"]
|
assert "No such file or directory" in telem_1.get_data()["files"][0]["error"]
|
||||||
|
|
||||||
|
|
||||||
def test_readme_false(ransomware_payload_config, ransomware_target, telemetry_messenger_spy):
|
def test_readme_false(build_ransomware_payload, ransomware_payload_config, ransomware_target):
|
||||||
ransomware_payload_config["other_behaviors"]["readme"] = False
|
ransomware_payload_config["other_behaviors"]["readme"] = False
|
||||||
ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy)
|
ransomware_payload = build_ransomware_payload(ransomware_payload_config)
|
||||||
|
|
||||||
ransomware_payload.run_payload()
|
ransomware_payload.run_payload()
|
||||||
assert not Path(ransomware_target / README_DEST).exists()
|
assert not Path(ransomware_target / README_DEST).exists()
|
||||||
|
|
||||||
|
|
||||||
def test_readme_true(ransomware_payload_config, ransomware_target, telemetry_messenger_spy):
|
def test_readme_true(build_ransomware_payload, ransomware_payload_config, ransomware_target):
|
||||||
ransomware_payload_config["other_behaviors"]["readme"] = True
|
ransomware_payload_config["other_behaviors"]["readme"] = True
|
||||||
ransomware_payload = RansomwarePayload(ransomware_payload_config, telemetry_messenger_spy)
|
ransomware_payload = build_ransomware_payload(ransomware_payload_config)
|
||||||
|
|
||||||
ransomware_payload.run_payload()
|
ransomware_payload.run_payload()
|
||||||
assert Path(ransomware_target / README_DEST).exists()
|
assert Path(ransomware_target / README_DEST).exists()
|
||||||
|
|
Loading…
Reference in New Issue