diff --git a/.gitignore b/.gitignore index 4960139a0..c60473c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ empire/server/data/misc/Obfuscated.ps1 empire/server/data/generated-stagers/* empire/server/downloads/* empire/server/api/static/* +empire/server/api/v2/starkiller-temp # client empire/client/generated-stagers/* diff --git a/empire.py b/empire.py index a22bfdd9b..3d0b29e35 100644 --- a/empire.py +++ b/empire.py @@ -11,6 +11,15 @@ import empire.server.server as server server.run(args) + elif args.subparser_name == "sync-starkiller": + import yaml + + from empire.scripts.sync_starkiller import sync_starkiller + + with open("empire/server/config.yaml") as f: + config = yaml.safe_load(f) + + sync_starkiller(config) elif args.subparser_name == "client": import empire.client.client as client diff --git a/empire/arguments.py b/empire/arguments.py index c0c19fead..b8e6371ae 100644 --- a/empire/arguments.py +++ b/empire/arguments.py @@ -10,6 +10,9 @@ server_parser = subparsers.add_parser("server", help="Launch Empire Server") client_parser = subparsers.add_parser("client", help="Launch Empire CLI") +sync_starkiller_parser = subparsers.add_parser( + "sync-starkiller", help="Sync Starkiller submodule with the config" +) # Client Args client_parser.add_argument( diff --git a/empire/scripts/sync_starkiller.py b/empire/scripts/sync_starkiller.py new file mode 100644 index 000000000..d98d0da98 --- /dev/null +++ b/empire/scripts/sync_starkiller.py @@ -0,0 +1,64 @@ +import logging +import os +import subprocess + +log = logging.getLogger(__name__) + + +def sync_starkiller(empire_config): + """ + Syncs the starkiller submodule with what is in the config. + Using dict acccess because this script should be able to run with minimal packages, not just within empire. + """ + starkiller_config = empire_config["starkiller"] + starkiller_submodule_dir = "empire/server/api/v2/starkiller" + starkiller_temp_dir = "empire/server/api/v2/starkiller-temp" + + subprocess.run(["git", "submodule", "update", "--init", "--recursive"], check=True) + + if not starkiller_config["use_temp_dir"]: + log.info("Syncing starkiller submodule to match config.yaml") + subprocess.run( + [ + "git", + "submodule", + "set-url", + "--", + starkiller_submodule_dir, + starkiller_config["repo"], + ], + check=True, + ) + subprocess.run( + ["git", "submodule", "sync", "--", starkiller_submodule_dir], check=True + ) + + subprocess.run(["git", "fetch"], cwd=starkiller_submodule_dir, check=True) + subprocess.run( + ["git", "checkout", starkiller_config["ref"]], + cwd=starkiller_submodule_dir, + check=True, + ) + + else: + if not os.path.exists(starkiller_temp_dir): + log.info("Cloning starkiller to temp dir") + subprocess.run( + ["git", "clone", starkiller_config["repo"], starkiller_temp_dir], + check=True, + ) + + else: + log.info("Updating starkiller temp dir") + subprocess.run( + ["git", "remote", "set-url", "origin", starkiller_config["repo"]], + cwd=starkiller_temp_dir, + check=True, + ) + + subprocess.run(["git", "fetch"], cwd=starkiller_temp_dir, check=True) + subprocess.run( + ["git", "checkout", starkiller_config["ref"]], + cwd=starkiller_temp_dir, + check=True, + ) diff --git a/empire/server/api/app.py b/empire/server/api/app.py index 6501d43ac..823619290 100644 --- a/empire/server/api/app.py +++ b/empire/server/api/app.py @@ -1,6 +1,7 @@ import json import logging import os +import subprocess from datetime import datetime from json import JSONEncoder @@ -10,8 +11,10 @@ from starlette.middleware.gzip import GZipMiddleware from starlette.staticfiles import StaticFiles +from empire.scripts.sync_starkiller import sync_starkiller from empire.server.api.middleware import EmpireCORSMiddleware from empire.server.api.v2.websocket.socketio import setup_socket_events +from empire.server.core.config import empire_config log = logging.getLogger(__name__) @@ -40,6 +43,25 @@ def default(self, o): return JSONEncoder.default(self, o) +def load_starkiller(v2App): + use_temp = empire_config.starkiller.use_temp_dir + starkiller_submodule_dir = "empire/server/api/v2/starkiller" + starkiller_temp_dir = "empire/server/api/v2/starkiller-temp" + + if empire_config.starkiller.auto_update: + sync_starkiller() + + v2App.mount( + "/", + StaticFiles( + directory=f"{starkiller_temp_dir}/dist" + if use_temp + else f"{starkiller_submodule_dir}/dist" + ), + name="static", + ) + + def initialize(secure: bool = False, port: int = 1337): # Not pretty but allows us to use main_menu by delaying the import from empire.server.api.v2.agent import agent_api, agent_file_api, agent_task_api @@ -119,11 +141,7 @@ def shutdown_event(): setup_socket_events(sio, main) try: - v2App.mount( - "/", - StaticFiles(directory="empire/server/api/v2/starkiller/dist"), - name="static", - ) + load_starkiller(v2App) log.info(f"Starkiller served at http://localhost:{port}/index.html") except Exception as e: log.warning("Failed to load Starkiller: %s", e) diff --git a/empire/server/config.yaml b/empire/server/config.yaml index 411d05477..2ac08f872 100644 --- a/empire/server/config.yaml +++ b/empire/server/config.yaml @@ -34,6 +34,15 @@ database: # an IP black list to reject accept clients from # 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 + # Can be a branch, tag, or commit hash + ref: 5.0-dev-sponsors + # 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. + use_temp_dir: false + auto_update: true plugins: # Auto-load plugin with defined settings csharpserver: diff --git a/empire/server/core/config.py b/empire/server/core/config.py index f408a55dc..8271eb1a2 100644 --- a/empire/server/core/config.py +++ b/empire/server/core/config.py @@ -8,6 +8,13 @@ log = logging.getLogger(__name__) +class StarkillerConfig(BaseModel): + repo: str = "bc-security/starkiller" + ref: str = "main" + use_temp_dir: bool = False + auto_update: bool = True + + class DatabaseDefaultObfuscationConfig(BaseModel): language: str = "powershell" enabled: bool = False @@ -72,6 +79,7 @@ class EmpireConfig(BaseModel): supress_self_cert_warning: bool = Field( alias="supress-self-cert-warning", default=True ) + starkiller: StarkillerConfig database: DatabaseConfig plugins: Dict[str, Dict[str, str]] = {} directories: DirectoriesConfig diff --git a/empire/test/test_server_config.yaml b/empire/test/test_server_config.yaml index 37769c972..e3a376406 100644 --- a/empire/test/test_server_config.yaml +++ b/empire/test/test_server_config.yaml @@ -35,6 +35,15 @@ database: # an IP black list to reject accept clients from # 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 + # Can be a branch, tag, or commit hash + ref: 5.0-dev-sponsors + # 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. + use_temp_dir: false + auto_update: true directories: downloads: empire/test/downloads/ module_source: empire/server/data/module_source/