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