From 3a8a2ce63b8e8e927f6e6f18e86101d79c3a65d0 Mon Sep 17 00:00:00 2001 From: its-a-feature Date: Wed, 17 May 2023 14:12:34 -0500 Subject: [PATCH] bump 0.2.8-rc03 fixed an issue with c2 profile arrays and translation container message formats --- mythic_container/C2ProfileBase.py | 4 +- mythic_container/TranslationBase.py | 302 +++++++++++++++++++++++++--- mythic_container/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 279 insertions(+), 31 deletions(-) diff --git a/mythic_container/C2ProfileBase.py b/mythic_container/C2ProfileBase.py index ddbb1c5..96e544a 100644 --- a/mythic_container/C2ProfileBase.py +++ b/mythic_container/C2ProfileBase.py @@ -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, diff --git a/mythic_container/TranslationBase.py b/mythic_container/TranslationBase.py index 3421c1f..e748a1a 100644 --- a/mythic_container/TranslationBase.py +++ b/mythic_container/TranslationBase.py @@ -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: @@ -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: @@ -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: @@ -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: diff --git a/mythic_container/__init__.py b/mythic_container/__init__.py index feabe16..b7726c8 100644 --- a/mythic_container/__init__.py +++ b/mythic_container/__init__.py @@ -3,7 +3,7 @@ containerVersion = "v1.0.6" -PyPi_version = "0.2.8-rc02" +PyPi_version = "0.2.8-rc03" RabbitmqConnection = rabbitmqConnectionClass() diff --git a/setup.py b/setup.py index 2a7811b..44f53cf 100755 --- a/setup.py +++ b/setup.py @@ -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",