Skip to content

Commit b8fb1c9

Browse files
authored
Backgroundjob bugfix (#807)
1 parent ab3c3f0 commit b8fb1c9

File tree

22 files changed

+280
-19
lines changed

22 files changed

+280
-19
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
## [Unreleased]
1616

17+
### Added
18+
19+
- Added a runtime `Background` option to C# modules, allowing operators to override background/foreground execution at task time
20+
21+
### Fixed
22+
23+
- Fixed stop-job handlers in PowerShell and Python agents crashing when the target job doesn't exist
24+
1725
## [6.4.1] - 2026-02-15
1826
- Fixed the `docs/quickstart/installation/README.md` file to specify a previously missing reference to Ubuntu
1927
- Updated Starkiller to v3.3.0

docs/modules/module-development/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,34 @@ options:
3232
strict: true
3333
```
3434
35+
## Special Options
36+
37+
Empire reserves certain option names that receive special handling during module execution. These are filtered out of the parameters passed to the module's script and instead control how the task is dispatched or processed.
38+
39+
### Agent
40+
**Required on all modules.** Identifies which agent should execute the module. This is automatically populated by Empire and should not be included in the module's script logic.
41+
42+
### Background
43+
Allows the operator to override the module-level `background` field at runtime. When a module defines `background: true` in its YAML metadata, it will run as a background job by default. Adding a `Background` option lets operators choose per-execution whether to run in the foreground or background.
44+
45+
```yaml
46+
options:
47+
- name: Background
48+
description: Run as a background job (non-blocking). Can be killed via the jobs/kill_job endpoint.
49+
required: false
50+
value: 'true'
51+
type: bool
52+
suggested_values:
53+
- 'true'
54+
- 'false'
55+
strict: true
56+
```
57+
58+
If the `Background` option is not defined on the module, the module-level `background` field is used as-is.
59+
60+
### OutputFunction
61+
PowerShell-specific. Controls how module output is formatted. Substituted into the script via the `{{ OUTPUT_FUNCTION }}` placeholder. Defaults to `Out-String`. See [PowerShell Modules](powershell-modules.md) for details.
62+
3563
## Advanced Options
3664

3765
Empire modules support advanced configuration for dynamic dependencies between options. For example, one option may depend on the value of another option. This is handled using the `depends_on` field.

empire/server/core/module_service.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,11 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
178178

179179
extension = module.output_extension.rjust(5) if module.output_extension else ""
180180

181+
effective_background = cleaned_options.pop("Background", module.background)
182+
181183
if agent.language in ("ironpython", "python"):
182184
if module.language == "python":
183-
if module.background:
185+
if effective_background:
184186
module_data.command = "TASK_PYTHON_CMD_JOB"
185187
else:
186188
command, data = self._handle_save_file_command(
@@ -189,7 +191,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
189191
module_data.command = command
190192
module_data.data = data
191193
elif module.language == "powershell":
192-
if module.background:
194+
if effective_background:
193195
module_data.command = "TASK_POWERSHELL_CMD_JOB"
194196
else:
195197
command, data = self._handle_save_file_command(
@@ -198,7 +200,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
198200
module_data.command = command
199201
module_data.data = data
200202
elif module.language in ("csharp", "bof"):
201-
if module.background:
203+
if effective_background:
202204
module_data.command = "TASK_CSHARP_CMD_JOB"
203205
else:
204206
module_data.command = "TASK_CSHARP_CMD_WAIT"
@@ -209,7 +211,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
209211

210212
elif agent.language == "csharp":
211213
if module.language in ("csharp", "bof"):
212-
if module.background:
214+
if effective_background:
213215
module_data.command = "TASK_CSHARP_CMD_JOB"
214216
else:
215217
module_data.command = "TASK_CSHARP_CMD_WAIT"
@@ -222,7 +224,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
222224

223225
elif agent.language == "powershell":
224226
if module.language == "powershell":
225-
if module.background:
227+
if effective_background:
226228
module_data.command = "TASK_POWERSHELL_CMD_JOB"
227229
else:
228230
command, data = self._handle_save_file_command(
@@ -231,7 +233,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
231233
module_data.command = command
232234
module_data.data = data
233235
elif module.language in ("csharp", "bof"):
234-
if module.background:
236+
if effective_background:
235237
module_data.command = "TASK_CSHARP_CMD_JOB"
236238
else:
237239
module_data.command = "TASK_CSHARP_CMD_WAIT"
@@ -241,7 +243,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
241243
)
242244
elif agent.language == "go":
243245
if module.language == "powershell":
244-
if module.background:
246+
if effective_background:
245247
module_data.command = "TASK_POWERSHELL_CMD_JOB"
246248
else:
247249
command, data = self._handle_save_file_command(
@@ -250,7 +252,7 @@ def execute_module( # noqa: PLR0913 PLR0912 PLR0915
250252
module_data.command = command
251253
module_data.data = data
252254
elif module.language == "csharp":
253-
if module.background:
255+
if effective_background:
254256
module_data.command = "TASK_CSHARP_CMD_JOB"
255257
else:
256258
module_data.command = "TASK_CSHARP_CMD_WAIT"

empire/server/data/agent/agent.ps1

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,17 +1166,23 @@ function Invoke-Empire {
11661166
$JobResultID = $ResultIDs[$JobName];
11671167

11681168
try {
1169-
$Results = Stop-AgentJob -JobName $JobName | fl | Out-String;
1170-
# send result data if there is any
1171-
if($Results -and $($Results.trim() -ne '')) {
1172-
Encode-Packet -type $type -data $($Results) -ResultID $JobResultID;
1169+
# Check if the job exists
1170+
if ($script:tasks.ContainsKey($JobName)) {
1171+
$Results = Stop-AgentJob -JobName $JobName | fl | Out-String;
1172+
# send result data if there is any
1173+
if($Results -and $($Results.trim() -ne '')) {
1174+
Encode-Packet -type $type -data $($Results) -ResultID $JobResultID;
1175+
}
1176+
Encode-Packet -type 51 -data "[+] Job $JobName killed successfully." -ResultID $ResultID;
1177+
} else {
1178+
# Job doesn't exist - send error message
1179+
Encode-Packet -type 0 -data "[!] Job $JobName not found. Available jobs: $($script:tasks.Keys -join ', ')" -ResultID $ResultID;
11731180
}
1174-
Encode-Packet -type 51 -data "Job $JobName killed." -ResultID $JobResultID;
1175-
$script:tasks[$ResultID]['status'] = 'completed'
1181+
# Note: STOPJOB tasks are not background jobs, so we don't track them in $script:tasks
11761182
}
11771183
catch {
1178-
Encode-Packet -type 0 -data "[!] Error in stopping job: $JobName" -ResultID $JobResultID;
1179-
$script:tasks[$ResultID]['status'] = 'error'
1184+
Encode-Packet -type 0 -data "[!] Error in stopping job $JobName : $_" -ResultID $ResultID;
1185+
# Note: STOPJOB tasks are not background jobs, so we don't track them in $script:tasks
11801186
}
11811187
}
11821188

empire/server/data/agent/agent.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,16 @@ def stop_task(self, job_to_kill, result_id):
561561
Task 51
562562
"""
563563
try:
564+
# Check if the job exists
565+
if job_to_kill not in self.tasks:
566+
available_jobs = ", ".join(str(k) for k in self.tasks.keys())
567+
self.packet_handler.send_message(
568+
self.packet_handler.build_response_packet(
569+
0, "[!] Job %s not found. Available jobs: %s" % (job_to_kill, available_jobs), result_id
570+
)
571+
)
572+
return
573+
564574
if self.tasks[job_to_kill]['task_thread'].is_alive():
565575
self.tasks[job_to_kill]['task_thread'].kill()
566576
self.tasks[job_to_kill]['status'] = "stopped"
@@ -578,15 +588,15 @@ def stop_task(self, job_to_kill, result_id):
578588
)
579589
)
580590

581-
self.tasks[result_id]["status"] = "completed"
591+
# Note: STOPJOB tasks are not background jobs, so we don't track them in self.tasks
582592

583593
except Exception as e:
584594
self.packet_handler.send_message(
585595
self.packet_handler.build_response_packet(
586-
51, "[!] Error stopping job thread: %s" % (e), result_id
596+
0, "[!] Error stopping job thread %s: %s" % (job_to_kill, e), result_id
587597
)
588598
)
589-
self.tasks[result_id]["status"] = "error"
599+
# Note: STOPJOB tasks are not background jobs, so we don't track them in self.tasks
590600

591601
def dynamic_code_execute_wait_nosave(self, data, result_id):
592602
"""

empire/server/modules/csharp/code_execution/Assembly.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ options:
2727
description: The command-line parameters to pass to the assembly's EntryPoint.
2828
required: false
2929
value: ''
30+
- name: Background
31+
description: Run as a background job (non-blocking). Can be killed via the jobs/kill_job
32+
endpoint.
33+
required: false
34+
value: 'false'
35+
type: bool
36+
suggested_values:
37+
- 'true'
38+
- 'false'
39+
strict: true
3040
csharp:
3141
UnsafeCompile: false
3242
CompatibleDotNetVersions:

empire/server/modules/csharp/code_execution/AssemblyReflect.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ options:
3636
description: The name of the method to execute.
3737
required: true
3838
value: ''
39+
- name: Background
40+
description: Run as a background job (non-blocking). Can be killed via the jobs/kill_job
41+
endpoint.
42+
required: false
43+
value: 'false'
44+
type: bool
45+
suggested_values:
46+
- 'true'
47+
- 'false'
48+
strict: true
3949
csharp:
4050
UnsafeCompile: false
4151
CompatibleDotNetVersions:

empire/server/modules/csharp/code_execution/RunCoff.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,16 @@ options:
3838
[arg2]
3939
required: false
4040
value: ''
41+
- name: Background
42+
description: Run as a background job (non-blocking). Can be killed via the jobs/kill_job
43+
endpoint.
44+
required: false
45+
value: 'true'
46+
type: bool
47+
suggested_values:
48+
- 'true'
49+
- 'false'
50+
strict: true
4151
csharp:
4252
UnsafeCompile: true
4353
CompatibleDotNetVersions:

empire/server/modules/csharp/credentials/Certify.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ options:
2525
value: help
2626
strict: false
2727
suggested_values: []
28+
- name: Background
29+
description: Run as a background job (non-blocking). Can be killed via the jobs/kill_job
30+
endpoint.
31+
required: false
32+
value: 'false'
33+
type: bool
34+
suggested_values:
35+
- 'true'
36+
- 'false'
37+
strict: true
2838
csharp:
2939
UnsafeCompile: false
3040
CompatibleDotNetVersions:

empire/server/modules/csharp/credentials/Rubeus.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ options:
4343
- changepw
4444
- hash
4545
- tgssub
46+
- name: Background
47+
description: Run as a background job (non-blocking). Can be killed via the jobs/kill_job
48+
endpoint.
49+
required: false
50+
value: 'true'
51+
type: bool
52+
suggested_values:
53+
- 'true'
54+
- 'false'
55+
strict: true
4656
csharp:
4757
UnsafeCompile: true
4858
CompatibleDotNetVersions:

0 commit comments

Comments
 (0)