From 62ec0ae086014cc2026c44528449f31b9a7b04a6 Mon Sep 17 00:00:00 2001 From: Mika Ayenson Date: Fri, 24 Mar 2023 16:53:37 -0400 Subject: [PATCH] [FR] Add new macOS RTAs for Endpoint Rules (#2632) --- rta/app_hijack.py | 46 +++++++ rta/at_job.py | 32 +++++ rta/background_process_from_tmp.py | 36 ++++++ rta/bash_cmdline_history.py | 40 ++++++ rta/bin/com.apple.ditto_and_spawn_arm | Bin 33722 -> 34026 bytes rta/bin/com.apple.ditto_and_spawn_intel | Bin 49712 -> 50016 bytes rta/bin/inject_arm.dylib | Bin 0 -> 33421 bytes rta/bin/inject_intel.dylib | Bin 0 -> 33000 bytes rta/cron_tab_file_create.py | 32 +++++ rta/curl_data_exfil.py | 40 ++++++ rta/curl_sus_payload.py | 39 ++++++ rta/dmg_create_in_tmp.py | 43 +++++++ rta/dylib_injection.py | 47 +++++++ rta/echo_tmp_file_create.py | 42 +++++++ rta/exec_cmd_non_executable_file.py | 30 +++++ rta/exec_from_mount.py | 36 ++++++ rta/exec_from_python.py | 41 ++++++ rta/exec_from_terminal.py | 40 ++++++ rta/exec_nohup.py | 42 +++++++ rta/exec_privhelper_tool.py | 43 +++++++ rta/exec_tclsh.py | 35 ++++++ rta/file_mod_via_chmod.py | 33 +++++ rta/hidden_file_mount.py | 41 ++++++ rta/hidden_plist.py | 32 +++++ rta/kext_load.py | 41 ++++++ rta/keychain_dump.py | 9 +- rta/launchd_load_plist.py | 57 +++++++++ rta/link_to_tmp.py | 37 ++++++ rta/openssl_file_drop.py | 41 ++++++ rta/path_passed_to_system.py | 40 ++++++ rta/pkg_install_chmod.py | 61 +++++++++ rta/shove_sip_bypass.py | 36 ++++++ rta/src/ditto_and_spawn.c | 88 +++++++++++++ rta/src/sleep.c | 22 ++++ rta/src/thread_injector_intel.c | 160 ++++++++++++++++++++++++ rta/src/thread_injector_m1.c | 154 +++++++++++++++++++++++ rta/tar_dylib.py | 39 ++++++ rta/unzip_to_tmp.py | 34 +++++ 38 files changed, 1587 insertions(+), 2 deletions(-) create mode 100644 rta/app_hijack.py create mode 100644 rta/at_job.py create mode 100644 rta/background_process_from_tmp.py create mode 100644 rta/bash_cmdline_history.py create mode 100755 rta/bin/inject_arm.dylib create mode 100755 rta/bin/inject_intel.dylib create mode 100644 rta/cron_tab_file_create.py create mode 100644 rta/curl_data_exfil.py create mode 100644 rta/curl_sus_payload.py create mode 100644 rta/dmg_create_in_tmp.py create mode 100644 rta/dylib_injection.py create mode 100644 rta/echo_tmp_file_create.py create mode 100644 rta/exec_cmd_non_executable_file.py create mode 100644 rta/exec_from_mount.py create mode 100644 rta/exec_from_python.py create mode 100644 rta/exec_from_terminal.py create mode 100644 rta/exec_nohup.py create mode 100644 rta/exec_privhelper_tool.py create mode 100644 rta/exec_tclsh.py create mode 100644 rta/file_mod_via_chmod.py create mode 100644 rta/hidden_file_mount.py create mode 100644 rta/hidden_plist.py create mode 100644 rta/kext_load.py create mode 100644 rta/launchd_load_plist.py create mode 100644 rta/link_to_tmp.py create mode 100644 rta/openssl_file_drop.py create mode 100644 rta/path_passed_to_system.py create mode 100644 rta/pkg_install_chmod.py create mode 100644 rta/shove_sip_bypass.py create mode 100644 rta/src/ditto_and_spawn.c create mode 100644 rta/src/sleep.c create mode 100644 rta/src/thread_injector_intel.c create mode 100644 rta/src/thread_injector_m1.c create mode 100644 rta/tar_dylib.py create mode 100644 rta/unzip_to_tmp.py 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 288756d22ed124343d1b8a159f36a01e09f3e612..15cdfa944f02355c90f5687b713783fedd0b6a3f 100755 GIT binary patch literal 34026 zcmeI5e{ft?701uZCe7wo8%U)_&#*(oOwT)>UI@tEH*?no&?62;= zv`v4pn3h(U!eO=EQ`kq2UWRTKxTK0D zv))<2I`1o2N-2?RqhT0vtG_#*>ngU_)neNVIx|?$x94{3fuSmf;fq_bKzMsjFWm2x zWAE?IIP3W(-!}IfMt^u$AnZ2+;hu=n3++wbXZy!z#D(=@8}^9(%jfVNh+*8{+SO{T z*|=eoeHtDo2vDn1RJ+j0!e>EI+yd#`LaJMepD~fT|^1?k5BnDsxLVDY3Nbt%;tJX$D>4xP#^s!(J1OF z?t|o)Z0uF$cNVohSYKDv$Ld0e4&k_H>W{~of`RTP|3J_Woy$@P`|<7rf2?`)hLu&v zUv9Ym7tg=_xfSTsA@-hX(5O*uW>4B(t~*ev{jrYHfc>$ZiF=ir`{C!_1K}|{9&X2W zv`fCgpG^aC%M3MssS$n!y6vU6p2lBfT8)0fHrzxf z-I`~fAkdHALe0s)V4hBi&Of2flskU9oM*an&**#0W&6qYX3OEHRxzY}b|q38d@iBH z)4zs|Y#;CA9P~8is(rwAcI|oi*bZOMqpt#X+@*=m28~v#ed3vFpYUOZ_o<%eNi8p< z9qu=l57lGd4EiqaT{8ye$tjm{_CK+2z@A3IKMnrME_jbk8;Voqf;{F+ZRU!{!RF+v z@cH3Vx|`RGVE!?j%W?2MhxLaundfGzz_bbsT$F^*^N6OSG})Xyj&(EWA482ht)G9? zIoHdW-?}>aq%-#^)Xe^{qYt4@b6kK8`_4XOeOFAdmtTPi$5L)DrS@L(ef>WC-om~* z=U?pWE`A;u9zcCHUwzJ8+`Cl1ZgKR7QL}Fa=P*ck2eh$^YwUO6_b!?rW#$#Mqy0)e ze!e={?$~ZaopvEU%bfVcdEmPA@RVkshq6`kF}Bv@B|60c~gLGM(ypDHt zWYB(3M+WV4J`HBsb|2cWJ7=JHV-CM#?Ilj^;0(^Y+~XYYW7988x)JZp`3}THLzxp7 z(U#<=9Y5Gc_FO-}8LGIMXi4He=rFFJcII=${m_%O3&5eI{;X`FaCEe>4{HnejMn zih6g2aXDL8y}Hw_FN;@_W5`~nTM<3Za|+l{UI}K zU9yy&lO54}BC(LyYSi0yM@^q)`fb};A6AFGA(JinBB78s>~EU1-+%-Zt0ePL010N_XyZCzL8bLO=)z0U;m+gn$qb0zyCt2mv7= z1cZPP5CTF#2nYcoAOwVf5D)@FKnMr{As_^VfDjM@LO=)z0U;m+gn$qb0zyCt2mv7= z1cZPP5CTF#2nYcoAOwVf5D)@FKnMr{As_^V!2e7@$BGWfZU}$t+|C7n28wDpV4KG9%Or zTc)qyGL2r-i)T?8k}DvKf@yGS1$rpt4e(M<^ngbrPXx@XL=k43{3^qP7i$CIXum}P z7ag&XLSbSreh%)!&*OXWxFgoTN{+Zja>)46i!}a1I$VKV2U&y$=VW00R!8UWwX@FO zbLa7c&9>g^=+(~nSD|B=r>cVdB?bC7iu7oaK2W4TQlukyOMyM)bScoEE7HGLq;oz9 z&IQ4FAUFpE$4?c6zdw(!*V94>=Z^Sq=3yG?h=$A0%_ulG1jCij%_uszPb$vXS(FR! zS5JZ1IGWirT8ut^3!bGCygeoON3;{N8Nvma20eO-LS*-R?5`&~ssTcyHyRC^jsAdT zMGRyKae~(y3(?69?=G4cT3>nKqruFJKchQ;+A$pd-Htb2{DW5i=2yP^+R77;O#Enb zE{mIn!6NadTN_-;ek0IrQ7thSq-TvoBq3eCvbTp6=;baZkuAO)Q delta 1237 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 R;71~*3em$Jr8gzSe*o?dSzrJF delta 1435 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 {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())