If you want to have a different form of communication between Mythic and your agent than the specific JSON messages that Mythic uses, then you'll need a "translation container".
The first thing you'll need to do is specify the name of the container in your associated Payload Type class code. Update the Payload Type's class to include a line like translation_container = "binaryTranslator" . Now we need to create the container.
The process for making a translation container is almost identical to a c2 profile or payload type container, we're simply going to change which classes we instantiate, but the rest of it is the same.
Unlike Payload Type and C2 Profile containers that mainly do everything over RabbitMQ for potentially long-running queues of jobs, Translation containers use gRPC for fast responses.
If a translation_container is specified for your Payload Type, then the three functions defined in the following two examples will be called as Mythic processes requests from your agent.
You then need to get the new container associated with the docker-compose file that Mythic uses, so run sudo ./mythic-cli add binaryTranslator. Now you can start the container with sudo ./mythic-cli start binaryTranslator and you should see the container pop up as a sub heading of your payload container.
Additionally, if you're leveraging a payload type that has mythic_encrypts = False and you're doing any cryptography, then you should use this same process and perform your encryption and decryption routines here. This is why Mythic provides you with the associated keys you generated for encryption, decryption, and which profile you're getting a message from.
Python
For the Python version, we simply instantiate our own subclass of the TranslationContainer class and provide three functions. In our main.py file, simply import the file with this definition and then start the service:
For the GoLang side of things, we instantiate an instance of the translationstructs.TranslationContainer struct with our same three functions. For GoLang though, we have an Initialize function to add this struct as a new definition to track.
import ("crypto/rand""encoding/json""errors""github.com/MythicMeta/MythicContainer/logging""github.com/MythicMeta/MythicContainer/translationstructs")var myTranslationService =translationstructs.TranslationContainer{ Name: "myTranslationService", Description: "My test translation service that doesn't actually translate anything", Author: "@its_a_feature_", GenerateEncryptionKeys: func(input translationstructs.TrGenerateEncryptionKeysMessage) translationstructs.TrGenerateEncryptionKeysMessageResponse { response :=translationstructs.TrGenerateEncryptionKeysMessageResponse{ Success: false, }if keys, err :=GenerateKeysForPayload(input.CryptoParamValue); err !=nil { logging.LogError(err, "Failed to determine valid crypto type") response.Error = err.Error()return response } else { response.Success =true response.EncryptionKey = keys.EncKey response.DecryptionKey = keys.DecKeyreturn response } }, TranslateMythicToCustomFormat: func(input translationstructs.TrMythicC2ToCustomMessageFormatMessage) translationstructs.TrMythicC2ToCustomMessageFormatMessageResponse { response :=translationstructs.TrMythicC2ToCustomMessageFormatMessageResponse{}if input.MythicEncrypts {// mythic will take our resulting bytes and encrypt them, so just convert to bytes and returnif jsonBytes, err := json.Marshal(input.Message); err !=nil { response.Success =false response.Error = err.Error() } else { response.Success =true response.Message = jsonBytes } } else { }return response }, TranslateCustomToMythicFormat: func(input translationstructs.TrCustomMessageToMythicC2FormatMessage) translationstructs.TrCustomMessageToMythicC2FormatMessageResponse { response :=translationstructs.TrCustomMessageToMythicC2FormatMessageResponse{} outputMap :=map[string]interface{}{}if input.MythicEncrypts {// mythic already decrypted these bytes, so just convert to map and returnif err := json.Unmarshal(input.Message, &outputMap); err !=nil { response.Success =false response.Error = err.Error() } else { response.Success =true response.Message = outputMap } } else {// we're expected to decrypt the bytes first, then convert them }return response },}funcGenerateKeysForPayload(cryptoType string) (translationstructs.CryptoKeys, error) {switch cryptoType {case"aes256_hmac": bytes :=make([]byte, 32)if _, err := rand.Read(bytes); err !=nil { logging.LogError(err, "Failed to generate new random 32 bytes for aes256 key")returntranslationstructs.CryptoKeys{ EncKey: nil, DecKey: nil, Value: cryptoType, }, err }returntranslationstructs.CryptoKeys{ EncKey: &bytes, DecKey: &bytes, Value: cryptoType, }, nilcase"none":returntranslationstructs.CryptoKeys{ EncKey: nil, DecKey: nil, Value: cryptoType, }, nildefault:returntranslationstructs.CryptoKeys{ EncKey: nil, DecKey: nil, Value: cryptoType, }, errors.New("Unknown crypto type") }}funcInitialize() { translationstructs.AllTranslationData.Get("myTranslationService").AddPayloadDefinition(myTranslationService)}
Then, in our main.go code, we call the Initialize function and start the services:
mytranslatorfunctions.Initialize()// sync over definitions and listenMythicContainer.StartAndRunForever([]MythicContainer.MythicServices{ MythicContainer.MythicServiceTranslationContainer,})
Examples:
These examples can be found at the MythicMeta organization on GitHub:
Docker doesn't allow you to have capital letters in your image names, and when Mythic builds these containers, it uses the container's name as part of the image name. So, you can't have capital letters in your agent/translation container names. That's why you'll see things like service_wrapper instead of serviceWrapper
Just like with Payload Types, a Translation container doesn't have to be a Dockerized instance. To turn any VM into a translation container just follow the general flow at