Skip to content

Commit

Permalink
bump 0.2.8-rc03
Browse files Browse the repository at this point in the history
fixed an issue with c2 profile arrays and translation container message formats
  • Loading branch information
its-a-feature committed May 17, 2023
1 parent 795bb17 commit 3a8a2ce
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 31 deletions.
4 changes: 1 addition & 3 deletions mythic_container/C2ProfileBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,9 +834,7 @@ def to_json(self):
return {
"name": self.name,
"description": self.description,
"default_value": self.default_value if self.parameter_type not in [ParameterType.Array,
ParameterType.Dictionary] else json.dumps(
self.default_value),
"default_value": self.default_value,
"randomize": self.randomize,
"format_string": self.format_string,
"required": self.required,
Expand Down
302 changes: 276 additions & 26 deletions mythic_container/TranslationBase.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,213 @@
import asyncio
from abc import abstractmethod
from .grpc.translationContainerGRPC_pb2_grpc import TranslationContainerStub
from .grpc.translationContainerGRPC_pb2 import TrGenerateEncryptionKeysMessage, TrGenerateEncryptionKeysMessageResponse
from .grpc.translationContainerGRPC_pb2 import TrCustomMessageToMythicC2FormatMessage, \
TrCustomMessageToMythicC2FormatMessageResponse
from .grpc.translationContainerGRPC_pb2 import TrMythicC2ToCustomMessageFormatMessage, \
TrMythicC2ToCustomMessageFormatMessageResponse
from .grpc import translationContainerGRPC_pb2 as grpcFuncs
import grpc.aio
from mythic_container.logging import logger
from .config import settings
import json
from typing import List


class TrGenerateEncryptionKeysMessage:
"""
Message from gRPC asking translation container to generate encryption/decryption keys
:param TranslationContainerName
:param C2Name
:param CryptoParamValue - what the user selected when building an agent
:param CryptoParamName - the name of the parameter that needs a crypto key generated
"""
def __init__(self,
TranslationContainerName: str,
C2Name: str,
CryptoParamValue: str,
CryptoParamName: str):
self.TranslationContainerName = TranslationContainerName
self.C2Name = C2Name
self.CryptoParamValue = CryptoParamValue
self.CryptoParamName = CryptoParamName

def to_json(self):
return {
"translation_container_name": self.TranslationContainerName,
"c2_profile_name": self.C2Name,
"value": self.CryptoParamValue,
"name": self.CryptoParamName
}


class TrGenerateEncryptionKeysMessageResponse:
"""
Response to a request to generate encryption keys
:param Success indicate if this was successful or not
:param Error if this wasn't successful, what's the error message
:param EncryptionKey the bytes of the new encryption key to use
:param DecryptionKey the bytes of the new decryption key to use
"""
def __init__(self,
Success: bool = False,
Error: str = "",
EncryptionKey: bytes = None,
DecryptionKey: bytes = None):
self.Success = Success
self.Error = Error
self.EncryptionKey = EncryptionKey
self.DecryptionKey = DecryptionKey

def to_json(self):
return {
"success": self.Success,
"error": self.Error,
"enc_key": self.EncryptionKey,
"dec_key": self.DecryptionKey
}


class CryptoKeys:
"""
Crypto Key information available for a translation container to decrypt/encrypt a message if necessary
:param EncKey the bytes of the encryption key or None
:param DecKey the bytes of the decryption key or None
:param Value the value selected by the user for the type of encryption
"""
def __init__(self,
EncKey: bytes = None,
DecKey: bytes = None,
Value: str = ""):
self.EncKey = EncKey
self.DecKey = DecKey
self.Value = Value
def to_json(self):
return {
"enc_key": self.EncKey,
"dec_key": self.DecKey,
"value": self.Value
}


class TrCustomMessageToMythicC2FormatMessage:
"""
Request to translation a custom C2 formatted message into a Mythic dictionary message
:param TranslationContainerName
:param C2Name
:param Message the raw message to be translated WITHOUT the UUID in front
:param UUID the extracted UUID from the front of the message
:param MythicEncrypts indicates if Mythic will do the encryption/decryption or not
:param CryptoKeys the encryption/decryption keys Mythic is tracking in case the translation container needs them
"""
def __init__(self,
TranslationContainerName: str,
C2Name: str,
Message: bytes,
UUID: str,
MythicEncrypts: bool,
CryptoKeys: List[CryptoKeys] = []):
self.TranslationContainerName = TranslationContainerName
self.C2Name = C2Name
self.Message = Message
self.UUID = UUID
self.MythicEncrypts = MythicEncrypts
self.CryptoKeys = CryptoKeys

def to_json(self):
return {
"translation_container_name": self.TranslationContainerName,
"c2_profile_name": self.C2Name,
"message": self.Message,
"uuid": self.UUID,
"mythic_encrypts": self.MythicEncrypts,
"crypto_keys": [x.to_json() for x in self.CryptoKeys]
}


class TrCustomMessageToMythicC2FormatMessageResponse:
"""
Response to a request to translate a custom C2 formatted message into Mythic dictionary format
:param Success indicating if this translation was successful or not
:param Error if success is false, what is the error string to report back to the operator
:param Message the Mythic dictionary message
"""
def __init__(self,
Success: bool = False,
Error: str = "",
Message: dict = {}):
self.Success = Success
self.Error = Error
self.Message = Message

def to_json(self):
return {
"success": self.Success,
"error": self.Error,
"message": self.Message
}


class TrMythicC2ToCustomMessageFormatMessage:
"""
Request to translate a Mythic message into a custom C2 format
:param TranslationContainerName
:param C2Name
:param Message the Mythic dictionary message
:param UUID the UUID associated with this message
:param MythicEncrypts does Mythic handle encryption or not, if not, then this function needs to encrypt
:param CryptoKeys the encryption/decryption keys that Mythic is tracking for this UUID
"""
def __init__(self,
TranslationContainerName: str,
C2Name: str,
Message: dict,
UUID: str,
MythicEncrypts: bool,
CryptoKeys: List[CryptoKeys]):
self.TranslationContainerName = TranslationContainerName
self.C2Name = C2Name
self.Message = Message
self.UUID = UUID
self.MythicEncrypts = MythicEncrypts
self.CryptoKeys = CryptoKeys

def to_json(self):
return {
"translation_container_name": self.TranslationContainerName,
"c2_profile_name": self.C2Name,
"message": self.Message,
"uuid": self.UUID,
"mythic_encrypts": self.MythicEncrypts,
"crypto_keys": [x.to_json() for x in self.CryptoKeys]
}


class TrMythicC2ToCustomMessageFormatMessageResponse:
"""
Response to a request for converting a Mythic dictionary message to a custom C2 format
:param Success was this translation successful or not
:param Error if this was not successful, what was the error
:param Message The final message that should be returned back to the agent. No other processing happens to this message after this function, so add the UUID as needed and base64 encode it.
"""
def __init__(self,
Success: bool = False,
Error: str = "",
Message: bytes = b''):
self.Success = Success
self.Error = Error
self.Message = Message

def to_json(self):
return {
"success": self.Success,
"error": self.Error,
"message": self.Message
}


class TranslationContainer:
Expand Down Expand Up @@ -103,22 +301,32 @@ async def handleGenerateKeys(tr_name: str, client):
try:
while True:
stream = client.GenerateEncryptionKeys()
await stream.write(TrGenerateEncryptionKeysMessageResponse(
await stream.write(grpcFuncs.TrGenerateEncryptionKeysMessageResponse(
Success=True,
TranslationContainerName=tr_name
))
logger.info(f"Connected to gRPC for generating encryption keys for {tr_name}")
async for request in stream:
try:
result = await translationServices[tr_name].generate_keys(request)
result.TranslationContainerName = tr_name
await stream.write(result)
result = await translationServices[tr_name].generate_keys(TrGenerateEncryptionKeysMessage(
TranslationContainerName=request.TranslationContainerName,
C2Name=request.C2Name,
CryptoParamName=request.CryptoParamName,
CryptoParamValue=request.CryptoParamValue
))
await stream.write(grpcFuncs.TrGenerateEncryptionKeysMessageResponse(
Success=result.Success,
TranslationContainerName=tr_name,
Error=result.Error,
EncryptionKey=result.EncryptionKey,
DecryptionKey=result.DecryptionKey
))
except Exception as d:
logger.exception(f"Failed to process message:\n{d}")
await stream.write(TrGenerateEncryptionKeysMessageResponse(
logger.exception(f"Failed to process handleGenerateKeys message:\n{d}")
await stream.write(grpcFuncs.TrGenerateEncryptionKeysMessageResponse(
Success=False,
TranslationContainerName=tr_name,
Error=f"Failed to process message:\n{d}"
Error=f"Failed to process handleGenerateKeys message:\n{d}"
))
logger.error(f"disconnected from gRPC for generating encryption keys for {tr_name}")
except Exception as e:
Expand All @@ -129,22 +337,43 @@ async def handleCustomToMythic(tr_name: str, client):
try:
while True:
stream = client.TranslateFromCustomToMythicFormat()
await stream.write(TrCustomMessageToMythicC2FormatMessageResponse(
await stream.write(grpcFuncs.TrCustomMessageToMythicC2FormatMessageResponse(
Success=True,
TranslationContainerName=tr_name
))
logger.info(f"Connected to gRPC for handling CustomC2 to MythicC2 Translations for {tr_name}")
async for request in stream:
try:
result = await translationServices[tr_name].translate_from_c2_format(request)
result.TranslationContainerName = tr_name
await stream.write(result)
grpcCryptoKeys = request.CryptoKeys
localCryptoKeys = []
for keys in grpcCryptoKeys:
localCryptoKeys.append(CryptoKeys(
Value=keys.Value,
DecKey=keys.DecKey,
EncKey=keys.EncKey
))
inputMsg = TrCustomMessageToMythicC2FormatMessage(
TranslationContainerName=request.TranslationContainerName,
C2Name=request.C2Name,
Message=request.Message,
UUID=request.UUID,
MythicEncrypts=request.MythicEncrypts,
CryptoKeys=localCryptoKeys
)
result = await translationServices[tr_name].translate_from_c2_format(inputMsg)
response = grpcFuncs.TrCustomMessageToMythicC2FormatMessageResponse(
Success=result.Success,
Error=result.Error,
TranslationContainerName=tr_name,
Message=json.dumps(result.Message).encode()
)
await stream.write(response)
except Exception as d:
logger.exception(f"Failed to process message:\n{d}")
await stream.write(TrCustomMessageToMythicC2FormatMessageResponse(
logger.exception(f"Failed to process handleCustomToMythic message:\n{d}")
await stream.write(grpcFuncs.TrCustomMessageToMythicC2FormatMessageResponse(
Success=False,
TranslationContainerName=tr_name,
Error=f"Failed to process message:\n{d}"
Error=f"Failed to process handleCustomToMythic message:\n{d}"
))
logger.error(f"disconnected from gRPC for doing custom->mythic c2 for {tr_name}")
except Exception as e:
Expand All @@ -155,22 +384,43 @@ async def handleMythicToCustom(tr_name: str, client):
try:
while True:
stream = client.TranslateFromMythicToCustomFormat()
await stream.write(TrMythicC2ToCustomMessageFormatMessageResponse(
await stream.write(grpcFuncs.TrMythicC2ToCustomMessageFormatMessageResponse(
Success=True,
TranslationContainerName=tr_name
))
logger.info(f"Connected to gRPC for handling MythicC2 to CustomC2 Translations for {tr_name}")
async for request in stream:
try:
result = await translationServices[tr_name].translate_to_c2_format(request)
result.TranslationContainerName = tr_name
await stream.write(result)
grpcCryptoKeys = request.CryptoKeys
localCryptoKeys = []
for keys in grpcCryptoKeys:
localCryptoKeys.append(CryptoKeys(
Value=keys.Value,
DecKey=keys.DecKey,
EncKey=keys.EncKey
))
inputMsg = TrMythicC2ToCustomMessageFormatMessage(
TranslationContainerName=request.TranslationContainerName,
C2Name=request.C2Name,
UUID=request.UUID,
MythicEncrypts=request.MythicEncrypts,
CryptoKeys=localCryptoKeys,
Message=json.loads(request.Message)
)
result = await translationServices[tr_name].translate_to_c2_format(inputMsg)
response = grpcFuncs.TrMythicC2ToCustomMessageFormatMessageResponse(
Success=result.Success,
Error=result.Error,
TranslationContainerName=tr_name,
Message=result.Message
)
await stream.write(response)
except Exception as d:
logger.exception(f"Failed to process message:\n{d}")
await stream.write(TrMythicC2ToCustomMessageFormatMessageResponse(
logger.exception(f"Failed to process handleMythicToCustom message:\n{d}")
await stream.write(grpcFuncs.TrMythicC2ToCustomMessageFormatMessageResponse(
Success=False,
TranslationContainerName=tr_name,
Error=f"Failed to process message:\n{d}"
Error=f"Failed to process handleMythicToCustom message:\n{d}"
))
logger.error(f"disconnected from gRPC for doing mythic->custom c2 for {tr_name}")
except Exception as e:
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.0.6"

PyPi_version = "0.2.8-rc02"
PyPi_version = "0.2.8-rc03"

RabbitmqConnection = rabbitmqConnectionClass()

Expand Down
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.2.8-rc02",
version="0.2.8-rc03",
description="Functionality for Mythic Services",
long_description=README,
long_description_content_type="text/markdown",
Expand Down

0 comments on commit 3a8a2ce

Please sign in to comment.