Skip to content

Commit

Permalink
v0.4.5
Browse files Browse the repository at this point in the history
fixing c2 debug output blocking callbacks
  • Loading branch information
its-a-feature committed Jan 16, 2024
1 parent c9bb763 commit 6fa5781
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 14 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@

## [v0.4.5] - 2024-01-16

### Changed

- Updated the c2 profile sub-process code to not cause deadlocks and only keep the latest 100 messages from debug output

## [v0.4.4] - 2024-01-13

### Changed
Expand Down
2 changes: 1 addition & 1 deletion mythic_container/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

containerVersion = "v1.1.4"

PyPi_version = "0.4.4"
PyPi_version = "0.4.5"

RabbitmqConnection = rabbitmqConnectionClass()

Expand Down
48 changes: 36 additions & 12 deletions mythic_container/c2_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import subprocess
import asyncio
import traceback
from collections import deque


def kill(proc_pid):
Expand All @@ -20,22 +21,43 @@ def kill(proc_pid):
logger.exception(f"[-] Failed to kill process: {e}")


async def deal_with_stdout(c2_profile: str) -> str:
output = ""
lines = 0
async def keep_reading_stdout(c2_profile: str):
if "reading" in mythic_container.C2ProfileBase.runningServers[c2_profile]:
return
while True:
mythic_container.C2ProfileBase.runningServers[c2_profile]["reading"] = 1
try:
line = await asyncio.wait_for(
mythic_container.C2ProfileBase.runningServers[c2_profile]["process"].stdout.readline(), timeout=3.0)
output += line.decode()
lines += 1
if lines > 100:
break
except asyncio.TimeoutError:
break
if line is not None:
if len(line) == 0:
# the process has stopped, but our loop will keep returning blank lines
if mythic_container.C2ProfileBase.runningServers[c2_profile]["process"].returncode is not None:
del mythic_container.C2ProfileBase.runningServers[c2_profile]["reading"]
return
if "output" in mythic_container.C2ProfileBase.runningServers[c2_profile]:
mythic_container.C2ProfileBase.runningServers[c2_profile]["output"].append(line.decode())
else:
mythic_container.C2ProfileBase.runningServers[c2_profile]["output"] = deque([line.decode()], 100)
except TimeoutError:
await asyncio.sleep(1)
continue
except Exception as e:
logger.exception(f"hit exception trying to get server output: {traceback.format_exc()}")
return output + traceback.format_exc()
del mythic_container.C2ProfileBase.runningServers[c2_profile]["reading"]
return


async def deal_with_stdout(c2_profile: str) -> str:
output = ""
if "output" in mythic_container.C2ProfileBase.runningServers[c2_profile]:
try:
while True:
output += mythic_container.C2ProfileBase.runningServers[c2_profile]["output"].popleft()
except IndexError:
pass
except Exception as e:
logger.exception(f"hit exception trying to read server output: {traceback.format_exc()}")
return output


Expand Down Expand Up @@ -421,6 +443,7 @@ async def startServerBinary(
process = await asyncio.create_subprocess_shell(cmd=str(path), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
shell=True, cwd=str(cwd), env=os.environ.copy())
mythic_container.C2ProfileBase.runningServers[c2.name]["process"] = process
asyncio.create_task(keep_reading_stdout(c2.name))
await asyncio.sleep(3)
if process.returncode is not None:
# this means something went wrong and the process is dead
Expand Down Expand Up @@ -521,7 +544,8 @@ async def stopServer(msg: bytes) -> bytes:
Message=f"Stopped server:\nOutput:{await deal_with_stdout(c2.name)}",
InternalServerRunning=False
)
mythic_container.C2ProfileBase.runningServers[c2.name]["output"] = ""
if "output" in mythic_container.C2ProfileBase.runningServers[c2.name]:
mythic_container.C2ProfileBase.runningServers[c2.name]["output"].clear()
return ujson.dumps(response.to_json()).encode()
else:
# just means we've never started it before, so start it now
Expand Down Expand Up @@ -671,4 +695,4 @@ async def hostFile(msg: bytes) -> bytes:
Success=False,
Error=f"Hit exception trying to call host_file function: {traceback.format_exc()}\n{e}"
)
return ujson.dumps(response.to_json()).encode()
return ujson.dumps(response.to_json()).encode()
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# This call to setup() does all the work
setup(
name="mythic_container",
version="0.4.4",
version="0.4.5",
description="Functionality for Mythic Services",
long_description=README,
long_description_content_type="text/markdown",
Expand Down

0 comments on commit 6fa5781

Please sign in to comment.