diff --git a/monkey/common/data/post_breach_consts.py b/monkey/common/data/post_breach_consts.py index 1e36f9e20..27167228e 100644 --- a/monkey/common/data/post_breach_consts.py +++ b/monkey/common/data/post_breach_consts.py @@ -2,3 +2,4 @@ POST_BREACH_COMMUNICATE_AS_NEW_USER = "Communicate as new user" POST_BREACH_BACKDOOR_USER = "Backdoor user" POST_BREACH_FILE_EXECUTION = "File execution" POST_BREACH_SHELL_STARTUP_FILE_MODIFICATION = "Modify shell startup file" +POST_BREACH_HIDDEN_FILES = "Hide files and directories" diff --git a/monkey/infection_monkey/post_breach/actions/hide_files.py b/monkey/infection_monkey/post_breach/actions/hide_files.py new file mode 100644 index 000000000..da9caca6c --- /dev/null +++ b/monkey/infection_monkey/post_breach/actions/hide_files.py @@ -0,0 +1,37 @@ +from common.data.post_breach_consts import POST_BREACH_HIDDEN_FILES +from infection_monkey.post_breach.pba import PBA +from infection_monkey.telemetry.post_breach_telem import PostBreachTelem +from infection_monkey.utils.hidden_files import\ + get_commands_to_hide_files,\ + get_commands_to_hide_folders,\ + cleanup_hidden_files,\ + get_winAPI_to_hide_files +from infection_monkey.utils.environment import is_windows_os + + +HIDDEN_FSO_CREATION_COMMANDS = [get_commands_to_hide_files, + get_commands_to_hide_folders] + + +class HiddenFiles(PBA): + """ + This PBA attempts to create hidden files and folders. + """ + + def __init__(self): + super(HiddenFiles, self).__init__(name=POST_BREACH_HIDDEN_FILES) + + def run(self): + # create hidden files and folders + for function_to_get_commands in HIDDEN_FSO_CREATION_COMMANDS: + linux_cmds, windows_cmds = function_to_get_commands() + super(HiddenFiles, self).__init__(name=POST_BREACH_HIDDEN_FILES, + linux_cmd=' '.join(linux_cmds), + windows_cmd=windows_cmds) + super(HiddenFiles, self).run() + if is_windows_os(): # use winAPI + result, status = get_winAPI_to_hide_files() + PostBreachTelem(self, (result, status)).send() + + # cleanup hidden files and folders + cleanup_hidden_files(is_windows_os()) diff --git a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py index 095c7cb11..383a7ae4c 100644 --- a/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py +++ b/monkey/infection_monkey/post_breach/actions/modify_shell_startup_files.py @@ -21,7 +21,7 @@ class ModifyShellStartupFiles(PBA): class ShellStartupPBAGenerator(): def get_modify_shell_startup_pbas(): (cmds_for_linux, shell_startup_files_for_linux, usernames_for_linux),\ - (cmds_for_windows, shell_startup_files_per_user_for_windows) = get_commands_to_modify_shell_startup_files() + (cmds_for_windows, shell_startup_files_per_user_for_windows) = get_commands_to_modify_shell_startup_files() pbas = [] diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py index 8a1ed7246..b18dff768 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/linux/shell_startup_files_modification.py @@ -9,7 +9,7 @@ def get_linux_commands_to_modify_shell_startup_files(): HOME_DIR = "/home/" # get list of usernames - USERS = subprocess.check_output( + USERS = subprocess.check_output( # noqa: DUO116 "cut -d: -f1,3 /etc/passwd | egrep ':[0-9]{4}$' | cut -d: -f1", shell=True ).decode().split('\n')[:-1] diff --git a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py index a9116c221..68889f28c 100644 --- a/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py +++ b/monkey/infection_monkey/post_breach/shell_startup_files/windows/shell_startup_files_modification.py @@ -11,7 +11,7 @@ def get_windows_commands_to_modify_shell_startup_files(): SHELL_STARTUP_FILE_PATH_COMPONENTS = SHELL_STARTUP_FILE.split("\\") # get list of usernames - USERS = subprocess.check_output('dir C:\\Users /b', shell=True).decode().split("\r\n")[:-1] + USERS = subprocess.check_output('dir C:\\Users /b', shell=True).decode().split("\r\n")[:-1] # noqa: DUO116 STARTUP_FILES_PER_USER = ['\\'.join(SHELL_STARTUP_FILE_PATH_COMPONENTS[:2] + [user] + diff --git a/monkey/infection_monkey/utils/hidden_files.py b/monkey/infection_monkey/utils/hidden_files.py new file mode 100644 index 000000000..4fe520177 --- /dev/null +++ b/monkey/infection_monkey/utils/hidden_files.py @@ -0,0 +1,29 @@ +import subprocess +from infection_monkey.utils.linux.hidden_files import\ + get_linux_commands_to_hide_files,\ + get_linux_commands_to_hide_folders,\ + get_linux_commands_to_delete +from infection_monkey.utils.windows.hidden_files import\ + get_windows_commands_to_hide_files,\ + get_windows_commands_to_hide_folders,\ + get_winAPI_to_hide_files,\ + get_windows_commands_to_delete +from infection_monkey.utils.environment import is_windows_os + + +def get_commands_to_hide_files(): + linux_cmds = get_linux_commands_to_hide_files() + windows_cmds = get_windows_commands_to_hide_files() + return linux_cmds, windows_cmds + + +def get_commands_to_hide_folders(): + linux_cmds = get_linux_commands_to_hide_folders() + windows_cmds = get_windows_commands_to_hide_folders() + return linux_cmds, windows_cmds + + +def cleanup_hidden_files(is_windows=is_windows_os()): + subprocess.run(get_windows_commands_to_delete() if is_windows # noqa: DUO116 + else ' '.join(get_linux_commands_to_delete()), + shell=True) diff --git a/monkey/infection_monkey/utils/linux/hidden_files.py b/monkey/infection_monkey/utils/linux/hidden_files.py new file mode 100644 index 000000000..468318cf8 --- /dev/null +++ b/monkey/infection_monkey/utils/linux/hidden_files.py @@ -0,0 +1,34 @@ +HIDDEN_FILE = '$HOME/.monkey-hidden-file' +HIDDEN_FOLDER = '$HOME/.monkey-hidden-folder' + + +def get_linux_commands_to_hide_files(): + return [ + 'touch', # create file + HIDDEN_FILE, + '&&' + 'echo \"Successfully created hidden file: {}\" |'.format(HIDDEN_FILE), # output + 'tee -a', # and write to file + HIDDEN_FILE + ] + + +def get_linux_commands_to_hide_folders(): + return [ + 'mkdir', # make directory + HIDDEN_FOLDER, + '&& touch', # create file + '{}/{}'.format(HIDDEN_FOLDER, 'some-file'), # random file in hidden folder + '&& echo \"Successfully created hidden folder: {}\" |'.format(HIDDEN_FOLDER), # output + 'tee -a', # and write to file + '{}/{}'.format(HIDDEN_FOLDER, 'some-file') # random file in hidden folder + ] + + +def get_linux_commands_to_delete(): + return [ + 'rm', # remove + '-rf', # force delete recursively + HIDDEN_FILE, + HIDDEN_FOLDER + ] diff --git a/monkey/infection_monkey/utils/windows/hidden_files.py b/monkey/infection_monkey/utils/windows/hidden_files.py new file mode 100644 index 000000000..3ffad48f5 --- /dev/null +++ b/monkey/infection_monkey/utils/windows/hidden_files.py @@ -0,0 +1,81 @@ +import os + + +HOME_PATH = os.path.expanduser("~") + +HIDDEN_FILE = HOME_PATH + "\\monkey-hidden-file" +HIDDEN_FOLDER = HOME_PATH + "\\monkey-hidden-folder" +HIDDEN_FILE_WINAPI = HOME_PATH + "\\monkey-hidden-file-winAPI" + + +def get_windows_commands_to_hide_files(): + return [ + 'echo', + 'Successfully created hidden file: {}'.format(HIDDEN_FILE), # create empty file + '>', + HIDDEN_FILE, + '&&', + 'attrib', # change file attributes + '+h', # hidden attribute + '+s', # system attribute + HIDDEN_FILE, + '&&', + 'type', + HIDDEN_FILE + ] + + +def get_windows_commands_to_hide_folders(): + return [ + 'mkdir', + HIDDEN_FOLDER, # make directory + '&&', + 'attrib', + '+h', # hidden attribute + '+s', # system attribute + HIDDEN_FOLDER, # change file attributes + '&&', + 'echo', + 'Successfully created hidden folder: {}'.format(HIDDEN_FOLDER), + '>', + '{}\\{}'.format(HIDDEN_FOLDER, 'some-file'), + '&&', + 'type', + '{}\\{}'.format(HIDDEN_FOLDER, 'some-file') + ] + + +def get_winAPI_to_hide_files(): + import win32file + try: + fileAccess = win32file.GENERIC_READ | win32file.GENERIC_WRITE # read-write access + fileCreation = win32file.CREATE_ALWAYS # overwrite existing file + fileFlags = win32file.FILE_ATTRIBUTE_HIDDEN # make hidden + + hiddenFile = win32file.CreateFile(HIDDEN_FILE_WINAPI, + fileAccess, + 0, # sharing mode: 0 => can't be shared + None, # security attributes + fileCreation, + fileFlags, + 0) # template file + + return "Succesfully created hidden file: {}".format(HIDDEN_FILE_WINAPI), True + except Exception as err: + return str(err), False + + +def get_windows_commands_to_delete(): + return [ + 'powershell.exe', + 'del', # delete file + '-Force', + HIDDEN_FILE, + ',', + HIDDEN_FILE_WINAPI, + ';', + 'rmdir', # delete folder + '-Force', + '-Recurse', + HIDDEN_FOLDER + ] diff --git a/monkey/monkey_island/cc/services/attack/attack_report.py b/monkey/monkey_island/cc/services/attack/attack_report.py index b2ad3234a..16e6e51a1 100644 --- a/monkey/monkey_island/cc/services/attack/attack_report.py +++ b/monkey/monkey_island/cc/services/attack/attack_report.py @@ -4,7 +4,7 @@ from monkey_island.cc.models import Monkey from monkey_island.cc.services.attack.technique_reports import T1210, T1197, T1110, T1075, T1003, T1059, T1086, T1082 from monkey_island.cc.services.attack.technique_reports import T1145, T1105, T1065, T1035, T1129, T1106, T1107, T1188 from monkey_island.cc.services.attack.technique_reports import T1090, T1041, T1222, T1005, T1018, T1016, T1021, T1064 -from monkey_island.cc.services.attack.technique_reports import T1136, T1156, T1504 +from monkey_island.cc.services.attack.technique_reports import T1136, T1156, T1504, T1158 from monkey_island.cc.services.attack.attack_config import AttackConfig from monkey_island.cc.database import mongo from monkey_island.cc.services.reporting.report_generation_synchronisation import safe_generate_attack_report @@ -39,7 +39,8 @@ TECHNIQUES = {'T1210': T1210.T1210, 'T1064': T1064.T1064, 'T1136': T1136.T1136, 'T1156': T1156.T1156, - 'T1504': T1504.T1504 + 'T1504': T1504.T1504, + 'T1158': T1158.T1158 } REPORT_NAME = 'new_report' diff --git a/monkey/monkey_island/cc/services/attack/attack_schema.py b/monkey/monkey_island/cc/services/attack/attack_schema.py index 99e8dcfd2..c70ff2a70 100644 --- a/monkey/monkey_island/cc/services/attack/attack_schema.py +++ b/monkey/monkey_island/cc/services/attack/attack_schema.py @@ -90,6 +90,16 @@ SCHEMA = { "description": "Adversaries with a sufficient level of access " "may create a local system, domain, or cloud tenant account." }, + "T1158": { + "title": "Hidden files and directories", + "type": "bool", + "value": True, + "necessary": False, + "link": "https://attack.mitre.org/techniques/T1158", + "description": "Adversaries can hide files and folders on the system " + "and evade a typical user or system analysis that does not " + "incorporate investigation of hidden files." + }, "T1504": { "title": "PowerShell profile", "type": "bool", diff --git a/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py new file mode 100644 index 000000000..a90ee6e1f --- /dev/null +++ b/monkey/monkey_island/cc/services/attack/technique_reports/T1158.py @@ -0,0 +1,37 @@ +from monkey_island.cc.services.attack.technique_reports import AttackTechnique +from monkey_island.cc.database import mongo +from common.utils.attack_utils import ScanStatus +from common.data.post_breach_consts import POST_BREACH_HIDDEN_FILES + + +__author__ = "shreyamalviya" + + +class T1158(AttackTechnique): + tech_id = "T1158" + unscanned_msg = "Monkey did not try creating hidden files or folders." + scanned_msg = "Monkey tried creating hidden files and folders on the system but failed." + used_msg = "Monkey created hidden files and folders on the system." + + query = [{'$match': {'telem_category': 'post_breach', + 'data.name': POST_BREACH_HIDDEN_FILES}}, + {'$project': {'_id': 0, + 'machine': {'hostname': '$data.hostname', + 'ips': ['$data.ip']}, + 'result': '$data.result'}}] + + @staticmethod + def get_report_data(): + data = {'title': T1158.technique_title(), 'info': []} + + hidden_file_info = list(mongo.db.telemetry.aggregate(T1158.query)) + + status = [] + for pba_node in hidden_file_info: + status.append(pba_node['result'][1]) + status = (ScanStatus.USED.value if any(status) else ScanStatus.SCANNED.value)\ + if status else ScanStatus.UNSCANNED.value + + data.update(T1158.get_base_data_by_status(status)) + data.update({'info': hidden_file_info}) + return data diff --git a/monkey/monkey_island/cc/services/config_schema.py b/monkey/monkey_island/cc/services/config_schema.py index e37f3a75f..30425dc1d 100644 --- a/monkey/monkey_island/cc/services/config_schema.py +++ b/monkey/monkey_island/cc/services/config_schema.py @@ -167,6 +167,14 @@ SCHEMA = { ], "title": "Modify shell startup files", "attack_techniques": ["T1156", "T1504"] + }, + { + "type": "string", + "enum": [ + "HiddenFiles" + ], + "title": "Hidden files and directories", + "attack_techniques": ["T1158"] } ], }, @@ -388,7 +396,8 @@ SCHEMA = { "default": [ "BackdoorUser", "CommunicateAsNewUser", - "ModifyShellStartupFiles" + "ModifyShellStartupFiles", + "HiddenFiles" ], "description": "List of actions the Monkey will run post breach" }, diff --git a/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1158.js b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1158.js new file mode 100644 index 000000000..9fbf010de --- /dev/null +++ b/monkey/monkey_island/cc/ui/src/components/attack/techniques/T1158.js @@ -0,0 +1,45 @@ +import React from 'react'; +import ReactTable from 'react-table'; +import {renderMachineFromSystemData, ScanStatus} from './Helpers'; +import MitigationsComponent from './MitigationsComponent'; + +class T1158 extends React.Component { + + constructor(props) { + super(props); + } + + static getColumns() { + return ([{ + columns: [ + { Header: 'Machine', + id: 'machine', + accessor: x => renderMachineFromSystemData(x.machine), + style: {'whiteSpace': 'unset'}}, + { Header: 'Result', + id: 'result', + accessor: x => x.result, + style: {'whiteSpace': 'unset'}} + ] + }]) + } + + render() { + return ( +
+
{this.props.data.message}
+
+ {this.props.data.status === ScanStatus.USED ? + : ''} + +
+ ); + } +} + +export default T1158;