Skip to content

Commit

Permalink
Update CHANGELOG and code
Browse files Browse the repository at this point in the history
  • Loading branch information
D3vil0p3r committed Dec 3, 2024
1 parent 01db305 commit aca6df6
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 46 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Support Empire for system-wide deployment (@D3vil0p3r)
- Paths specified in config.yaml where user does not have write permission will be fallback to ~/.empire directory and config.yaml updated as well (@D3vil0p3r)
- Invoke-Obfuscation is no longer copied to /usr/local/share

## [5.11.7] - 2024-11-11

- Fix arm installs by installing dotnet and powershell manually
Expand Down
2 changes: 2 additions & 0 deletions docs/quickstart/configuration/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
The Client configuration is managed via [empire/client/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml).

Once launched, Empire checks for user write permissions on paths specified in `config.yaml`. If the current user does not have write permissions on these paths, `~/.empire` will be set as fallback parent directory and the configuration file will be updated as well.

* **servers** - The servers block is meant to give the user the ability to set up frequently used Empire servers.

If a server is listed in this block then when connecting to the server they need only type: `connect -c localhost`.
Expand Down
2 changes: 2 additions & 0 deletions docs/quickstart/configuration/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

The Server configuration is managed via [empire/server/config.yaml](https://github.com/BC-SECURITY/Empire/blob/master/empire/client/config.yaml).

Once launched, Empire checks for user write permissions on paths specified in `config.yaml`. If the current user does not have write permissions on these paths, `~/.empire` will be set as fallback parent directory and the configuration file will be updated as well.

* **suppress-self-cert-warning** - Suppress the http warnings when launching an Empire instance that uses a self-signed cert.

* **api** - Configure the RESTful API. This includes the port to run the API on, as well as the path for the SSL certificates. If `empire-priv.key` and `empire-chain.pem` are not found in this directory, self-signed certs will be generated.
Expand Down
2 changes: 1 addition & 1 deletion empire/client/src/EmpireCliConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self):
if len(self.yaml.items()) == 0:
log.info("Loading default config")
self.set_yaml(config_manager.CONFIG_CLIENT_PATH)
config_manager.check_config_permission(self.yaml, "client")
config_manager.check_config_permission(self.yaml, "client")

def set_yaml(self, location: str):
try:
Expand Down
64 changes: 43 additions & 21 deletions empire/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,32 @@ def check_config_permission(config_dict: dict, config_type: str):
# Define paths to check based on config type
if config_type == "server":
paths_to_check = {
("api", "cert_path"): config_dict["api"]["cert_path"],
("database", "sqlite", "location"): config_dict["database"]["sqlite"][
"location"
],
("starkiller", "directory"): config_dict["starkiller"]["directory"],
("logging", "directory"): config_dict["logging"]["directory"],
("debug", "last_task", "file"): config_dict["debug"]["last_task"]["file"],
("directories", "downloads"): config_dict["directories"].get("downloads"),
("api", "cert_path"): config_dict.get("api", {}).get("cert_path"),
("database", "sqlite", "location"): config_dict.get("database", {})
.get("sqlite", {})
.get("location"),
("starkiller", "directory"): config_dict.get("starkiller", {}).get(
"directory"
),
("logging", "directory"): config_dict.get("logging", {}).get("directory"),
("debug", "last_task", "file"): config_dict.get("debug", {})
.get("last_task", {})
.get("file"),
("directories", "downloads"): config_dict.get("directories", {}).get(
"downloads"
),
}
config_path = CONFIG_SERVER_PATH # Use the server config path

