[7.0.2] - 2026-09-08
- Updated Starkiller to v4.0.3
Changed
- Updated Empire Compiler to v2.0.2: Sharpire now uses a CSPRNG for AES-GCM nonces and AES-CBC IVs, and fixes JSON backslash parsing,
cdpersistence, and network drive visibility. Certify submodule switched to BC-SECURITY fork with null-safety fixes, and adds BouncyCastle.Crypto.dll and updated CommandLine.dll/System.Net.Http.dll for Certify 2.0.
Removed
- Dropped Debian 11 (bullseye) support. It reached end of security support on 2026-08-31 and its
bullseye-securityrelease file expired on 2026-09-07, soapt-get updatenow fails and the installer cannot complete. Removed from the install script, the CI install-test matrix, and the docs.
Fixed
- Updated the
credentials/Certifymodule to the Certify 2.0 command format, building asNet47with merged references and passing arguments straight through. - Fixed
cdin the Gopire agent not persisting: shellcdis now routed toos.Chdirinstead of a throwaway subprocess, and the missingTASK_CHDIRhandler was added.
[7.0.1] - 2026-08-25
Security
- Stopped logging the staging key:
get_staging_key()no longer logs it at INFO, andConfig.__repr__no longer renders it in plaintext. - Made Ed25519 agent-certificate verification mandatory in the PowerShell and C# staging branches, which previously skipped it for an all-zero certificate.
Added
- Added an Inveigh credential parser (
situational_awareness/host/inveigh) that ingests NetNTLMv1/v2 hashes from task output, tagging machine accountsinveigh machine_account. - Added a per-option
format_stringoverride and a{{ VALUE_ARRAY }}token that renders a value as double-quoted, comma-joined elements for PowerShell[Array]parameters. - Added a
krb_session_keycredential type storing the Rubeus session key as<keytype>:<base64key>, for bothdumpandasktgt/asktgsoutput.
Fixed
- Fixed Inveigh's six
[Array]options binding as a single joined element; they now render per-element via{{ VALUE_ARRAY }}. - Fixed Inveigh's
Proxy(now optional) andmDNSTypes(now suggestion-based, not a closed set) options failing validation. - Fixed the Rubeus credential parser not ingesting
dumpoutput;dumptickets now carry theirUserSIDand are taggedrubeus:dump. - Rubeus credential
notesnow carry the ticket's service name (e.g.rubeus:dump krbtgt/ASGARD.CORP) so a session key can be paired with its ticket. - Fixed
chat/messagebroadcasts dying withDetachedInstanceErroron MySQL, so chat messages now reach everyone in the room instead of only reappearing viachat/history. - Fixed C# obfuscation on a stock install: the Docker image and
install.shnow installmono-completeinstead ofmono-runtime(needed for modules like SharpHound). - Fixed C# obfuscation of
Net47modules (e.g. SharpHound) by adding anet47probePath toconfuser.crprojso ConfuserEx resolvesSystem.ValueTuple.
[7.0.0] - 2026-08-12
Security
- BREAKING: Replaced bcrypt password hashing with PBKDF2-HMAC-SHA256 (600K iterations) for FIPS SP 800-132 compliance. Existing hashes are incompatible — reset passwords or recreate the database.
- BREAKING: Replaced MD5 with SHA-256 for staging key normalization (non-32-char keys) per FIPS. Agents staged under the old algorithm must be re-staged.
- BREAKING: Replaced
randomwithsecrets(server) andrandom.SystemRandom()(agent) for CSPRNG generation per FIPS SP 800-90A, covering staging keys, keyword obfuscation, session IDs, and nonces across listeners, stagers, agents, and utilities. Deployed agents must be re-staged. - Hardened TLS for FIPS SP 800-52r2: enforce TLS 1.2 minimum, drop non-GCM/non-ECDHE cipher suites from the JA3 evasion pool, and use RSA 4096-bit keys for self-signed certificates.
- Added
filter='data'totar.extractall()in compiler download to prevent path traversal from untrusted archives. - Malleable profile
headerdirectives now reject CRLF in the name or value at both parse and deserialize time (dropping the directive with a warning), closing a header-injection vector through the Flaskafter_requestmerge. - Replaced MySQL
MD5()withSHA2(..., 256)for thehosts.unique_checkgenerated column. - The generated TLS private key is now written with mode
0600rather than inheriting the process umask, which undersudo -Eleft a root-owned world-readable key. - The generated TLS private key and staged certificate are written with
O_NOFOLLOW, so a symlink planted at either path during asudo -E(root) run can no longer redirect the write and truncate an arbitrary root-owned file.
Added
- Added an
AFTER_CHAT_MESSAGE_HOOKcore hook, fired after a chat message is persisted with(db, message: ChatMessage), so plugins can observe chat traffic. Refactored thechat/messageSocket.IO handler onto a testablepersist_and_fire_chathelper (behavior-preserving). - BREAKING: Added a
./ps-empire updatesubcommand that moves the checkout to the latest release tag, refreshesconfig.yaml, and fast-forwards Starkiller, the plugin registry, and the Empire Compiler binary (skips source on dev branches). The baseconfig.yamlis overwritten on every update — move local customizations toconfig.user.yaml. ./ps-empire updatenow applies pending Alembic migrations during the upgrade (prompts to back up first,-yto auto-confirm), so a schema mismatch no longer crashes the server on next start.- Smoothed the bcrypt → PBKDF2 upgrade: a bcrypt-shaped default-user hash is auto-reset to PBKDF2 on startup (logged with recovery steps) and non-default bcrypt users are left untouched. Manual recovery: an admin
PUT /api/v2/users/{id}/password(or Starkiller) stores a PBKDF2 hash. - Added
starkiller.directoryconfig option to serve an already-built Starkiller instead of cloning one from GitHub at startup, enabling air-gapped and distro-packaged installs. - Added
wdtoggle,psx,psm,psk,psw,psc, andwinverBOF modules from the Outflank C2-Tool-Collection:wdtoggle— patch WDigest/Credential Guard in LSASS to re-enable plaintext credential cachingpsx— process list annotated with detected security products (AV/EDR/logging tools)psm— detailed single-process info by PID (loaded modules, TCP connections, RDP sessions)psk— loaded kernel modules/drivers annotated with security-product driver detectionpsw— enumerate window titles across running processespsc— process list with active TCP connections and RDP session detailswinver— Windows version, build number, and patch revision of the target host
- Added
netuse_add,netuse_list, andnetuse_deleteBOF modules splitting the TrustedSec netuse BOF into three focused modes (map a share, enumerate connections, disconnect). - Added
sc_enumBOF module to bulk-enumerate all services on a local or remote host via SCM (config, query, failure, and trigger data in one call). - Added
sc_qcBOF module to query a Windows service's configuration (binary path, start type, account, dependencies) via SCM. - Added
sc_qdescriptionBOF module to query a Windows service's description via SCM. - Added
sc_qfailureBOF module to query a Windows service's failure actions (restart, run command, reboot) via SCM. - Added
sc_qtriggerinfoBOF module to query the trigger events that start or stop a Windows service. - Added
netuserenumBOF module to enumerate local or domain user accounts viaNetUserEnumwith a configurable filter. - Added
nslookupBOF module for DNS lookups via the Windows DNS client API, with optional custom server and record type. - Added
notepadBOF module to enumerate open Notepad/Notepad++ windows and extract the displayed file path or in-memory text. - Added
sprayadBOF module (credentials). - Added
findLoadedModuleBOF module to enumerate processes that have a given module (DLL substring) loaded — useful for locating injection targets and fingerprinting EDR. - Added
vssenumBOF module to enumerate Volume Shadow Copies exposed on a host's SMB share — useful for locating offline SAM/SYSTEM/NTDS hives. - Added an
InjectSelfoption (defaulttrue) toinject_amsi_bypassandinject_etw_bypassso the server injects the bypass into the agent's own process; setfalseto target a remote PID. - Added
addmachineaccountBOF module (lateral movement). - Added
nonpaged_ldapsearchBOF module for synchronous non-paged LDAP queries against a domain controller, useful when paged queries are throttled; refreshed the in-repo.ofiles. - Added
ldapsearchBOF module for LDAP/LDAPS queries against a domain controller with configurable scope. - Added 5 ajpc500 BOF modules for syscall-based injection and defense evasion:
etw_patching— patch or revertEtwEventWritein ntdll to degrade ETW logging in-processstatic_syscalls_dump— dump a target process's memory via static syscall numbers, bypassing ntdll hooksstatic_syscalls_inject— inject shellcode into a remote process via static syscallsstatic_syscalls_apc_spawn— spawn a temporary process and inject shellcode via APC queue using static syscallssyscalls_shellcode_injection— inject shellcode into a remote process via direct syscalls
- Added 7 Outflank C2-Tool-Collection BOF modules:
reconad— ADSI-based Active Directory reconnaissancedomaininfo— AD domain membership and Azure AD join status of the hostsmbinfo— SMB server info viaNetServerGetInfofindmodule— find processes with a given module (DLL) loadedfindprochandle— find processes holding a handle to a named objectstartwebclient— start the WebClient service via ETW trigger registrationlapsdump— dump LAPS passwords (ms-Mcs-AdmPwd) from AD via LDAP
- Added a downloadable chatlog report (
report=chat, included inreport=all) exportingchatlog.csv. Operator chat is now persisted to a newchat_messagestable (alembic0003) instead of an in-memory list, andchat/historynow returns the most recent 20 messages. - Added
TASK_CHDIR(opcode 44) andPOST /api/v2/agents/{id}/tasks/chdirto persistently change the agent's working directory (PowerShell, Python, IronPython). - Added situational-awareness modules replacing the removed in-agent aliases:
powershell/situational_awareness/host/{processes,ipconfig,route,dir_list}andpython/situational_awareness/host/processes(PowerShell variants emit JSON for the existing server-side filters). - Added chunked file uploads to agents, removing the 1MB limit — files split into 512KB chunks drip-fed across checkins. Supports all agent languages with backward-compatible protocol detection.
- Added
reg_queryBOF module for in-agent Windows registry queries (key enumeration, value lookup, recursive traversal) on local or remote systems; refreshed the in-repo.o. - Added SharpHound C# module for BloodHound Active Directory enumeration with ILRepack assembly merging.
- Added
MergeReferencesoption to the C# module YAML schema, enabling ILRepack dependency merging via the Empire Compiler--merge-referencesflag. - Added AES-256-GCM across the stack: an
AES256GCMAEAD class inencryption.py(server), a pure-Python version (agent/stagers/common/aesgcm.py) for Python/IronPython stagers, and a .NET 4.x C#AesGcmHelperfor PowerShell stagers. - Added
SafeChecksPS/SafeChecksPythonbypass YAMLs consolidating the snippets the removedSafeChecksoption used to inject. Opt in via a stager'sBypassesparameter. - Added parser support for the Cobalt Strike
http-configblock. The malleable HTTP listener wirestrust_x_forwarded_for(default False),block_useragents(fnmatch globs returning the shared IIS 7.5 404), andheader(merged into every response);set headersordering is parsed but not yet enforced. - Added parser support for the Cobalt Strike
https-certificateblock (CN,O,OU,C,L,ST,validity,keystore,password). Parse-only — Empire still loads the cert fromCertPathand logs a startup warning; runtime cert generation is deferred. - Added a
languagequery parameter to the bypasses list endpoint to filter bypasses to a single execution language. - Added a computed
bypass_language_mapto stager-template options, exposing per payloadLanguagewhich execution language its bypasses run in so Starkiller's bypass picker can filter correctly (declared viaBypassLanguageplus sparseBypassLanguageOverrides). - Added
port_forward_pivotsupport for all agent types: a backgrounded userspace TCP relay replaces the netsh redirector (adding Sharpire, Gopire, and Python alongside PowerShell), and the listener manages its own inbound firewall rule so the relay binds without a Windows Firewall prompt. - Added a
register_listener_templateplugin hook so plugins can contribute listener templates fromon_load(with matchingunregister_listener_templateand aBasePlugin.register_listenerconvenience). - Added credential-parser hooks for non-agent ingestion paths: the
CredentialParserprotocol now acceptsagent=Nonefor sources not tied to an Empire agent, plus a newNETNTLMV2credential type (hashcat mode 5600). - Added Gopire (Go agent) support for backgrounding long-running PowerShell jobs and the
TASK_STOPJOBstop-job task. - Added a malleable
host_stagegate to disable the stager URI, plus a Gopire malleable schema version guard. - Added
api.cors_originsconfig field (default["*"]) to make CORS allowed origins operator-configurable for both the REST API and Socket.IO server. Override viaconfig.yamlorEMPIRE_API__CORS_ORIGINS(JSON-encoded list). - Global tag registry API at
/api/v2/tags: list (withusage_count), get, create, rename/recolor/edit-description, and delete-everywhere. - Added a standalone C# module compilation test (
tests/test_compile_csharp.py) compiling every C# module against the real EmpireCompiler, parametrized per-module. Marked@pytest.mark.slow. - Added AES-GCM interoperability and routing-packet tests: cross-implementation server/agent parity, plus wrong-key rejection, tampered packets, AAD mismatch, empty/non-block-aligned plaintexts, and concatenated packets.
- Migration
0006: widensagent_files.session_idtoString(255)and addsON DELETE CASCADEFK toagents.session_id. - Added an
empire-serverconsole script so an installed Empire has a working entry point. - Added
clickandwerkzeugas explicit dependencies; both were imported directly but supplied only transitively by Flask.
Changed
- BREAKING: Base directories now use
platformdirs, making config, data, and cache paths XDG-compliant. Defaults are unchanged unless$XDG_*_HOMEis set; the Go build cache moves to~/.cache/empireand rebuilds on first use. - BREAKING: Replaced raw SHA-256 with HKDF-SHA256 (RFC 5869) for DH session key derivation per FIPS SP 800-56C across all agent languages, normalizing the shared secret to 768 bytes (NIST SP 800-56A). All deployed agents must be re-staged.
- BREAKING: Tags are now a shared registry: a flat unique
namewith a single sharedcoloranddescription, applied many-to-many to entities. The oldkey:valuemodel (per-entity tag rows,valuefield,name:valuelabel) is removed. - BREAKING: Per-entity tag endpoints now ATTACH/DETACH a shared tag by id:
POST {entity}/{id}/tagstakes{tag_id}(attach existing,200;404if unknown) — create tags first viaPOST /api/v2/tags;DELETEdetaches but the tag persists. Tag list filters use exact?tags=name(no colon). - BREAKING: Tag hook contract changed: new
AFTER_TAG_ATTACHED_HOOK(db, tag, taggable) fires on every attach;AFTER_TAG_CREATED_HOOKandAFTER_TAG_UPDATED_HOOKare now pure registry signals (db, tag, notaggable). - BREAKING: Typed module-option metadata in GET responses. Boolean options moved to native
value: falsein YAML, so options now report a realvalue_type(BOOLEAN/INTEGER/FLOATinstead ofSTRING) and a populatedtype. POST bodies are unaffected — native JSONbool/int/floatare accepted and re-typed per option bysafe_cast. - BREAKING: Listeners and stagers now raise
ListenerValidationException/StagerGenerationExceptioninstead of returning error strings or(False, "msg")tuples. Plugin and module execution paths no longer return tuples; errors are raised. - BREAKING: Removed unused
listenerandexternal_ipparameters fromupdate_agent_sysinfo()inAgentCommunicationService. - Pointed the plugin registry at the
7.xbranch ofEmpire-Plugin-Registry-Sponsors. Each major line reads its own<major>.xbranch;mainstays pinned for 6.x installs. - The persisted plugin registry row is now reconciled from the on-disk clone on every boot instead of written once at first load, so a ref change reaches an already-installed server. A sync or parse failure keeps the last-good row instead of aborting startup.
- The plugin marketplace now serves only registries named in the config, so a renamed or removed registry's leftover row can't list plugins or resolve installs against an older major line.
- Extended typed boolean options to stager and listener templates, which now report
value_type: BOOLEAN(e.g.Obfuscate,Base64,JA3_Evasion) instead of string"True"/"False"defaults — also fixingJA3_Evasion's'False'default always being truthy. POST bodies are unaffected. - Migrated server-side module
generate()methods to read native typed options (if params["X"]:) and removed thenormalize_legacy_paramsshim. API clients are unaffected; only direct non-API callers ofvalidate_optionspassing a nativeboolagainst a stringlydepends_on/suggested_valueslist see a change. - Replaced ChaCha20-Poly1305 with AES-256-GCM for routing-packet encryption across all agent languages (FIPS compliance). The C# agent (Sharpire) must be updated separately.
- Increased HMAC-SHA256 truncation from 10 to 16 bytes (128 bits) for AES-CBC payload encryption per FIPS SP 800-107, across all agent languages. The C# agent (Sharpire) must be updated separately.
- Updated Empire Compiler to v2.0.0 (FIPS-compliant Sharpire with 16-byte HMAC, HKDF-SHA256, AES-GCM) and migrated C# module YAMLs to its new format.
- Fixed Go
CheckPublicKeyto use the Legendre symbol(prime-1)/2exponent instead of Fermat's(prime-1), which always returned true. - Set all C# modules to
background: trueso compiled tasks run without blocking the agent. - Renamed VNC module
Usernameoption toServerNameto reflect its purpose (session display name, not a credential). - Removed the redundant
Agentoption from PatchETW and PatchlessAMSI modules (auto-injected by the framework). - Replaced
AESCipher.generate_key()(sampled printables, ~207-bit entropy) withos.urandom(32).hex()for full 256-bit CSPRNG entropy, returned as hex to match the DH session-key format. - Unexpected (non-
Module*Exception) errors in a custom-generate module'sgenerate()now return HTTP 500 ("Error generating script.") instead of 400;ModuleValidationException/ModuleExecutionExceptionstill map to 400/500 and the legacy tuple path still returns 400. BypassService.load_bypassesrunsps_convert_to_onelineronly onlanguage: powershellscripts, so multi-line Python/Bash bodies are persisted verbatim.- Python launcher branches in the
http,http_malleable,http_foreign,http_hop,port_forward_pivot, andsmblisteners now concatenate matching-language bypass scripts (previously Python-targeted bypasses never reached the agent). - Changed
AES256GCM/ChaCha20Poly1305decrypt/open to catchInvalidTagspecifically instead of bareException. - Changed
AESCipher.verify_hmac()to usehmac.compare_digestfor constant-time comparison. - Downgraded the compiler-args log message from INFO to DEBUG to reduce server log noise.
- Demoted the malleable "accepting
set <key>but not acting on it" log from INFO to DEBUG (the allow-list spans 60+ keys and flooded consoles); unknown directives still WARN. - Parallelized the test suite with pytest-xdist and preserved the cached empire-compiler / Starkiller / Go-build dirs across runs instead of re-downloading them each time (developer/CI change).
- The two heaviest C# compile tests (SharpHound, Rubeus) now run only on release branches / the
run-all-versionslabel instead of every PR; the other C# modules still compile per-PR (developer/CI change). - Download responses now carry an explicit
Content-Typefrom an Empire-owned extension map, so a file no longer serves as a different type depending on the operator's distro; anything unmapped serves asapplication/octet-stream. - The self-signed server certificate is now generated in process with
cryptographyinstead of shelling out tosetup/cert.sh, which removes Empire's runtime dependency onbashand anopensslbinary. - Vendored the Malleable C2 profiles directly into the repository instead of shipping them as a git submodule, so installs that do not clone with submodules -- release tarballs, "Download ZIP", and the sdist and wheel -- now receive all 75 profiles instead of silently receiving none.
Upgrading an existing clone: runrm -rf empire/server/data/profilesbefore checking out or merging this change, or git will abort with "untracked working tree files would be overwritten".
Removed
- BREAKING: Removed the deprecated
handle_error_messagehelper. Callers should let exceptions propagate. - BREAKING: Removed deprecated
installPathstring attribute fromMainMenu. Useinstall_path(aPath); third-party plugins and stagers referencingself.main_menu.installPathorself.mainMenu.installPathmust switch. - BREAKING: Removed in-agent
shellcommand aliases (ls,cd,pwd,ps,ipconfig, etc.) from the PowerShell, Python, and IronPython agents;shell <cmd>now passes straight to the system shell (matching C#/Go). Use the newsituational_awareness/host/*modules for structured output andTASK_CHDIRfor directory changes. - BREAKING: Removed unused
stager_retriesparameter fromgenerate_launcher()across all listeners and stagers, and the correspondingStagerRetriesstager option. - BREAKING: Removed the per-entity
PUT {entity}/{id}/tags/{tag_id}endpoint and thevalue/labelfields on the tag DTO. Tags are now a flat global registry — edit a tag once viaPUT /api/v2/tags/{id}; entities only attach and detach. - BREAKING: Removed the
SafeChecksoption from all stagers, modules, listeners, and the stager API DTO (including the PowerShell version-guard,Expect: 100-Continue, andpython_safe_checks()helpers). Behavior is now opt-in via theSafeChecksPS/SafeChecksPythonbypasses; callers that hardcodedSafeChecks=Truemust add the matching bypass toBypasses. - Removed ChaCha20-Poly1305 classes from
encryption.pyand the agent-sidechacha.pystager — not FIPS-approved; routing packets already use AES-256-GCM. - Removed the Seatbelt module (superseded by updated Empire Compiler modules).
- Removed legacy PowerShell BloodHound/SharpHound modules, replaced by the native C# SharpHound module.
- Removed the
powershell/management/switch_listenermodule and all switch-listener infrastructure (API endpoint, packet types, task service, response handlers). - Removed dead
getIV()function from agent stager AES code (bypassed by inlineos.urandom()). - Removed a byte-identical duplicate definition of
Profile._apply_set_directiveinmalleable/profile.py(dead code from a merge artifact). - Removed
setup/cert.sh; certificate generation happens automatically on first server start. - Removed the
submodules.auto_updateconfig key and the startup submodule check/fetch; existing configs carrying the key still load and the key is ignored. - The startup submodule check used to abort the server with
sys.exit(1)on an unpopulated submodule, though it returned early when no.gitdirectory was present and so never fired for the archive installs this release fixes; with the submodule gone, a missing profiles directory is now logged rather than fatal.
Fixed
- Fixed one plugin raising in
on_stop/on_unloadaborting the whole shutdown teardown, leaving every plugin after it running. Each teardown now runs in its own session. - Fixed a plugin raising during load taking down startup and every plugin behind it. Each load now runs in its own savepoint, a malformed
plugin.yamlis skipped instead of aborting the boot, and a plugin that fails afteron_loadis unloaded rather than left with its hooks and listener templates registered. - Fixed
plugin.enabledbeing set only afteron_start/on_stopreturned, so awhile self.enabledworker raced the flag — exiting at boot, or never stopping on disable or shutdown. It is now set before the hook on every path. - Fixed
hooks.unregister_hook(name)/unregister_filter(name)raisingKeyErrorwhen called without an event, unless the name was registered under every event. An unregistered name is still tolerated, but now warns. - Fixed registry-supplied authors never reaching a plugin installed from the marketplace:
_merge_plugin_configindexed a single registry entry as if it were a whole registry document, so the lookup always missed. - Fixed
custom_generatePowerShell modules emitting-Option Falsefor an unset boolean[switch], producing a malformed command (find_fruit,WireTap,runas,inveigh_relay,deaduser,get_subnet_ranges, and thepowershell_template.pythey derive from). A set switch now emits a bare flag and an unset one emits nothing. Closes #1518. - Fixed
bof/credentials/nanodumpboolean flags (valid,fork,snapshot, etc.) being silently ignored — the module compared against lowercase"true"while option values render as"True", so every flag resolved to0. They now read the native bool. - Fixed
bof/management/static_syscalls_injectandsyscalls_shellcode_injectionreporting success but never delivering a callback:BeaconDataLength()includes the 4-byte length prefix, so the shellcode size was 4 bytes too large and the page-rounded value clobbered the write length. Shellcode is now page-aligned. - Fixed agent listener name not updating in the database on rename, causing Starkiller to show the old name.
- Fixed the csharp/ironpython/go EXE-oneliner stagers silently dropping the operator's
Bypassesselection; the PowerShell downloader they emit now prepends the selected PowerShell bypasses. - Fixed
windows/launcher_batignoring the operator'sBypassesfor PowerShell payloads on non-HTTP listeners, and silently emitting a do-nothing.batfor csharp/ironpython/go on non-HTTP listeners; bypasses now apply and unsupported language/listener combinations raiseStagerGenerationException. - Fixed a race in concurrent stager generation where
generate_powershell_exeandgenerate_python_exeshared a singleData/EmbeddedResources/common/launcher.txt, cross-contaminating launcher content when two compilations overlapped. Each call now writes to a unique per-compilation subdirectory. - Fixed the dead
length < 0bounds check in routing-packet parsing (unreachable for unsigned lengths), replaced with proper end-of-buffer validation in both the Python serverparse_routing_packetand the Go agent. - Fixed Go agent
ParseRoutingPacketto use the offset when reading the nonce from multi-packet payloads. - Fixed silent error swallowing in agent
aesgcm.pyprocess_tasking/process_job_tasking— bareexcept Exception: passreplaced with specific handling and error reporting to C2. - Fixed the VNC module using copy-pasted ThreadlessInject code instead of the NVNC library (non-functional since introduction); now uses
NVNC.VncServerwith the module's Password/Port/ServerName options. - Fixed bare
except:clauses in agentparse_task_packet, narrowed toUnicodeDecodeError. - Fixed bypass concatenation being silently skipped in the
http_foreign,http_hop,port_forward_pivot, andtemplatelisteners whenSafeChecks=False(the loop was nested in thesafe_checksblock); bypasses now always apply. - Fixed the pre-existing typo
safe_checks = params["UserAgent"]inpython/privesc/multi/sudo_spawn.pyby removing the line with the rest of theSafeChecksplumbing. - Fixed
Profile._deserialize/HttpsCertificate._deserializeusing rawbool(...)forhost_stage/trust_x_forwarded_for, mapping"false"back toTrue— which could silently re-enable a disabled stager URI. A malformedvalidityis now log-dropped instead of killing listener startup. - Fixed the malleable HTTP listener returning a Flask 500 (
AttributeErroronextract_client) for a request URI matching none of the configuredhttp-get/http-post/http-stagerURIs; it now returns the shared IIS 7.5 404, preserving the uniform fingerprint. - Fixed
taskUrivstaskURIvariable name mismatch in HTTP malleable listener generated agent code that would cause aNameErroron target. - Fixed BOF argument packing in
generate_script_bof/generate_go_bof: empty optionalz-format options became a" "sentinel that BOFs misread as a value (e.g.reg_query→ERROR_BAD_NETPATH), and POSIX-modeshlexstripped backslashes from registry paths. Arguments are now passed as a pre-built list. - Fixed C#/IronPython/Go stagers being blocked against the malleable HTTP listener despite Sharpire/Gopire support. The 15 hardcoded allow-list gates (
multi/launcher, thewindows/*wrappers,invoke_psexec) now route vialistener.stager_url(), andhttp_malleable.generate_stagerserves the binary instead of an empty stage, so launcher/wrapper flows get an executable payload at stage 0. - Fixed boolean module options forwarded straight to a truthy check (
if obfuscate:) being effectively always-on; nativeFalsenow reaches custom-generate modules. Most notablycsharp/management/ThreadlessInject'sObfuscatealways obfuscated regardless of the toggle; the same applies toCheckAll/NoDefaults/Debugin several SQL and macOS modules. - Fixed
powershell/privesc/powerup/service_stagerandservice_exe_stagerraisingKeyErroron every invocation by settingUserAgent/Proxy/ProxyCredson thewindows_launcher_battemplate, which defines no such options; removed the dead proxy plumbing from both modules and their YAMLs. - Fixed
ExtFilemode being non-functional in seven PowerShell persistence modules (persistence/userland/{registry,schtasks,backdoor_lnk},persistence/elevated/{registry,schtasks,wmi,wmi_updater}):enc_powershell()returnsbytes, so six raisedTypeError(shown to the operator as the genericError generating script.) andbackdoor_lnksilently embedded theb'...'repr. Each call site now decodes tostr. - Fixed several long-standing module option read-mismatches surfaced during the typed-options review, where an option's name or type didn't line up with how
generate()read it — leaving a toggle dead, always-on, or crashing the module. None were introduced by the boolean migration; all predate it.bof/situational_awareness/windowlist— the "list every window" flag readparams.get("all")(lowercase), which never matched theAlloption, so it was hard-wired off regardless of the toggle.powershell/lateral_movement/invoke_sqloscmd— was entirely non-functional: itsscript_pathpointed atInvoke-SQLOSCmd.ps(the source file is.ps1), so loading failed on every run, andgenerate()readObfuscate/ObfuscateCommandoptions the YAML never declared (aKeyError). The path is corrected and the two launcher-obfuscation options added.powershell/credentials/mimikatz/dcsync_hashdump—Forest,Computers, andActiveare booleans but were compared against"", so-DumpForest/-GetComputerswere always appended and-OnlyActive:$falsenever was. They now read as native bools;Activedefaults totrue(only-active) to match its description and the$OnlyActive = $truedefault inInvoke-DCSync.ps1.python/collection/osx/prompt—ListApps/SandboxModeare booleans compared against"", so theListAppsbranch was always taken and the sandbox / app-prompt branches were dead.powershell/credentials/credential_injection— the "eitherNewWinLogonorExistingWinLogonmust be specified" guard compared both booleans against"", so it never raised and the module ran with neither option selected. The module's bespoke option loop also emitted every option as-Name Value, so a normalNewWinLogonrun still appended the unset-ExistingWinLogon False; because the two are[Switch]parameters in mutually-exclusive parameter sets, that command failed to bind on the agent. The loop now keys on each option's native type — boolean switches emit a bare flag only when set, while value options (including a literal"True"/"False", e.g. a password) pass through unchanged.powershell/persistence/elevated/schtasks— theOnLogonboolean was compared against"", so the ONLOGON trigger was always selected and theIdleTime/DailyTimetrigger branches were unreachable.powershell/situational_awareness/host/packet_capture— thePersistentboolean was compared against"", sopersistent=yeswas always appended tonetsh trace startregardless of the toggle.powershell/privesc/bypassuac_fodhelper—generate()reads aCommandoption (a custom launcher command used instead of a Listener) that the YAML never declared, so that path was dead; andListenerwasrequired: true, so the custom-command branch was unreachable anyway.Commandis now declared andListenermade optional, matching the module's "either Listener or Command" logic.
- Fixed the
csharp_exestager ignoring the global obfuscation config — csharp/ironpython payloads (where the per-stagerObfuscateoption is hidden) now fall back to thecsharpObfuscationConfig, so ConfuserEx runs on the produced.exe. - Fixed dynamic PowerShell script generation not resolving aliased, mis-cased, or long-form PowerView function names.
- Fixed
create_downloadrequiring a user, which blocked downloads from autorun tasks that have no associated operator. - Fixed 25 module YAMLs carrying ATT&CK technique IDs that no longer resolve under v19, remapping the
T1562Impair Defenses family toT1685/T1686/etc. andT1070.001toT1685.005. - Fixed Python 3.13/3.14 and library deprecation warnings across the server (
datetime.utcnow()→now(UTC),asyncio.iscoroutinefunction→inspect, pyparsingescChar/searchString, Pydantic v1class Config/Field(env=), StarletteHTTP_422_*). - Fixed
_generate_scriptfiring a spuriousDeprecationWarningon every successful module execution; it now returns data directly (or raisesModule*Exception). The tuple-return warning inexecute_modulenow fires only for legacy(None, msg)custom-generate modules. - Fixed
ShellPostRequest.literalfiring a spuriousDeprecationWarningon every shell POST; the handler no longer reads the field andliteralwas dropped fromcreate_task_shell, though the DTO keeps itdeprecated=for clients. - Fixed
is_option_requiredraisingKeyErroron adepends_onentry that omits avalueslist; a valueless dependency now imposes no value constraint, matchingevaluate_dependencies. No shipped module triggers this, but it crashed validation for any valuelessdepends_on. - Fixed a missing
x86_64-w64-mingw32-gccin the Windows C stager returning an opaque 500 instead of a 400 naming the toolchain and its install command. - The built wheel now ships modules, listeners, stagers, profiles, agent sources and configs, so an installed Empire is no longer left with zero of them.
- The server no longer resolves its seeded config, ConfuserEx project file or certificate script against the current working directory, so it can be started from any directory.
gitsubmodule and commit-SHA lookups now run against Empire's own repository instead of whatever directory the server was launched from.- The
installsubcommand now exits with an error where it is unavailable instead of silently succeeding. - Running the server with no subcommand now prints usage and exits 2, instead of importing the whole server and exiting 0 for what is a usage error.
- The server now regenerates its self-signed certificate when either half of the pair is missing, so a key written without its certificate is no longer left mismatched on every subsequent start.
- An HTTPS
http_malleablelistener with noCertPathnow falls back to the generated pair in the data directory instead of a CWD-relativesetup/, which never contained one. - Removed a startup network fetch (
git submodule update --init --recursive) that ran by default on git checkouts and blocked air-gapped installs. - The profile loader now logs an error when it finds no profiles at all instead of starting up silently, so an empty profiles directory is visible at boot rather than when a malleable listener has nothing to select.