elif config_type == "client":
paths_to_check = {
("logging", "directory"): config_dict["logging"]["directory"],
("directories", "downloads"): config_dict["directories"].get("downloads"),
("directories", "generated-stagers"): config_dict["directories"].get(
"generated-stagers"
("logging", "directory"): config_dict.get("logging", {}).get("directory"),
("directories", "downloads"): config_dict.get("directories", {}).get(
"downloads"
),
("directories", "generated-stagers"): config_dict.get(
"directories", {}
).get("generated-stagers"),
}
config_path = CONFIG_CLIENT_PATH # Use the client config path

Expand All @@ -70,13 +78,21 @@ def check_config_permission(config_dict: dict, config_type: str):

# Check permissions and update paths as needed
for keys, dir_path in paths_to_check.items():
if not os.access(dir_path, os.W_OK):
if dir_path is None:
continue

current_dir = dir_path
while current_dir and not os.path.exists(current_dir):
current_dir = os.path.dirname(current_dir)

if not os.access(current_dir, os.W_OK):
log.info(
"No write permission for %s. Switching to fallback directory.", dir_path
"No write permission for %s. Switching to fallback directory.",
current_dir,
)
user_home = Path.home()
fallback_dir = os.path.join(
user_home, ".empire", dir_path.removeprefix("empire/")
user_home, ".empire", str(current_dir).removeprefix("empire/")
)

# Update the directory in config_dict
Expand All @@ -91,10 +107,16 @@ def check_config_permission(config_dict: dict, config_type: str):

# Write the updated configuration back to the correct YAML file
with open(config_path, "w") as config_file:
yaml.safe_dump(config_dict, config_file)
yaml.safe_dump(paths2str(config_dict), config_file)

return config_dict


log.info(
"Updated %s config.yaml to use fallback directory: %s",
config_type,
fallback_dir,
)
def paths2str(data):
if isinstance(data, dict):
return {key: paths2str(value) for key, value in data.items()}
if isinstance(data, list):
return [paths2str(item) for item in data]
if isinstance(data, Path):
return str(data)
return data
43 changes: 28 additions & 15 deletions empire/server/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ def __getitem__(self, key):


class DirectoriesConfig(EmpireBaseModel):
downloads: Path
module_source: Path
obfuscated_module_source: Path
downloads: Path = Path("empire/server/downloads")
module_source: Path = Path("empire/server/modules")
obfuscated_module_source: Path = Path("empire/server/data/obfuscated_module_source")


class LoggingConfig(EmpireBaseModel):
Expand All @@ -101,17 +101,26 @@ class EmpireConfig(EmpireBaseModel):
alias="supress-self-cert-warning", default=True
)
api: ApiConfig | None = ApiConfig()
starkiller: StarkillerConfig
submodules: SubmodulesConfig
database: DatabaseConfig
starkiller: StarkillerConfig = StarkillerConfig()
submodules: SubmodulesConfig = SubmodulesConfig()
database: DatabaseConfig = DatabaseConfig(
sqlite=SQLiteDatabaseConfig(),
mysql=MySQLDatabaseConfig(),
defaults=DatabaseDefaultsConfig(),
)
plugins: dict[str, dict[str, str]] = {}
directories: DirectoriesConfig
logging: LoggingConfig
debug: DebugConfig
directories: DirectoriesConfig = DirectoriesConfig()
logging: LoggingConfig = LoggingConfig()
debug: DebugConfig = DebugConfig(last_task=LastTaskConfig())

model_config = ConfigDict(extra="allow")

def __init__(self, config_dict: dict):
def __init__(self, config_dict: dict | None = None):
if config_dict is None:
config_dict = {}
if not isinstance(config_dict, dict):
raise ValueError("config_dict must be a dictionary")

super().__init__(**config_dict)
# For backwards compatibility
self.yaml = config_dict
Expand All @@ -128,14 +137,18 @@ def set_yaml(location: str):
log.warning(exc)


config_dict = {}
config_dict = EmpireConfig().model_dump()
if "--config" in sys.argv:
location = sys.argv[sys.argv.index("--config") + 1]
log.info(f"Loading config from {location}")
config_dict = set_yaml(location)
if len(config_dict.items()) == 0:
loaded_config = set_yaml(location)
if loaded_config:
config_dict = loaded_config
elif config_manager.CONFIG_SERVER_PATH.exists():
log.info("Loading default config")
config_dict = set_yaml(config_manager.CONFIG_SERVER_PATH)
loaded_config = set_yaml(config_manager.CONFIG_SERVER_PATH)
if loaded_config:
config_dict = loaded_config
config_dict = config_manager.check_config_permission(config_dict, "server")

config_manager.check_config_permission(config_dict, "server")
empire_config = EmpireConfig(config_dict)
3 changes: 1 addition & 2 deletions empire/server/modules/bof/nanodump.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ def generate(
module=module, params=params, obfuscate=obfuscate
)

for name in params:
value = params[name]
for name, value in params.items():
if name == "write":
if value != "":
dump_path = value
Expand Down
8 changes: 1 addition & 7 deletions empire/test/test_zz_reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict):
1. Deletes the sqlite db. Don't need to test mysql atm.
2. Deletes the downloads dir contents
3. Deletes the csharp generated files
4. Deletes the obfuscated modules
5. Deletes / Copies invoke obfuscation
"""
monkeypatch.setattr("builtins.input", lambda _: "y")
sys.argv = [*default_argv.copy(), "--reset"]
Expand All @@ -64,9 +62,8 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict):
for f in download_files:
assert Path(downloads_dir + f[0]).exists()

# Change the csharp and Invoke-Obfuscation dir so we don't delete real files.
# Change the csharp dir so we don't delete real files.
csharp_dir = tmp_path / "empire/server/data/csharp"
invoke_obfs_dir = tmp_path / "powershell/Modules/Invoke-Obfuscation"

# Write files to csharp_dir
csharp_files = [
Expand Down Expand Up @@ -105,7 +102,6 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict):
assert Path(server_config_dict["database"]["location"]).exists()

server.CSHARP_DIR_BASE = csharp_dir
server.INVOKE_OBFS_DST_DIR_BASE = invoke_obfs_dir

with pytest.raises(SystemExit):
server.run(args)
Expand All @@ -126,8 +122,6 @@ def test_reset_server(monkeypatch, tmp_path, default_argv, server_config_dict):
csharp_dir / "Data/Tasks/CSharp/Compiled/netcoreapp3.0" / f[0]
).exists()

assert Path(invoke_obfs_dir / "Invoke-Obfuscation.ps1").exists()

if server_config_dict.get("database", {}).get("type") == "sqlite":
assert not Path(server_config_dict["database"]["location"]).exists()

Expand Down

0 comments on commit aca6df6

Please sign in to comment.