# Common Errors Source: https://docs.mythic-c2.net/version-4.0/common-errors Discussion / Explanation for Common Errors ## Payload container, X, of version Y is not supported All Payload containers leverage the `mythic_container` [PyPi package](https://github.com/MythicMeta/Mythic_PayloadType_Container) or the [Golang Package](https://github.com/MythicMeta/MythicContainer). These packages keeps track of a version that syncs up with Mythic when the container starts. As Mythic gains new functionality or changes how things are done, these containers might not be supported anymore. At any given time, Mythic could support only a single version or a range of versions. ### How do I fix this? The agent in question needs to have its container updated or downgraded to be within the range specified by your version of Mythic. This likely means either updating the installed container or downgrading to a previously tagged version. ## Failed to correlate UUID, X, to something Mythic knows Failed to correlate UUID The "Failed to correlate UUID" message means that data came in through some C2 Profile, made its way to Mythic, Mythic base64 decoded the data successfully and looked at the first characters for a UUID. In Mythic messages, the only piece that's normally not encrypted is this random UUID4 string in the front that means something to Mythic, but is generally meaningless to everybody else. Mythic uses that UUID to look up the callback/payload/stage crypto keys and other related information for processing. In this case though, the UUID that Mythic sees isn't registered within Mythic's database. Normally people see this because they have old agents still connecting in, but they've since reset their database. Looking at the rest of the message, we can see additional data. All C2 Profile docker containers add an additional header when forwarding messages to the Mythic server with `mythic: c2profile_name`. So, in this case we see `'mythic': 'http'` which means that the `http` profile is forwarding this message along to the Mythic server. ### How do I fix this? First check if you have any agents that you forgot about from other engagements, tests, or deployments. The error message should tell you where they're connecting from. If the UUID that Mythic shows isn't actually a UUID format, then that means that some other data made its way to Mythic through a C2 profile. In that case, check your Firewall rules to see if there's something getting through that shouldn't be getting through. This kind of error does not impact the ability for your other agents to work (if they're working successfully), but does naturally take resources away from the Mythic server (especially if you're getting a lot of these). ## I installed an agent, but it's not showing up If you use `mythic-cli` to install an agent, but you're not seeing it show up in the UI, it means that something is going wrong with booting up the agent and syncing to Mythic. ### How do I fix this? Run `sudo ./mythic-cli logs [agent name]` to look at the output of the container. Usually you'll see some sort of error here about why things aren't working. This is typically the result of an agent/c2 profile being too far out of date from the rest of the Mythic instance that it can't properly sync up anymore. # Change Log Source: https://docs.mythic-c2.net/version-4.0/common-questions-and-answers/change-log ## Mythic 4.0 Mythic 4.0 is a major compatibility boundary. The release adds operation chat, scoped opaque API tokens, operator aliases, task references, richer payload and C2 parameters, wrapper build metadata, interactive eventing approvals, offset/resumable transfers, interactive file editing, managed C2-hosted files, richer credentials, and agent RPC. It also modernizes task output, callback workflows, service pages, search, and documentation navigation. Integrations must move to Bearer authentication, recreate API tokens, remove `/api/v1.4`, adopt camelCase actions and display-ID arguments, and update all container libraries. See [Mythic 3.4 -> 4.0 Updates](/version-4.0/updating/mythic-3.4-greater-than-4.0-updates) for exact mappings and migration examples. The detailed historical entries below document earlier releases. What's new for 2.3? * New React-based User Interface * New Parameter group based command parsing when tasking * Users can tab-complete command parameters * Parameter Groups allow for conditional parameter sets * Tasking tracks if issued from command line, parsed cli, modals, browser scripts * Updated MITRE ATT\&CK mappings * Reporting updated to use LaTeX for PDF generation * can generate PDF, raw LaTeX, or JSON output * Reporting can now filter out callbacks, users, or hosts * Reporting has an option for a MITRE ATT\&CK overview with counts at the end * Browser Scripting updated * Browser scripts now return dictionaries instead of raw HTML * Users can issue follow-on tasks right from browser scripts * Browser Scripts can generate tables, download links, links to search pages, and screenshot buttons * Improved search page * Can search across tasking, callbacks, files, screenshots, tokens, and more * Searches can be saved and linked to via the updated URL bar * Translation containers now tracked in the UI and indicated when they are associated or not associated to payload type containers * Improved information for C2 profile status (container running vs internal server running) * Payloads page shows which C2 profiles are loaded into the payloads and if those profiles are currently running or not * You can re-trigger a payload to build from the payloads page without having to go through all the steps again * C2 profiles can expose redirect rules and check agent configurations to make sure everything aligns well between the two. These functions exist within the C2 Profile container and are taskable through the payloads page in the UI. * The main callbacks page has new icons for the egress route of agents (wifi or link icon) that, when clicked, will show a graph view of the egress path of that agent's traffic. * The main callbacks page can track the sleep information for a callback automatically (based on the agent updating it for the callback) and display it (orange or blue alarm clock icon) * All columns on the main callbacks page are filterable based on data or can be hidden completely by right clicking on the column headers. * This also applies to all of the tables generated via browser scripting * Response output is now paginated to help reduce the stress of streaming a lot of output for agents * Response tables in from browser scripts are now virtualized to help reduce the stress of displaying hundreds or thousands of rows at a time * Normal output is now displayed via an Ace editor, so if the output is JSON, you can collapse/expand sections like a normal text editor * Graph view now uses Dagre for D3 rendering of graphs and automatically groups agents together on the same host for easier reading * The display can be changed via the dropdown on the top left for: * Left-to-right or bottom-to-top directions * Showing all callbacks or just the active ones * Showing all edges or only the active edges * The resulting graph can be saved as a PNG for easier inclusion in reports * GraphQL endpoints have been added for the new React user interface * the scripting PyPi package will eventually move to use these instead of the REST endpoints * ## What's new for 2.2? * "translation container" support for agent's that want their own custom c2 message spec and support for arbitrary other encryption outside of the default Mythic supported ones * OPSEC scripting checks, logging, bypasses for tasking * Solidified P2P spec - [Delegates](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/delegates) and [P2P Connections](/version-4.0/customizing/hooking-features/linking-agents/action-p2p_info) * Alpha version of new back-end/UI updates for nginx reverse proxy, hasura graphql, and react * All Payload Types moved out of main Mythic repo and into Mythic Agents organization * Track sleep information in UI in easy-to-see form (new UI only) * Track linked agent info in the table ui form of working with callbacks (new ui only) * New way of viewing linked agents in the UI using dagre-d3 instead of raw d3 (new ui only) * Commands track which OS they support. This helps things payload types like poseidon that might have os-specific commands since golang can compile to multiple operating systems * Deleted payloads can't be used to create new callbacks - instead, mythic will generate a warning in the UI alerting you with information about the payload, but the agent will get a 404 and blank message back. * De-duplication of alert messages * Tasks can change their `display_params` in the `create_tasking` function to alter what user's see - this helps with ugly large json blocks from using structured input. The original parameters can still be queried through the UI if the user wants to see the original JSON * Tasks can set stdout/stderr during `create_tasking` to help with debugging or recreating artifacts * Payload creation can track its own `build_message` , `build_stderr` , and `build_stdout`for stdout and stderr messages to make it easier to see what happened during creation. This split information is also viewable via the Created Payloads page. * Command parameters can opt to have their choices be all commands for a payload type or all loaded commands * Command parameters can filter down their choices based on command attributes (supported os and if they support process injection) * Password policy now added - requires all passwords to be at least 12 characters long * Mythic scripting (i.e. the `mythic` PyPi package) has been updated to version 0.0.19 to support these changes * C2 Profile specific OPSEC checks - provide an `opsec` function to your C2 Profiles to check if the parameters that the operator selected pass your risk-level before even allowing a payload to be created. * RPC Calls are updated within the Payload Type containers to allow for dynamic queries and updates. New RPC calls just import one file * Removed ability for users to create new accounts on the login page * Re-build payload option from the created payloads page * Export payload from created payloads page and import it in the payloads creation page to trigger a new payload * Starting Mythic with `MYTHIC_DEBUG` environment variable or `debug` set to true in the `mythic-docker/config.json` file will cause mythic to be extremely verbose in agent messages to help with debugging development * Merged in a pull request to update ATT\&CK to ATT\&CK v9 The following are the breaking changes for this version * For detailed steps on how to make updates as a developer, check out the [updating guide](https://github.com/MythicMeta/Documentation/blob/primary/updating/mythic-2.1-greater-than-2.2-updates/agents-2.1.-greater-than-2.2.2) for how to take your 2.1.\* agent into the 2.2.2 Mythic * All Payload Types and C2 Profiles are split out from the main Mythic repo. If you just install mythic and start it, there will be *no* payload types or c2 profiles that sync in. You need to leverage the installer script to install additional agents from GitHub * To help support this and keep track of these sorts of updates, containers now have "versions" and the main Mythic server has a range of supported container versions. If you have a container that tries to check in that's outside that range, you'll get a warning in the Mythic UI. * All Payload Types need to do the following: * Control for container files have been moved into PyPi packages rather than just raw files on disk so that updating them will be easier going forward. The current version needed for this is version 0.0.42 (yes, lots of development has been going on): `pip install mythic_payloadtype_container==0.0.42`. * If you are using a DockerImage from the `itsafeaturemythic` repo, update to the latest: * csharp\_payload==0.0.11 * python38\_payload==0.0.4 * xgolang\_payload==0.0.9 * leviathan\_payload==0.0.4 * Updating to using a PyPi package instead of local files means that you need to adjust the imports for all of the Python files for your agents. See [Payload Type Definition](/version-4.0/customizing/payload-type-development/payload-type-info/payload-type-definition#30-building). * All C2 Profiles need to do the following: * Control for container files has been moved into PyPi packages rather than just raw files on disk so that updating them will be easier going forward. The current version needed for this is version 22: `pip install mythic_c2`\_container`==0.0.22` * Updating to using a PyPi packages instead of local files means that you need to adjust the imports for all of the Python files for your C2 profiles. See [C2 Docker Containers](/version-4.0/customizing/c2-related-development/server-side-coding/2.3.1-c2-docker-containers). * Mythic crypto is defined by the Payload Type now rather than the C2 profile. This is part of a change to keep each container in charge of just one thing. Payload Types by default have Mythic handle their crypto, to have a Payload Type do something *other* than what Mythic does, set `mythic_encrypts = False` in the builder file for your Payload Type. * To more easily support various crypto components going forward, the selection and usage of Crypto parameters in C2 profiles has changed slightly. There's no longer a requirement that the parameter name be `AESPSK`. Instead, there is another field you can specify for any parameter that `crypto_type=True`. This specifies that the resulting thing that the user selects/inputs defines what kind of crypto to use. This is simply a boolean value so that you can still leverage the C2 Parameter as normal (string input, choose one, etc) with the expectation that the final value is the type of crypto. In the case of Mythic's standard crypto, the value would be `aes256_hmac`. This means that Mythic no longer auto-generates a base64 of an AES256 key when displayed to the user, this happens behind the scenes if the resulting type is `aes256_hmac`. * This also causes some variation when building your payload. Normally, you get a dictionary of \{"key": "value"} for each C2 Profile parameter for you to leverage when building your payload. For crypto though, this could be highly variable and the components that you want to leverage could vary widely. So, if the parameter has `crypto_type=True`, then you'll get a dictionary of values. This is split out by type, encryption key, and decryption key because you might want to leverage some pub/priv key asymmetric crypto where those pieces are different or you might want to leverage some other kind of symmetric crypto. ``` { "key": { "crypto_type": "the value that the user selected, like aes256_hmac", "enc_key": "base64 of the encryption key", "dec_key": "base64 of the decryption key" } } ``` * Make sure you update your pip install of the `mythic==0.0.19` package for scripting to handle the updated aspects of these objects. ## What Changed for 2.0? * Rebranded Apfell to Mythic * Added File Browser support to the apfell and poseidon agents * Added SOCKS5 support to poseidon * Added user roles during operations (operator, developer, spectator) * Added a documentation docker container with more verbose details including overviews, traffic flows, opsec considerations, and detailed help/info * Updated the general UI for Mythic * Updated the event logging system for "warnings" that can be resolved * Added ability for search to look through file browsing and eventing data as well * Operator feedback if their parameters don't meet certain validation requirements before the commands even make it to the agents * Complete restructuring of how a developer uses/creates agents/c2 profiles * Added RPC call functionality from payload type and c2 profile containers to the main mythic instance to start hooking into the back-end for scripting ## What changed for 1.4? * Toggle timestamp view in the UI to see either UTC timestamps or localized timestamps * Persistent, unified processes listings based on the host * Persistent, unified file browser * Badges to see the number of new tasks on a callback you're not currently viewing * More malleable HTTP-based C2 profile with a JSON config * Cookie Support * Arbitrary transforms to text (base64, append, prepend, add random values, etc etc) * Proxy Support * User Agent strings * Slack notifications on new callbacks * The ability to have this only fire for specific payloads (i.e. you might not want it to happen for all of your lateral movement payloads, but do want it to fire for your phishing payloads) * Exportable search results (just export the page or the entire results of a search query) * Exportable artifact search results (just the page or the entire results) * More granular search controls * Filter search by operator * Duplicate saved parameters for c2 profiles to make quick edits * Edit credentials that are saved in the database * Add comments to the saved credentials * Export final report in JSON format * Export information about downloaded / uploaded files * Automatically calculate MD5/SHA1 of files * Badge notifications and searching for Keylogging * Task comments shown when viewing files * Download command output as a text file * Selective caching of responses so large output doesn't slow down the entire callback * Confirmation for mass exit/remove via the UI of callbacks * Issuing a command that causes a parameter popup will first try to auto-populate the values with the last instance of the command you ran on that callback * Save current browsing view in main callback window across refreshes (open tabs) * 8hr default token expiration and browser will auto-renew periodically so that there are fewer interruptions in longer ops * Use tab to select autocomplete option and up/down arrows to toggle through autocomplete options * Use `ctrl+[` and `ctrl+]` to navigate previous/next tab in the `active callbacks` tab. * set callback description from command line via `set description my description` and reset it back to the default for the payload with `set description reset` * Your open tabs for the active callbacks view will be remembered per-browser * View Apfell's web log from within the main web UI * Only start select payload type containers when starting Apfell - `./start_apfell.sh viper` will only start the `viper` payload type container, but all of the c2 profiles and main containers will still start. * Turn arbitrary VMs into Apfell compatible containers * Import and export single commands at a time instead of the whole payload type * C2 profiles support jitter percentages * Kill Dates in C2 profiles * Bulk download files from downloads page as zip file * Strict argument checking on tasking to make sure required parameters are given * Provide a single streaming list of tasking for all callbacks combined that the operation lead can watch * Filter callback viewable tasks to a single operator or all operators, certain command, and certain task ranges * Add new buttons for hiding or exiting multiple callbacks at once * C2 profiles have associated Notes, Sample Server configuration, and Sample Client configurations that are present from the main UI to give context and base configuration information. This will be particularly useful as C2 profiles grow more complex. * Provide and an interface for supporting P2P communications and visualizations * Provide a recommended style guide for how to create new payloads to best fit in and leverage all of Apfell's features * Integration of Poseidon Agent * Integration of Atlas Agent * Integration of Chrome-extension Agent * Introduction of command-line short-hands such as swap\_filenames to allow operators to type uploaded filenames, but have agents get file IDs instead. * All files are now contained within the Apfell folder, no more docker volumes mounted in weird spots automatically * Edit C2 profile code and configurations right in the browser * Included an 'event log' to see all events Apfell is doing behind the scenes as well as allow operators to 'chat' and store messages within their operation. If an operator sends a message on this screen, it'll be shown to all operators in that operation that are on the Active Callbacks page. * Included a 'web log' to see all the web requests to Apfell in the UI * Broke out transforms for commands and create/load operations to mirror the style of browser scripts * Exporting/Importing a payload type will bring along with it: payload code, all associated transforms, all command code, all browser scripts, and all c2 profile associated code. # FAQ / Troubleshooting Tips Source: https://docs.mythic-c2.net/version-4.0/common-questions-and-answers/faq-troubleshooting-tips This page will keep a running set of frequently asked questions or troubleshooting tips This likely means that the postgres container can't bind to the port it needs. Check to make sure there isn't already something listening locally on 5432. If you're using Kali Linux, it's likely that metasploit is already using that port for its postgres database. There a few common possibilities: * **Wrong IP/Port/Protocol somewhere**. All of the C2 profile docker containers simply accept connections from agents, remove any special C2 magic that you're using, and redirect the actual agent message to your Mythic instance. * Default Mythic UI is on port 7443 with SSL. The `HTTP` profile defaults to HTTP (no SSL) and port 80. When creating the callback host for your payload, specify either `http://mythicIP` to connect back directly through the C2 container or specify your redirector and make sure your redirector is set to go back to the right IP:Port combination. * **C2 Server isn't running**. All C2 components are composed of 2 pieces - the Docker container and the service running within it. Go to the "Global Configurations" -> "C2 Profiles" page and check the corresponding row for your profile. If there's a grey button saying to "Start Internal Server", click it to issue the start command to the Docker container. * **C2 Server is running, but no callbacks**. Depending on the C2 Profile you're using, there's likely a True/False indicator for debugging information (this is the case with the built-in profiles). Make sure that is set to True in the JSON configuration (restart your C2 profile if necessary). On the "Global Configurations" -> "C2 Profiles" page, when the C2 Profile is running there will be a red button with a dropdown. Click that dropdown and select to "View Stdout/Stderr". This will show you the latest un-read messages from the C2 profile debugging information. This will give some information about if the Docker container is even getting the connections or not. We've seen this a few times when people are on smaller VMs or pretty full VMs and are trying to include Poseidon. The Poseidon container is about 3.5GB currently since it contains the SDKs and information for cross compiling golang for macOS and Linux. We're working on making that container smaller and removing all the pieces we aren't using, but the easiest step here is to free up some room. # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/customizing-public-agent ## My Changes aren't working You installed a service into Mythic that's not yours (agent, c2, webhook, etc), made a change, but you're not seeing it? That could be from forking a public agent, making changes in your own repo and installing it with `./mythic-cli install `or just making local changes on disk. Luckily, there's a really easy solution to this. This page walks through the various things covered in this blog post as well: [https://medium.com/@its\_a\_feature\_/agent-customization-in-mythic-tailoring-tools-for-red-team-needs-1746fd02177f](https://medium.com/@its_a_feature_/agent-customization-in-mythic-tailoring-tools-for-red-team-needs-1746fd02177f) ### Remote Images Docker containers are really amazing. They rely on "images" to create a kind of "snapshot" of a simulated VM and then turn that image into an instance of that running snapshot by creating a container. These images can do a lot of things and configure a lot of different components for you so that you can be absolutely sure that how something is set up in one environment matches another environment regardless of whatever else is installed or set up. To do this, when building the image, you identify packages to install, things to configure, build new binaries, etc. The downside is that creating the image in the first place can be very taxing for a CPU and for the HDD. They can be building python from source and ballooning the size of intermediary layers all over the place. To help with this, some authors of Mythic services have opted to use remote images. This means that the images are already pre-built for the general case and hosted somewhere (GitHub, DockerHub, etc). If you're ever curious about an agent using a remote image, you can check [https://mythicmeta.github.io/overview/](https://mythicmeta.github.io/overview/) and look at the Docker Image column. If there's something there, then the service is going to use the image hosted there by default. If you want to check locally, you'll see three new variables in your `.env` about it. For example, let's say we installed `Poseidon`: ```env theme={"system"} POSEIDON_USE_BUILD_CONTEXT="true" POSEIDON_USE_VOLUME="false" POSEIDON_REMOTE_IMAGE="ghcr.io/mythicagents/poseidon:v0.0.0.14" ``` You'll see three new `.env` variables all prefixed with the name of the thing you installed. ### \*\_USE\_BUILD\_CONTEXT The `*_USE_BUILD_CONTEXT` variable says whether or not to use the LOCAL build context to create an image or to instead use the specified `*_REMOTE_IMAGE` that's pre-built. This means that when this variable is `false` (the default), then no new local changes will be used and the pre-built image will simply be fetched and turned into a container. So, no matter how many local changes you make, you'll never see the changes. Setting this to `true` means that the local `Dockerfile` will be used to generate the image you use for your container. It's *most likely* the case that this Dockerfile is set up to pull in your local changes when creating the image, rebuilding things as necessary. If it's not though, then your local Dockerfile will be used to generate a new local image, but it doesn't *guarantee* that your local changes are getting picked up. So, be sure to check the `Dockerfile` and if necessary, check for a `.docker/Dockerfile` that you might be able to copy from to make sure that your changes are used when generating the new image. ### \*\_USE\_VOLUME By default, the local folder in Mythic/InstalledServices is mounted into the container at `/Mythic`. This means that when local things change (such as uploading a file into a container), it goes on disk where the service is installed. Alternatively, if this is `true`, then a new Docker Volume is created and mounted inside the container at `/Mythic` instead. ### Changes to \*\_USE\_BUILD\_CONTEXT or \*\_USE\_VOLUME If you make a change to either of these two variables, you need to rebuild the container to make them apply. Simply run `sudo ./mythic-cli build [name]` and you should see your changes. # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features All of the following features describe information that can be included in responses. These sections describe some additional JSON formats and data that can be used to have your responses be tracked within Mythic or cause the creation of additional elements within Mythic (such as files, credentials, artifacts, etc). All of these fields are part of the `responses` array that can be included in both a `get_tasking` and a `post_response` message. You can hook multiple features in a single response because they're all unique. For example, to display something to the user, it should be in the `user_output`field, such as: ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "uuid", "user_output": "Still working" } ] } or even { "action": "post_response", "responses": [ { "task_id": "uuid", "user_output": "{\"key\": \"nested json for user as string\"}" } ] } ``` ### Reserved Keywords When we talk about `Hooking Features` in responses array of an agent messages, we're really talking about a specific set of Dictionary key value pairs that have special meaning. All responses from the agent to the Mythic server already have to be in a structured format. Each of the following sections goes into what their reserved keywords mean, but some simpler ones are: * task\_id - string - UUID associated with tasks * user\_output - string - used with any command to display information back to the user * completed - boolean - used with any command to indicate that the task is done (switches to the green completed icon) * status - string - used to indicate that a command is not only done, but has encountered an error or some other status to return to the user * process\_response - this is passed to your command's python file for processing in the `process_response` function. You only need to do this if there's some extra thing you want to have execution around based on the data here. ## PayloadType Development Reference As you're developing an agent to hook into these features, it's helpful to know where to look if you have questions. All of the Task, Command, and Parameter definitions/functions available to you are defined in the `mythic_container` PyPi package, which is hosted on the MythicMeta Organization on GitHub. Information about the Payload Type itself (BuildResponse, SupportedOS, BuildParameters, PayloadType, etc) can be found in the [PayloadBuilder.py](https://github.com/MythicMeta/Mythic_PayloadType_Container/blob/master/mythic_payloadtype_container/PayloadBuilder.py) file in the same PyPi repo. ## Message Keywords and Structure Throughout this section, the payload type development section, and the c2 message format sections, you'll see a lot of information about message structure. Here is a quick "cheat sheet" reference guide with links to the appropriate sections for more information. The following is an example of a `get_tasking` request to Mythic with almost every possible field added: ```json theme={"system"} { "action": "get_tasking", "tasking_size": 1, "responses": [ { "task_id": "uuid", "user_output": "something to show to the user", "completed": false, "status": "custom status here", "file_browser": { "host": "abc.com", "is_file": false, "permissions": { "customField": "customValue" }, "name": "C:\\", "parent_path": "", "success": true, "access_time": 1700164038000, "modify_time": 1700164038000, "size": 2300, "update_deleted": false, "files": [ { "is_file": false, "permissions": { "customField": "custom value" }, "name": "Users", "access_time": 1700164038000, "modify_time": 1700164038000, "size": 12345 } ] }, "removed_files": [ { "host": "abc.com", "path": "C:\\Users\\itsafeature\\Desktop\\evil.exe" } ], "credentials": [ { "credential_type": "plaintext", "realm": "domain.com", "account": "itsafeature", "credential": "oh no my password!", "comment": "scraped from lsass", "metadata": "anything else you want to add" } ], "artifacts": [ { "base_artifact": "Process Create", "artifact": "cmd.exe /C evil.exe", "host": "abc.com" } ], "processes": { "host": "abc.com", "update_deleted": false, "processes": [ { "process_id": 245, "parent_process_id": 244, "architecture": "x64", "bin_path": "C:\\Users\\itsafeature\\Desktop\\evil.exe", "name": "evil.exe", "user": "itsafeature", "command_line": "C:\\Users\\itsafeature\\Desktop\\evil.exe -f 2", "integrity_level": 2, "start_time": 1700164038000, "description": "totally not malware: TM", "signer": "", "protected_process_level": 0, } ], }, "edges": [ { "source": "my uuid", "destination": "uuid of remote callback", "action": "remove", "c2_profile": "smb", } ], "commands": [ { "action": "add", "cmd": "shell" } ], "keylogs": [ { "window_title": "Notepad", "user": "itsafeature", "keystrokes": "password: abc123" } ], "tokens": [ { "action": "add", "token_id": 34857, "user": "acme\\bob", "groups": "", "privileges": "", "thread_id": 12345, "process_id": 2344, "session_id": 1, "logon_sid": "", "integrity_level_sid": "" "restricted": false, "default_dacl": "", "handle": 0, "capabilities": "", "app_container_sid": "", "app_container_number": 0 } ], "callback_tokens": [ { "action": "add", "host": "abc.com", "token_id": 34857, "token": { // same info from tokens if you wanted to add/update that data } } ], "download": { "total_chunks": 4, "chunk_size": 512000, "host": "abc.com", "is_screenshot": false, "filename": "evil.exe", "full_path": "C:\\Users\\itsafeature\\Desktop\\evil.exe", }, "upload": { "file_id": "uuid here", "host": "abc.com", "chunk_size": 512000, "chunk_num": 1, "full_path": "C:\\Users\\itsafeature\\Desktop\\replaced.exe" }, "alerts": [{ "alert": "lost connection to remote agent", "level": "warning", "source": "disconnection warning", "send_webhook": false, }], "process_response": { "custom field": "custom val" }, "agent_rpc": { "name": "do something", "arguments": "whatever i want of any type as long as it's JSON serializable" } } ], "alerts": [{ "alert": "edr detected", "level": "warning", "source": "edr detection", "send_webhook": true, "webhook_alert": { "edr": "some edr name", "pid": 345 } }], "edges": [{ "action": "add", "source": "my uuid", "destination": "remote uuid", "c2_profile": "smb", "metadata": "anything else you want to add about the connection" }], "delegates": [{ "c2_profile": "tcp", "message": "base64 message", "uuid": "some uuid tracker here" }], "socks": [{ "server_id": 2345, "data": "base64", "exit": false }], "rpfwd": [{ "server_id": 12345, "data": "base64", "exit": false }], "interactive": [{ "task_id": "uuid of task that started interactive session", "message_type": 0, "data": "base64" }], } ``` * [Delegates](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/delegates) * [Socks](/version-4.0/customizing/hooking-features/socks) * [Rpfwd](/version-4.0/customizing/hooking-features/rpfwd) * [Interactive](/version-4.0/customizing/hooking-features/interactive-tasking) * [Edges](/version-4.0/customizing/hooking-features/linking-agents/action-p2p_info) * [Alerts](/version-4.0/customizing/hooking-features/alerts) * [Upload](/version-4.0/customizing/hooking-features/action-upload) * [Download](/version-4.0/customizing/hooking-features/download) * [Callback Tokens](/version-4.0/customizing/hooking-features/tokens) * [Tokens](/version-4.0/customizing/hooking-features/tokens) * [Keylogs](/version-4.0/customizing/hooking-features/keylog) * [ProcessResponse](/version-4.0/customizing/payload-type-development/process-response) * [Commands](/version-4.0/customizing/hooking-features/commands) * [Processes](/version-4.0/customizing/hooking-features/process_list) * [Artifacts](/version-4.0/customizing/hooking-features/artifacts) * [Credentials](/version-4.0/customizing/hooking-features/credentials) * RemovedFiles * [FileBrowser](/version-4.0/customizing/hooking-features/file-browser) # File Uploads (Mythic -> Agent) Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/action-upload Upload a file from Mythic to the target ## What does it mean to upload a file? This section is specifically for uploading a file from the Mythic server to an agent. Because these messages aren't just to an agent directly (they can be routed through a p2p mesh), they can't just be as simple as a GET request to download a file. The file needs to be chunked and routed through the agents. This isn't specific to the `upload` command, this is for any command that wants to leverage a file from Mythic. In general, there's a few steps that happen for this process (this can be seen visually on the [Message Flow](/version-4.0/message-flow#file-uploads-from-mythic-greater-than-agent) page): 1. The operator issues some sort of tasking that has a parameter type of "File". You'll notice this in the Mythic user interface because you'll always see a popup for you to supply a file from your file system. 2. Once you select a file and hit submit, Mythic loops through all of the files selected and registers them. This process sends each one down to Mythic, saves it off to disk, and assigns it a UUID. These UUIDs are what's stored in place of the raw bytes when you submit your task. So, if you had an upload command that takes a file and a path, your arguments would end up looking like `{"file":"uuid", "path": "/some/path"}` rather than `{"file": raw bytes of file, "path": "/some/path"}`. 3. In the Payload Type's corresponding command python file there is a function called `create_go_tasking` that handles the processing of tasks from users before handing them off to the database to be fetched by an agent. If your agent supports chunking and transferring files that way, then you don't *need* to do anything else, but if your agent requires that you send down the entire file's contents as part of your parameters, you need to get the associated file. 4. To get the file with Mythic, there's an RPC calls we can do: ```python Get File Contents {6-8} theme={"system"} async def create_go_tasking( self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) file_resp = await SendMythicRPCFileGetContent(MythicRPCFileGetContentMessage( AgentFileId=taskData.args.get_arg("file"), # <-- assuming the name of the parameter was called "file" )) if file_resp.Success: file_resp.Content # <-- this is the raw file content else: raise Exception("Error from Mythic: " + str(file_resp.error)) return response ``` ```python Get File Metadata {6-9} theme={"system"} async def create_go_tasking( self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( TaskID=taskData.Task.ID, AgentFileID=taskData.args.get_arg("file") )) if file_resp.Success: if len(file_resp.Files) > 0: response.DisplayParams = f"{file_resp.Files[0].Filename} else: raise Exception("Error from Mythic: " + str(file_resp.error)) return response ``` The highlighted lines are where we do an RPC call with Mythic to search for files, specifically, we want ones where the file\_id matches the one that was passed down as part of our parameters. This should only return one result, but the result, for consistency, will always come back as an Array. We can use this to get the original filename back out from Mythic from the user's upload command (in the `Get File Metadata` example). If there's something we want to modify about the file (such as adding a comment automatically or indicating that the file should be deleted from disk after the agent fetches it) we can use the `SendMythicRPCFileUpdate` RPC function to do so. At this point, if you wanted to use the raw bytes of a file instead of the UUID as part of your tasking, you should use the `Get File Contents` example above to fetch the actual contents. Then you can then swap out the contents of the parameter with `taskData.args.add_arg("arg name", "base64 of file contents here")`. If you want to register a NEW file with Mythic from the payload container that the user didn't first upload, you need to use the `SendMythicRPCFileCreate` RPC call. 4\. The agent gets the tasking and sees that there's a file UUID it needs to pull. It sends an initial message to Mythic saying that it will be downloading the file in chunks of a certain size and requests the first chunk. If the agent is going to be writing the file to disk (versus just pulling down the file into memory), then the agent should also send `full_path`. This allows Mythic to track a new entry in the database with an associated task for uploads. 5\. The Mythic server gets the request for the file, makes sure the file exists and belongs to this operation, then gets the first chunk of the file as specified by the agent's `chunk_size` and also reports to the agent how many chunks there are. 6\. The Agent can now use this information to request the rest of the chunks of the file. The agent reporting back `full_path` is what allows Mythic to track the file in the Files search page as a file that has been written to disk. If you don't report back a `full_path` or have `full_path` as an empty string, then Mythic thinks that the file transfer only lived in memory and didn't touch disk. This is separate from reporting that a file was written to disk as part of artifact tracking on the Reporting Artifacts page. It's not an extremely complex process, but it does require a bit more back-and-forth than a fire-and-forget style. The rest of this page will walk through those steps with more concrete code examples. There is no expectation when doing uploads or downloads that the operator must type the absolute path to a file, that's a bit of a strict requirement. Instead, Mythic allows operators to specify relative paths and has an option in the upload action to specify the actual full path (this option also exists for downloading files so that the absolute path can be returned for better tracking). This allows Mythic to properly track absolute file system paths that might have the same resulting file name without an extra burden on the operator. ## Example (agent pull down): Files can (optionally) be pulled down multiple times (if you set `delete_after_fetch` as `True`, then the file won't exist on disk after the first fetch and thus can't be re-used). This is to prevent bloating up the server with unnecessary files. An agent pulling down a file to the target is similar to downloading a file from the target to Mythic. The agent makes the following request to Mythic: ```json theme={"system"} { "action": "post_response", "responses": [ { "upload": { "chunk_size": 512000, //bytes of file per chunk "file_id": UUID, //the file specified to pull down to the target "chunk_num": #, // which chunk are we currently pulling down "full_path": "full path to uploaded file on target" //optional }, "task_id": task_id // the associated task that caused the agent to pull down this file }] } ``` The `chunk_num` field is 1-based. So, the first chunk you request is `"chunk_num": 1`. The `full_path` parameter is helpful for accurate tracking. This allows an operator to be in the `/Temp` directory and simply call the upload function to the current directory, but allows Mythic to track the full path for easier reporting and deconfliction. The `full_path` parameter is only needed if the agent plans to write the file to disk. If the agent is pulling down a file to load into memory, then there's no need to report back a `full_path`. The agent gets back a message like: ```json theme={"system"} { "action": "post_response", "responses": [ { "status": "success or error", "error": "error message if status is error, otherwise key not present", "total_chunks": #, // given the previous chunk size, the total num of chunks "chunk_num": #, //the current chunk number Mythic is returning "chunk_data": "base64_of_data", // the actual file data, "file_id": "file id that was requested", "task_id": "UUID of task" // task id that was presented in the request for tracking } ] } ``` This process repeats as many as times as is necessary to pull down all of the contents of the file. If there is an error pulling down a file, the server will respond with as much information as possible and blank out the rest (i.e.: `{'action': 'post_response', 'responses': [ {'total_chunks': 0, 'chunk_num': 0, 'chunk_data': '', 'file_id': '', 'task_id': '', 'status': 'error', 'error': 'some error message'} ] }`) If the task\_id was there in the request, but there was an error with some other piece, then the task\_id will be present in the response with the right value. ### Files in the Tasking JSON There's a reason that files aren't base64 encoded and placed inside the initial tasking blobs. Keeping the files tracked by the main Mythic system and used in a separate call allows the initial messages to stay small and allows for the agent and C2 mechanism to potentially cache or limit the size of the transfers as desired. Consider the case of using DNS as the C2 mechanism. If the file mentioned in this section was sent through this channel, then the traffic would potentially explode. However, having the option for the agent to pull the file via HTTP or some other mechanism gives greater flexibility and granular control over how the C2 communications flow. # Actions Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/actions ## What are actions? Actions are what indicate to Mythic what kind of message you're sending. There's `staging_rsa` (for doing EKE staging), `checkin` for going from payload -> callback, `post_response` for exclusively sending output about tasks, and `get_tasking` which allows you to get tasks as well as sending output about tasks. * [Action: checkin](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/initial-checkin) - Initial checkin messages and key exchanges * [Action: get\_tasking](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action_get_tasking) - Sending response data for tasks, socks, rpfwd, delegates, but ALSO getting new tasks. * [Action: post\_response](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response) - Sending response data for tasks, socks, rpfwd, delegates, but NOT getting new tasks. The main difference between `get_tasking` and `post_response` is just that you can get new tasks from `get_tasking`. Otherwise, everything you can send / get as part of a `post_response` message *also* applies to `get_tasking` messages. All of the features listed in this "Hooking Features" section apply to both `get_tasking` and `post_response` messages because they can both include a `responses` array. # Alerts Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/alerts ## Agent generated alerts Sometimes you want your agent to be able to bring something to the operator's attention. Your task might return an error, but if it's a long-running task, then who knows if the operator is actively looking at the task. ### alerts Using the `alerts` keyword, agents can sent alert messages to Mythic's operation event log. There are two ways you can do this: #### As part of a task As part of a task, you can use another keyword, `alerts` to send the following structure: ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "some uuid here", "alerts": [ { "source": "source of this message", //optional "alert": "the alert message you want to send to mythic", "level": "info", // optional level of severity for the alert - info, warning, debug, api, defaults to warning "send_webhook": true, // optional (default false) to indicate that you want to send a webhook alert for this anyway "webhook_alert": { // optional json field of the data to send along with an alert if you want more structured context "some": "data" } } ] } ] } ``` You can send multiple alerts at once since it's an array. The `source` field doesn't get displayed to the user, but it is used to collapse like-messages within the UI. If the user has an alert that's not resolved with a source of "bob", then the next message with a source of "bob" will **NOT** be displayed, but will instead simply increase the count of the current message. If the user resolves the message with a source of "bob" and a new one comes in, then that message **WILL** be displayed. This is to help prevent users from getting flooded with messages. The "squishing" of alert messages only happens in the UI - if you have a webhook container and are listening for alerts, you will get *all* of the messages. The `basic_webhook` container has code in it to throttle alert messages of the same source though that they must be a minute apart (again, to help prevent spam to users) #### Not as part of a task Sometimes you have other aspects to your agent that might be monitoring or running tasks and you want to report that data back to the user, but you don't have a specific task to associate it with. In that case, you can do the exact same `alerts` structure, but at a higher level in the structure: ```json theme={"system"} { "action": "get_tasking", // any action "alerts": [ { "source": "source of this message", //optional "alert": "the alert message you want to send to mythic", "level": "info", // optional level of severity for the alert - info, warning, debug, api, defaults to warning "send_webhook": true, // optional (default false) to indicate that you want to send a webhook alert for this anyway "webhook_alert": { // optional json field of the data to send along with an alert if you want more structured context "some": "data" } } ] } ``` * `send_webhook` - normally, if the `level` is not provided or is `warning` then an `alert` webhook is sent. However, if you set the level to something like `info` or `debug`, then you can optionally still force a webhook with this flag * `level` - this identifies how `alert` is presented to the user. `warning` is the default and will show a warning error as well as put a warning notification in the event log (increasing the warning count in the UI by 1). `info` is similar - it displays an informative message in the UI to the user and adds a message to the event log, but it isn't a warning message that needs to be addressed. `debug` will allow you to send a message to the event log, but it will *not* display a toast notification to the user. * `alert` - your normal string message that's displayed to the user and put in the event log * `webhook_alert` - optional dictionary data that doesn't get displayed to the user or put in the operation event log, but is instead sent along as custom data to the `custom_webhook` webhook for additional processing. Specifically, the data sent to the `custom_webhook` is as follows: The following is an example of the data you have available as part of a webhook alert. You can see the default fields, but your `webhook_alert`, `source`, and `alert` string are all also passed in. ```python webhook alert message format theme={"system"} if err := RabbitMQConnection.EmitWebhookMessage(WebhookMessage{ OperationID: operationInfo.ID, OperationName: operationInfo.Name, OperationWebhook: operationInfo.Webhook, OperationChannel: operationInfo.Channel, OperatorUsername: "", Action: WEBHOOK_TYPE_CUSTOM, Data: map[string]interface{}{ "callback_id": callbackID, "alert": alert.Alert, "webhook_alert": alert.WebhookAlert, "source": alert.Source, }, }); err != nil { logging.LogError(err, "Failed to send webhook") } ``` # Artifacts Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/artifacts Agent reports new artifacts created on the system or network ## Example (user tasking): Any command is able to reply with its own artifacts that are created along the way. The following response can be returned as a separate C2 message or as part of the command's normal output. The following response is part of the normal agent response. So, it is base64 encoded and put in the normal response format ## Example (agent response): ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "task uuid here", "user_output": "some user output here", "artifacts": [ { "base_artifact": "Process Create", "artifact": "sh -c whoami", "needs_cleanup": false, // optional, defaults to false "resolved": false, // optional, defaults to false }, { "base_artifact": "File Write", "artifact": "/users/itsafeature/Desktop/notmalware.exe", "needs_cleanup": true, // optional, defaults to false "resolved": false, // optional, defaults to false } ] } ] } ``` ## Walkthrough: Agents can report back their own artifacts they create at any time. They just include an `artifacts` keyword with an array of the artifacts. There are two components to this: 1. `base_artifact` is the type of base artifact being reported. 2. `artifact` is the actual artifact being created. This is a free-form field. 3. `needs_cleanup` - this is an optional field that indicates if this artifact will need to be cleaned up at some point 4. `resolved` - this is an optional field that indicates if the artifact is already cleaned up Artifacts created this way will be tracked in Artifacts page (click the fingerprint icon in any search page) # Add / Remove Commands Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/commands This page discusses how to register that commands are loaded/unloaded in a callback ## Example Agent Response ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "task uuid here", "commands": [ { "action": "add", "cmd": "shell" }, { "action": "add", "cmd": "jsimport" } ] } ] } ``` ## Walkthrough It's a common feature for agents to be able to load new functionality. Even within Mythic, you can create agents that only start off with a base set of commands and more are loaded in later. Using the `commands` keyword, agents can report back that commands are added (`"action": "add"`) or removed (`"action": "remove"`). This is easily visible when interacting with an agent. When you load a new command and register it back with mythic in this way, that new command will also appear in that autocomplete list. This only allows you to register commands that Mythic already knows about. You can't use this to register an entirely new command in Mythic (i.e. one that doesn't have a backing python/go definition file). # Task Context Tracking Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/context-tracking ## Tasking Context Tabs Mythic allows you to track a bunch of different attributes about your callback as little "tabs" right above where you issue tasks. This allows you to have a quick access to a few pieces of information without having to look back up at the top table. The default tracking shows the `impersonation_context` and `cwd` attributes of a callback. In your user settings you can change what fields you want displayed (if any) and what color you want them to be. ### cwd and impersonation\_context Using the `callback` keyword, agents can update any part of a callback as part of a task response. This data can also be set as part of your initial checkin data. #### As part of a task As part of a task, you can use another keyword, `callback` to send the following structure. Inside the `callback` key you can set anything that you could when checking in. ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "some uuid here", "callback": { "cwd": "C:\\Users\\tywin.lannister\\Desktop", //optional "impersonation_context": "sevenkingdoms.local\\", "sleep_info": "some string", "pid": 2345, "extra_info": "something else" } } ] } ``` #### Not as part of a task Sometimes you have other aspects to your agent that might be monitoring or running tasks and you want to report that data back to the user, but you don't have a specific task to associate it with. In that case, you can do the exact same `alerts` structure, but at a higher level in the structure: ```json theme={"system"} { "action": "checkin", // any action "cwd": "C:\\Users\\tywin.lannister\\Desktop", "pid": 12334, "user": "tywin.lannister", "domain": "sevenkingdoms.local" } ``` # Credentials Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/credentials Report structured credentials, parsed identity, metadata, and lifecycle state Agents can report one or more credentials in any `post_response` message. Mythic 4.0 stores a credential type and subtype, parses supported formats, tracks structured identity and metadata, and automatically evaluates time-based validity. ## Agent response ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "agent-task-uuid", "credentials": [ { "credential_type": "jwt", "credential_subtype": "access-token", "realm": "api.example.test", "account": "alice", "credential": "eyJhbGciOi...", "comment": "Token returned by the service", "custom_display": "alice API access token", "metadata": { // optional "issuer": "https://id.example.test", "not_before": "2026-07-31T18:00:00Z", "expires_at": "2026-07-31T19:00:00Z", "renew_until": "2026-08-07T19:00:00Z" }, "credential_identity": { // optional "subject": "user-42", "email": "alice@example.test" } } ] } ] } ``` `credential_type` is normalized to one of: * `plaintext` * `certificate` * `hash` * `key` * `ticket` * `cookie` * `hex` * `jwt` Unknown types fall back to `plaintext`. `credential_subtype` is a normalized, free-form value that distinguishes formats within a type, such as a particular hash algorithm or token role. Mythic requires at least an `account`, a `realm`, or a non-empty `credential_identity`. The remaining enrichment fields are optional: * `comment` records operator-facing context. * `custom_display` overrides the compact label used in the UI. * `metadata` stores descriptive and lifecycle data. * `credential_identity` stores stable structured identity fields. Any additional keys supplied on the credential object are merged into `metadata` for compatibility. ## Parsing and validity Mythic includes parsers for structured formats such as JWT and Kerberos credentials. A parser can populate the subtype, identity, account, realm, and metadata from the credential value. Explicit structured values are preserved where appropriate. Lifecycle metadata uses RFC 3339 timestamps: ```json theme={"system"} { "not_before": "2026-07-31T18:00:00Z", "expires_at": "2026-07-31T19:00:00Z", "renew_until": "2026-08-07T19:00:00Z" } ``` Mythic calculates a `validity` object inside the stored metadata and maintains operation tags when the credential is not yet valid, expired, or past its renewal window. ```mermaid theme={"system"} flowchart TD A["Agent reports credential"] --> B["Normalize type and subtype"] B --> C["Parse credential format"] C --> D["Store identity and metadata"] D --> E["Evaluate lifecycle timestamps"] E --> F["Refresh validity tags and UI state"] ``` When the same credential, type, subtype, identity, account, and realm are reported again in an operation, Mythic refreshes the existing record instead of creating a duplicate. See [Credentials](/version-4.0/operational-pieces/credentials) for operator workflows and [Tasking References](/version-4.0/operational-pieces/understanding-commands/tasking-references) for using `@cred` references in task arguments. # File Downloads (Agent -> Mythic) Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/download Send files with byte offsets or legacy numbered chunks An agent downloads a file by asking Mythic for a `file_id` to track the new file, and then posting file data against that ID. Mythic 4.0 supports two mutually exclusive transfer modes: * **Offset mode** uses `total_size` and zero-based `chunk_offset`. * **Chunk mode** uses `total_chunks`, `chunk_size`, and one-based `chunk_num` for compatibility with existing agents. Do not mix `total_size` with `total_chunks`, or `chunk_offset` with `chunk_num`, in the same transfer. ```mermaid theme={"system"} sequenceDiagram participant A as Agent participant M as Mythic A->>M: Register transfer (total_size or total_chunks) M-->>A: file_id loop Until complete A->>M: file_id + chunk_data + offset/number M-->>A: success end opt Resume after interruption A->>M: file_id + resume=true M-->>A: next byte offset or chunk number end ``` ## Offset mode Register the file with its byte length. Optional metadata can be supplied during registration or a later data message. A negative `total_size` can be updated later when the final size becomes known. ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "agent-task-uuid", "download": { "total_size": 1048576, "full_path": "/var/tmp/archive.bin", "host": "WORKSTATION-7", "filename": "archive.bin", "is_screenshot": false } } ] } ``` Mythic returns the registered file UUID: ```json theme={"system"} { "action": "post_response", "responses": [ { "status": "success", "file_id": "4b60bd75-bcf4-4c3e-8abe-9566c23b8cb8", "task_id": "agent-task-uuid" } ] } ``` Send each base64-encoded block with its zero-based position in the file. Blocks can arrive out of order and do not need a fixed size. ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "agent-task-uuid", "download": { "file_id": "4b60bd75-bcf4-4c3e-8abe-9566c23b8cb8", "chunk_offset": 0, "chunk_data": "AAECAwQFBgc=" } } ] } ``` Mythic marks the file complete after the received byte ranges cover `total_size`. ## Resume a transfer To resume an existing file from a later task, send the prior `file_id` with `resume: true`. Mythic returns the first contiguous byte offset that has not been received. ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "new-agent-task-uuid", "download": { "file_id": "4b60bd75-bcf4-4c3e-8abe-9566c23b8cb8", "resume": true } } ] } ``` ```json theme={"system"} { "action": "post_response", "responses": [ { "status": "success", "task_id": "new-agent-task-uuid", "file_id": "4b60bd75-bcf4-4c3e-8abe-9566c23b8cb8", "total_size": 1048576, "chunk_offset": 524288, "transfer_type": "offset" } ] } ``` Continue sending data from the returned `chunk_offset`. For a numbered transfer, the same resume request returns `total_chunks`, `chunk_size`, and the next one-based `chunk_num` instead. ## Numbered chunk compatibility Existing agents can continue registering with `total_chunks` and an optional fixed `chunk_size`: ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "agent-task-uuid", "download": { "total_chunks": 4, "chunk_size": 512000, "full_path": "/var/tmp/archive.bin" } } ] } ``` Data messages use one-based `chunk_num`: ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "agent-task-uuid", "download": { "file_id": "4b60bd75-bcf4-4c3e-8abe-9566c23b8cb8", "chunk_num": 1, "chunk_size": 512000, "chunk_data": "AAECAwQFBgc=" } } ] } ``` If chunks may arrive out of order, provide the fixed `chunk_size` during registration or on the first chunk. `chunk_size` is the normal block size, not necessarily the length of the final block. A negative `total_chunks` can be updated later when the final count becomes known. ## Common fields * `full_path` records the remote path and helps Mythic update the file browser. * `host` defaults to the callback host when omitted. * `filename` supplies a display name when no meaningful remote path exists. * `is_screenshot` routes the completed file to screenshot views; it defaults to `false`. * Additional keys are echoed in Mythic's response, which can be useful for an agent-local correlation ID. See [File Download Message Flow](/version-4.0/message-flow/file-download-agent-greater-than-mythic) for the end-to-end flow and [Action: post\_response](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response) for the surrounding agent message. # File Browser Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/file-browser ## Components For the file browser, there are a few capabilities that need to be present and implemented correctly if you want to allow users to list, remove, download, or upload from the file browser. Specifically: * File Listing - there needs to be a command marked as `supported_ui_features = ["file_browser:list"]` with your payload type that sends information in the proper format. * File Removal - there needs to be a command marked as `supported_ui_features = ["file_browser:remove"]` with your payload type that reports back success properly * File Download - there needs to be a command marked as `supported_ui_features = ["file_browser:download"]` with your payload type * File Upload - there needs to be a command marked as `supported_ui_features = ["file_browser:upload"]` with your payload type These components together allow an operator to browse the file system, request listings of new directories, track downloaded files, upload new files, and even remove files. Let's go into each of the components and see what they need to do specifically. ### File Listing There are two components to file listing that need to be handled - what the file browser sends as initial tasking to the command marked as `supported_ui_features = ["file_browser:list"]`and what data is sent back to Mythic for processing. #### Tasking When doing a file listing via the file browser, the command\_line for tasking will always be the following as a JSON string (this is what gets sent as the `self.command_line` argument in your command's `parse_arguments` function): ```json theme={"system"} { "host": "hostname of computer to list", "path": "path to the parent folder", "file": "name of the file or folder you're trying to list", "full_path": "absolute path to the file/folder" } ``` This might be different than the normal parameters you take for the command marked as `supported_ui_features = ["file_browser:list"]`. Since the payload type's command handles the processing of arguments itself, we can handle this case and transform the parameters as needed. For example, the `apfell` payload takes a single parameter `path` as an argument for file listing, but that doesn't match up with what the file browser sends. So, we can modify it within the `async def parse_arguments` function: ```python parse_arguments theme={"system"} async def parse_arguments(self): if len(self.command_line) > 0: if self.command_line[0] == '{': temp_json = json.loads(self.command_line) if 'host' in temp_json: # this means we have tasking from the file browser rather than the popup UI # the apfell agent doesn't currently have the ability to do _remote_ listings, so we ignore it self.add_arg("path", temp_json['full_path']) else: self.add_arg("path", temp_json['path']) self.add_arg("file_browser", "true") else: self.add_arg("path", self.command_line) self.add_arg("file_browser", "true") ``` In the above example we check if we are given a JSON string or not by checking that the `self.command_line` length is greater than 0 and that the first character is a `{`. We can then parse it into a Python dictionary and check for the two cases. If we're given something with `host` in it, then it must come from the file browser instead of the operator normally, so we take the supplied parameters and add them to what the command normally needs. In this case, since we only have the one argument `path`, we take the `full_path` variable from the file browser dictionary and use it our path variable. #### Agent File Browsing Responses Now that we know how to translate file browsing file listing tasking to whatever our command needs, what kind of output do we need to send back? We have another component to the `post_response` for agents. ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "UUID of task", "file_browser": { "host": "hostname of computer you're listing", // optional "is_file": false, "permissions": {json of permission values you want to present}, "name": "name of the file or folder you're listing (just the name, not the full path)", "parent_path": "full path of the parent folder (full path of parent, don't include name of thing you listed)", "success": true, "access_time": unix epoc time in milliseconds, "modify_time": unix epoc time in milliseconds, "size": 1345, //size of the entity in bytes "update_deleted": true, //optional "set_as_user_output": true, // optional "files": [ // if this is a folder, include data on the files within { "is_file": true, "permissions": {json of data here that you want to show}, "name": "name of the entity", "access_time": unix epoc time in milliseconds, "modify_time": unix epoc time in milliseconds, "size": 13567 // size of the entity } ] } } ] } ``` As a shortcut, if the file you're ls-ing is on the same host as your callback, then you can omit the `host` field or set it to `""` and Mythic will automatically add in your callback's host information instead. If you're listing out the top level folder (`/` on linux/macOS or a drive like `C:\` on Windows, then the parent path should be "" or null. Most of this is pretty self-explanatory, but there are some nuances. * Only list out the inner files for the initial folder/file listed (i.e. don't recursively do this listing). * For the `files` array, you don't need to include `host` or `parent_path` because those are both inferred based on the info outside the `files` array, and the `success` flag won't be included since you haven't tried to actually list out the contents of any sub-folders. * The permissions JSON blob allows you to include any additional information you want to show the user in the file browser. For example, with the `apfell` agent, this blob includes information about extended attributes, posix file permissions, and user/group information. Because this is heavily OS specific, there's no requirement here other than it being a JSON blob (not a string). The `set_as_user_output` field is new as of Mythic 3.3.1-rc23. It was relatively common practice for people to return this file browser data, but also return a string version in the `user_output` field to be displayed to the user. This means that you're sending the same data back twice though and bloating the size of your messages. This new flag tells Mythic to take this structured data, turn it into a JSON string, and add it as a response output for this task. This way your agent doesn't have to explicitly send it, but you still get the benefit. By having this information in *another* component within the responses array, you can display any information to the user that you want without being forced to *also* display this listing each time to the user. You can if you want, but it's not required. If you wanted to do that, you could simply turn all of the `file_browser` data into a JSON string and put it in the `user_output` field. In the above example, the user output is a simple message stating why the tasking was issued, but it could be anything (even left blank). Mythic doesn't currently support `.`, `..`, or `~` paths. Any information about `.` should be part of the main `file_browser` JSON data (not part of the `files` array). `~` should be fixed to an absolute path. #### update deleted There's a special key in there that doesn't really match the rest of the normal "file" data in that file\_browser response - `update_deleted`. If you include this key as `True` and your `success` flag is `True`, then Mythic will use the data presented here to update which files are deleted. By default, if you list the contents of `~/Downloads` twice, then the view you see in the UI is a *merge* of all the data from those two instance of listing that folder. However, that might not *always* be what you want. For instance, if a file was deleted between the first and second listing, that deletion won't be reflected in the UI because the data is simply merged together. If you want that delete to be automatically picked up and reported as a deleted file, use the `update_deleted` flag to say to Mythic "hey, this should be everything that's in the folder, if you have something else that used to be there but I'm not reporting back right now, assume it's deleted". You might be wondering why this isn't just the default behavior for listing files. There are two main other scenarios that we want to support that are counter to this idea - paginated results (only return 20 files at a time) and filtered results (only return files in the folder that end in .txt). In these cases, we don't want the rest of the data to be automatically marked as deleted because we're clearly not returning the full picture of what's in a folder. That's why it's an optional flag to say to performing the automatic updates. If you want to be explicit with things though (for example, if you delete a file and want to report it back without having to re-list the entire contents of the directory), you can use the next section - File Removal. ### File Removal There are two components to file listing that need to be handled - what the file browser sends as initial tasking to the command marked as `supported_ui_features = ["file_browser:remove"]`and what data is sent back to Mythic for processing. #### Tasking This is the exact same as the `supported_ui_features = ["file_browser:list"]`and the [File Browser](/version-4.0/customizing/hooking-features/file-browser#tasking) section above. #### Agent File Removal Responses Ok, so we listed files and tasked one for removal. Now, how is that removed file tracked back to the file browsing to mark it as removed? Nothing too crazy, there's another field in the `post_response`: ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "UUID of task", "user_output": "File successfully deleted", "removed_files": [ { "host": "hostname where file was removed", "path": "full path to the file" } ] } ] } ``` This `removed_files` section simply returns an array of dictionaries that spell out the host and paths of the files that were deleted. On the back-end, Mythic takes these two pieces of information and searches the file browsing data to see if there's a matching `path` for the specified `host` in the current operation that it knows about. If there is, it gets marked as `deleted` and in the UI you'll see a strikethrough. This response isn't ONLY for when a file is removed through the file browser though. You can return this from your normal removal commands as well and if there happens to be a matching file in the browser, it'll get marked as removed. This allows you to simply type things like `rm /path/to/file` on the command-line and still have this information tracked in the file browser without requiring you to remove it through the file browser specifically. ### File Downloading There are two components to file listing that need to be handled - what the file browser sends as initial tasking to the command marked as `supported_ui_features = ["file_browser:download"]`and what data is sent back to Mythic for processing. #### Tasking This is the exact same as the `supported_ui_features = ["file_browser:list"]` and the [File Browser](/version-4.0/customizing/hooking-features/file-browser#tasking) section above. #### Agent File Download Responses There's nothing special here outside of normal file download processes described in the [Download](/version-4.0/customizing/hooking-features/download) section. When a new file is tracked within Mythic, there's a "host" field in addition to the `full_path` that is reported. This information is used to look up if there's any matching browser objects and if so, they're linked together. ### File Uploading Having a command marked as `supported_ui_features = ["file_browser:upload"]`will cause that command's parameters to pop-up and allow the operator to supply in the normal file upload information. To see this information reflected in the file browser, the user will then need to issue a new file listing. # Interactive Tasking Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/interactive-tasking For terminal-style follow-on input, see [Interactive Tasking](/version-4.0/customizing/payload-type-development/15.-interactive-tasking) and the [agent message format](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/8.-interactive-tasking). For versioned UTF-8 document editing, use the v4 [File Editor Protocol](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol), which reserves interactive message types `100`–`102` and adds refresh/save/close semantics with conflict detection. # Keylog Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/keylog Keystrokes are sent back from the agent to the Mythic server ## Example (agent response): ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "task uuid here", "keylogs": [ { "user": "its-a-feature", "window_title": "Notepad - Untitled", "keystrokes": "my password is zer0c00l" } ] } ] } ``` ## Walkthrough: Agents can report back keystrokes at any time. There are three components to a keystroke report: * `user` - the user that is being keylogged * `window_title` - the title of the window to which the keystrokes belong * `keystrokes` - the actual recorded keystrokes Having the information broken out into these separate pieces allows Mythic to do grouping based on the user and window\_title for easier readability. If the agent doesn't know the user or the window\_title fields, they should still be included, but can be empty strings. If empty strings are reported for either of these two fields, they will be replaced with "UNKNOWN" in Mythic. ### Multiple users/windows What happens if you need to send keystrokes for multiple users/windows? ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "task uuid here", "keylogs": [ { "user": "its-a-feature", "window_title": "Notepad - Untitled", "keystrokes": "my password is zer0c00l" }, { "user": "its-a-feature", "window_title": "Notepad - Untitled", "keystrokes": "my password is zer0c00l" } ,{ "user": "its-a-feature", "window_title": "Notepad - Untitled", "keystrokes": "my password is zer0c00l" } ] } ] } ``` # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/linking-agents ## What does it mean to link agents This refers to the act of connecting two agents together with a peer-to-peer protocol. The details here are agnostic of the actual implementation of the protocol (could be SSH, TCP, Named Pipes, etc), but the goal is to provide a way to give one agent the context it needs to `link` or establish some peer-to-peer connectivity to a running agent. This also comes into play when trying to connect to a newly executed payload that hasn't gone through the checkin process with Mythic yet to get registered as a Callback. ### Getting the Linking information When creating a command, give a parameter of type `ParameterType.ConnectionInfo`. Now, when you type your command without parameters, you'll get a popup like normal. However, for this type, there will be three dropdown menus for you to fill out: #### Host: This field is auto populated based on two properties: * The list of all hosts where you have registered callbacks * The list of all hosts where Mythic is aware you've moved moved a payload to #### Payload: Once you've selected a host, the `Payload` dropdown will populate with the associated payloads that Mythic knows are on that host (this will also contain callbacks with p2p c2 profiles). These payloads are in two main groups: * The payloads that spawned the current callbacks on that host * The payloads that were moved over via a command that created a new payload and registered it to the new host This payload simply acts as a template of information so that you can select the final piece. The payloads listed are also the only ones that have a p2p profile associated with them. #### What if your host/payload isn't listed? Select the `Register New` button, manually specify which host your payload lives on, then select from the dropdown the associated payload that was used. Then click `Confirm`. Now Mythic is also tracking that the selected payload lives on the indicated host. You can continue with the host/payload/c2\_profile dropdowns like normal. #### C2 Profile: When trying to connect to a new agent, you have to specify which specific profile you're wanting to connect to. This is because on any given host and for any given payload, there might be multiple c2 profiles within it (think HTTP, SMB, TCP, etc). This field will auto populate based on the C2 profiles that are in the payload selected in the drop down above it. You'll only be able to select C2 profiles that are marked as `is_p2p` for peer-to-peer profiles. This is because it doesn't make any sense to remotely link to an HTTP callback profile for example. ### Submitting the task: Once you've selected all of the above pieces, the task will insert all of that selected profile's specific instantiations as part of the task for the agent to use when connecting. This can include things like specific ports to connect to, specific pipe names to use, or any other information that might be needed to make the connection. You'll notice when you submit this task, your input might look different than you expect. You might be expecting a JSON blob, but instead you'll get a unique task reference like `@link:callback=12,c2=smb` or `@link:payload=,host=workstation2,c2=smb`. This is another way you could provide this context, but on the command line without having to use the modal. This is also the ideal way to submit this sort of information via scripting or eventing. ### Shorthand: All of the above is to help an operator identify exactly which payload/callback they're trying to connect to and via which p2p protocol. As a developer, you have the freedom to instead allow operators to specify more generic information via the command-line such as: `link-command-name hostname` or `link-command-name hostname other-identifier`. The caveat is this now requires the operator to know more detailed information about the connection ahead of time. ## Leveraging Current/Old Links The `ParameterType.ConnectionInfo` parameter type is useful when you want to make a new connection between a callback to a payload you just executed or to another callback that your current callback hasn't connected to before. A common command that leverages this parameter type would be `link`. However, this isn't too helpful if you want to remove a certain connection or if you just want to re-establish a connection that died. To help with this, there's the `ParameterType.LinkInfo` which, as the name implies, gives information about the links associated with your callback. When you use a parameter type of `ParameterType.LinkInfo`, you'll get a dropdown menu where the user can select from live or dead links to leverage. When you select a current/dead link, the data that's sent down to your `create_tasking` function is the exact same as when you use the `ParameterType.ConnectionInfo` - i.e. information about the host, payload uuid, callback uuid, and the p2p c2 profile parameter information. # P2P Connections Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/linking-agents/action-p2p_info This describes how to report back p2p connection information to the server ## What is it This message type allows agents to report back new or removed connections between themselves or elsewhere within a p2p mesh. Mythic uses these messages to construct a graph of connectivity that's displayed to the user and for handling routing for messages through the mesh. ### Agent message to Mythic The agent message to Mythic has the following form: ```json theme={"system"} { "action": "post_response", "responses": [ { "user_output": "some user output here", "task_id": "uuid of task here", "edges": [ { "source": "uuid of source callback", "destination": "uuid of destination callback", "metadata": "{ optional metadata json string }", "action": "add or remove", "c2_profile": "name of the c2 profile used in this connection" } ] } ] } ``` This same `edges` array can be sent outside of a `responses` array, at the same level of `action`. This is handy if your agent suddenly loses connection, not because of a task, but because of other factors and still wants to report back that information to Mythic. Just like other [post\_response](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response) messages, this message has the same UUID and encryption requirements found in [Agent Message Format](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-message-format). Some things to note about the fields: * `edges` is an array of JSON objects describing the state of the connections that the agent is adding/removing. Each edge in this array has the following fields: * `source` this is one end of the p2p connection (more often than not, this is the agent that's reporting this information) * `destination` this is the other end of the p2p connection * `metadata` is an optional information about the connection that the agent wants to report. For example, when dealing with SMB bind pipes, this could contain information about the specific pipe name instances that are being used if they're being programmatically generated. * `action` this indicates if the connection described above is to be added or removed from Mythic. * `c2_profile` this indicates which c2 profile is used for the connection ### Response message from Mythic After getting a message like this, Mythic responds with a message of the following form: ```json theme={"system"} { "status": "success or error", "error": "error message if status was error", "task_id": "id of task" } ``` This is very similar to most other response messages from the Mythic server. ### Automatic Connection Announcements When an agent sends a message to Mythic with a `delegate` component, Mythic will automatically add a route between the delegate and the agent that sent the message. For example: If agentA is an egress agent and it links to agentB, a p2p agent. When agentA sends a message to Mythic with a delegate message from agentB, Mythic will automatically create a route between the two agents. When connecting via P2P, the linked agent should always send *something* to Mythic. If it has already gone through the `Checkin` process, then it should resend the checkin message data. This time though, the checkin message will still have the callback UUID out front instead of the payload or staging UUID. Everything else will still be the same though. This just forces Mythic to become aware of the connection between the two callbacks. # OnContainerStart Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/oncontainerstart onContainerStart Functionality ## What is it? OnContainerStartFunction and on\_container\_start are functions you can optionally implement in any container to get execution, per operation, when the container starts up. This is helpful when your container needs to do some housekeeping and prep an agent, c2 profile, or even eventing before anything else happens. ## Where is it? This function is one you can implement as part of the definition for your container (PayloadType, C2Profile, Eventing, etc). ## What does it do? This function receives an opaque API token that is valid for five minutes. Mythic 4.0 grants explicit scopes for supported startup work, including eventing, payload, file, tag, callback, and chat actions, under the operation's active bot identity. The token is also represented by the authenticated RabbitMQ context on the request. If the function calls protected HTTP or GraphQL endpoints, send the supplied value as `Authorization: Bearer `. Do not persist the token, or assume it has global `*` access. ## When is it called? This function is called when your container first comes online and syncs with Mythic. It's also called (as of Mythic 3.3.1-rc26) when anybody adds/removes/edits a file inside of your container through the UI. This allows you, the container developer, to be reactive to changes users make to files that might affect things like configurations. ```python Python theme={"system"} class ContainerOnStartMessage: def __init__(self, container_name: str = "", operation_id: int = 0, server_name: str = "", apitoken: str = "", **kwargs): self.ContainerName = container_name self.OperationID = operation_id self.ServerName = server_name self.APIToken = apitoken def to_json(self): return { "container_name": self.ContainerName, "operation_id": self.OperationID, "server_name": self.ServerName, "apitoken": self.APIToken } class ContainerOnStartMessageResponse: def __init__(self, ContainerName: str = "", EventLogInfoMessage: str = "", EventLogErrorMessage: str = ""): self.ContainerName = ContainerName self.EventLogInfoMessage = EventLogInfoMessage self.EventLogErrorMessage = EventLogErrorMessage def to_json(self): return { "container_name": self.ContainerName, "stdout": self.EventLogInfoMessage, "stderr": self.EventLogErrorMessage } # define this function inside of any Payload Type, C2 Profile, Eventing, etc etc class async def on_container_start(self, message: ContainerOnStartMessage) -> ContainerOnStartMessageResponse: return ContainerOnStartMessageResponse(ContainerName=self.name) ``` ```go Golang theme={"system"} type ContainerOnStartMessage struct { ContainerName string `json:"container_name"` OperationID int `json:"operation_id"` OperationName string `json:"operation_name"` ServerName string `json:"server_name"` APIToken string `json:"apitoken"` } type ContainerOnStartMessageResponse struct { ContainerName string `json:"container_name"` EventLogInfoMessage string `json:"stdout"` EventLogErrorMessage string `json:"stderr"` } OnContainerStartFunction func(sharedStructs.ContainerOnStartMessage) sharedStructs.ContainerOnStartMessageResponse `json:"-"` ``` # Process Browser Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/process_list Unified process listing across multiple callbacks for a single host ### Command Component The supported ui features flag on the command that does this tasking needs to have the following set:`supported_ui_features = ["process_browser:list"]` if you want to be able to issue a process listing from the UI process listing table. If you don't care about that, then you don't need that feature set for your command. ### Why a Unified Process List per Host There are many instances where you might have multiple agents running on a single host, and you run common tasking like process lists over and over and over again. You often do this because the tasking has scrolled out of view, maybe it's just stale information, or maybe you couldn't quite remember which callback actually had that task. This is where the unified process listing comes into play. With a special format for process listing, Mythic can track all the different process lists together for a single host. It doesn't matter which host you ran the task on, as long as you pull up the process\_list view for that host, all of the tasks will be available and viewable. ### Output Format Naturally, this has a special format for us to make it the most useful. Like almost everything else in Mythic, this requires structured output for each process we want the following: ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "uuid", "processes": { "update_deleted": false, // optional - setting this to true tells Mythic to mark any process not returned in this process array as deleted "host": "a.b.com", "os": "windows", "processes": [ { "process_id": 12345, "name": "evil.exe", "parent_process_id": 1234, //optional "architecture": "x64", // optional "bin_path": "C:\\Users\\bob\\Desktop\\evil.exe", // optional "user": "bob", // optional "command_line": "C:\\Users\\bob\\Desktop\\evil.exe -f test.txt -thread 12", // optional "integrity_level": 3, // optional "start_time": unix epoch time in milliseconds, //optional "description": "not malware", // optional "signer": "Bob's software co", // optional "protected_process_level": 0, // optional ** // any other fields you want, they all end up in the metadata field within the database } ] } } ]} ``` All that's needed is a dictionary to give some high level info (host, os type, and update\_deleted) and an array of all the process's data. That allows Mythic to create a process hierarchy (if you supply both `process_id` and `parent_process_id`) and a sortable/filterable table of processes. The above example shows a `post_response` with one response in it. Any field that ends with `_time` expects the value to be an int64 of unix epoch time in milliseconds. You're welcome to supply any additional field you want about a process - it all gets aggregated together and provided as part of the "metadata" for the process that you can view in the UI in a nice table listing. For example, a macOS agent might report back signing flags and entitlements and a windows agent might report back integrity level and user session id. ## Additional Process Browser UI Buttons All additional buttons through the Process Browser UI (such as task inject, task kill, etc) have their own supported ui features: `process_browser:inject`, `process_browser:kill`, `process_browser:list_tokens`, `process_browser:steal_token`. All of these will get three parameters passed to them for tasking: * host * process\_id * architecture For example, `{"host": "ABC.COM", "process_id": 1234, "architecture": "x64"}`. Your commands that support these features will need to expect and process these arguments. # RPFWD Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/rpfwd If you're curious how reverse port forwards work within Mythic and how you can hook into them with an agent, check out the section on [RPFWD](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/rpfwd) development. # Screenshots Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/screenshots Mythic has a special page specifically for viewing screenshots by clicking the camera icon at the top of any of the pages. When it comes to registering screenshots with Mythic, the process is almost identical to [File Downloads (Agent -> Mythic)](/version-4.0/customizing/hooking-features/download); however, we set the `is_screenshot` flag to `true` in the `download` portion of the message: ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "UUID here", "download": { "total_chunks": 4, "host": "hostname the file is downloaded from", "is_screenshot": true //indicate if this is a file or screenshot } } ]} ``` # SOCKS Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/socks If you're curious how SOCKS works within Mythic and how you can hook into it with an agent, check out the [SOCKS](/version-4.0/customizing/payload-type-development/socks) section for coding. # Task status Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/task-status ## What is Task status You probably noticed as you used Mythic that there's a status associated with your task. This status goes through a variety of words/colors depending on where things are in the pipeline and what the agent has done with the task. This provides a way for the operator to know what's happening behind the scenes. ## What are the default statuses? By default, a task goes through the following stages with the following statuses: 1. `preparing task for agent...` - The task is being sent to the Payload Type container for processing (parsing arguments, confirming values, doing RPC functionality to register files, etc) 2. `submitted` - The task is now ready for an agent to pick it up 3. `agent processing` - The task has been picked up by an agent, but there hasn't been any response back yet 4. `processed, agent sending responses...` - The task has at least one response, but the agent hasn't marked it as done yet. 5. `completed` - The task is marked as completed by the agent and there wasn't an error in execution 6. `error:*` - The agent report back a status of "error" ### Can I set my own status? The agent can set the status of the task to anything it wants as part of its normal `post_response` information. Similarly, in a task's `create_tasking` function, you're free to set the `task.status` value. Anything of the `error:*` format will show up as red in the Mythic UI as an error for the user. # Tokens Source: https://docs.mythic-c2.net/version-4.0/customizing/hooking-features/tokens Token awareness and Token tasking Mythic supports Windows tokens in two ways: tracking which tokens are viewable and tracking which tokens are usable by the callback. The difference here, besides how they show up in the Mythic interface, is what the agent can do with the tokens. The idea is that you can list out all tokens on a computer, but that doesn't mean that the agent has a handle to the token for use with tasking. ### Tokens As part of the normal responses sent back from the agent, there's an additional key you can supply, `tokens` that is a list of token objects that can be stored in the Mythic database. These will be viewable from the "Search" -> "Tokens" page, but are not leveraged as part of further tasking. ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "uuid here", "user_output": "got some tokens yo", "tokens": [ { "token_id": 18947, // required, agent generated "host": "bob.com", // optional "description": "", // optional "user": "bob", // optional "groups": "", //optional "thread_id": 456, // optional "process_id": 2345, // optional "default_dacl": "", //optional p.TextField(null=True) "session_id": 0, //optional p.IntegerField(null=True) "restricted": false, //optional = p.BooleanField(null=True) "capabilities": "", //optional = p.TextField(null=True) "logon_sid": "", //optional = p.TextField(null=True) "integrity_level_sid": 0, //optional = p.IntegerField(null=True) "app_container_number": 0, //optional = p.IntegerField(null=True) "app_container_sid": "", //optional = p.TextField(null=True) "privileges": "", //optional = p.TextField(null=True) "handle": 12345, //optional = p.IntegerField(null=True) } ] } ] } ``` `token_id` is simply a way for your callback to refer to the various tokens it interacts with. You'll use this `token_id` to register a token with your callback for use in subsequent tasking. ### Callback Tokens If you want to be able to leverage tokens as part of your tasking, you need to register those tokens with Mythic and the callback. This can be done as part of the normal `post_response` responses like everything else. The key here is to identify the right token - specifically via the unique combination of token\_id and host. ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "uuid here", "output": "now tracking token 12345", "callback_tokens": [ { "action": "add", // could also be "remove" "host": "a.b.com", //optional - default to callback host if not specified "token_id": 12345, // id } ] } ] } ``` If the token `12345` hasn't been reported via the `tokens` key then it will be created and then associated with Mythic. Once the token is created and associated with the callback, there will be a new dropdown menu next to the tasking bar at the bottom of the screen where you can select to use the default token or one of the new ones specified. When you select a token to use in this way when issuing tasking, the `create_tasking` function's `task` object will have a new attribute, `task.token` that contains a dictionary of all the token's associated attributes. This information can then be used to send additional data with the task down to the agent to indicate which tokens should be used for the task as part of your parameters. Additionally, when getting tasks that have tokens associated with them, the `TokenId` value will be passed down to the agent as an additional field: ```json theme={"system"} { "action": "get_tasking", "tasks": [ { "command": "shell", "parameters": "whoami", "id": "uuid here", "timestamp": 1234567, "token": 12345 } ] } ``` # 1. Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development This section describes new Payload Types ## Creating a new Mythic agent You want to create a new agent that fully integrates with Mythic. Since everything in Mythic revolves around Docker containers, you will need to ultimately create one for your payload type. This can be done with docker containers on the same host as the Mythic server or with an external [VM/host machine](/version-4.0/customizing/payload-type-development/payload-type-info/turning-a-vm-into-a-container). ## 1.0 - What are we creating and how does it fit in? What does Mythic's setup look like? We'll use the diagram below - don't worry though, it looks complicated but isn't too bad really: Mythic itself is a `docker-compose` file that stands up a bunch of microservices. These services expose various pieces of functionality like a database (`PostgreSQL`), the web UI (`React Web UI`), internal documentation (`Hugo Documentation`), and a way for the various services to communicate with each other (`RabbitMQ`). Don't worry though, you don't worry about 99% of this. Mythic by itself doesn't have any agents or command-and-control profiles - these all are their own Docker containers that connect up via `RabbitMQ` and `gRPC`. This is what you're going to create - a separate container that connects in (the far right hand side set of containers in the above diagram). ### 1.1 Where do things live? When you clone down the Mythic repo, run `make` to generate the `mythic-cli` binary, and then run `sudo ./mythic-cli start`, you are creating a `docker-compose` file automatically that references a bunch of `Dockerfile` in the various folders within the `Mythic` folder. These folders within `Mythic` are generally self-explanatory for their purpose, such as `postgres-docker` , `rabbitmq-docker`, `mythic-docker`, and `MythicReactUI`. When you use the `mythic-cli` to install an agent or c2 profile, these all go in the `Mythic/InstalledServices` folder. This makes it super easy to see what you have installed. Throughout development you'll have a choice - do development remotely from the Mythic server and hook in manually, or do development locally on the Mythic server. After all, everything boils down to code that connects to `RabbitMQ` and `gRPC` - Mythic doesn't really know if the connection is locally from Docker or remotely from somewhere else. ## 2.0 Starting with an example The first step is to clone down the example repository [https://github.com/MythicMeta/ExampleContainers](https://github.com/MythicMeta/ExampleContainers). The format of the repository is that of the [External Agent](https://github.com/MythicMeta/Mythic_External_Agent) template. This is the format you'll see for all of the agents and c2 profiles on the [overview](https://mythicmeta.github.io/overview/) page. Inside of the `Payload_Type` folder, there are two folders - one for GoLang and one for Python depending on which language you prefer to code your agent definitions in (this has nothing to do with the language of your agent itself, it's simply the language to define commands and parameters). We're going to go step-by-step and see what happens when you install something via `mythic-cli`, but doing it manually. ### 2.1 Copy the folder Pick whichever service you're interested in and copy that folder into your `Mythic/InstalledServices` folder. When you normally install via `mythic-cli`, it clones down your repository and does the same thing - it copies what's in that repository's `Payload_Type` and `C2_Profiles` folders into the `Mythic/InstalledServices` folder. ### 2.2 Update the docker-compose Now that a folder is in the `Mythic/InstalledServices` folder, we need to let the `docker-compose` file *know* that it's there. Assuming you copied over `python_services`, you then need to run `sudo ./mythic-cli add python_services`. This adds that `python_services` folder to the `docker-compose`. This is automatically done normally as part of the install process. As part of updating `docker-compose`, this process adds a bunch of environment variables to what will be the new container. ### 2.3 Building the image and running the container Now that `docker-compose` knows about the new service, we need to build the image that will be used to make the agent's container. We can use `sudo ./mythic-cli build python_services`. This tells `docker` to look in the `Mythic/InstalledServices/python_services` folder for a `Dockerfile` and use it to build a new image called `python_services`. As part of this, Mythic will automatically then use that new image to create a container and run it. If it doesn't, then you can create and start the container with `sudo ./mythic-cli start python_services`. Again, all of this happens automatically as part the normal installation process when you use `sudo ./mythic-cli install`. We're doing this step-by-step though so you can see what happens. ### 2.4 Check the Mythic UI At this point, your new example agent should be visible within Mythic. If it's not, we can check logs to see what the issue might be with `sudo ./mythic-cli logs python_services` (this is a wrapper around `sudo docker logs python_services` and truncates to the latest 500 lines). ### 2.5 Reminder Steps 2.1-2.4 all happen automatically when you install a service via `mythic-cli`. If you don't want to install via `mythic-cli` then you can do these steps manually like we did here. ## 3.0 Examining the pieces Now that you've seen the pieces and steps for installing an existing agent, it's time to start diving into what's going on within that `python_services` folder. ### 3.1 Dockerfile The only thing that absolutely **MUST** exist within this folder is a `Dockerfile` so that `docker-compose` can build your image and start the container. You can use anything as your base image, but Mythic provides a few to help you get started with some various environments: * `itsafeaturemythic/mythic_go_base` has GoLang 1.25 installed * `itsafeaturemythic/mythic_go_dotnet` has GoLang 1.25 and .NET * `itsafeaturemythic/mythic_go_macos` has GoLang 1.25 and the macOS SDK * `itsafeaturemythic/mythic_python_base` has Python 3.11 and the `mythic_container` pypi package * `itsafeaturemythic/mythic_python_go` has Python 3.11, the `mythic_container` pypi package, and GoLang v1.25 * `itsafeaturemythic/mythic_python_macos` has Python 3.11, the `mythic_container` pypi package, and the macOS SDK This allows two payload types that might share the same language to still have different environment variables, build paths, tools installed, etc. Docker containers come into play for a few things: * Sync metadata about the payload type (this is in the form of python classes or GoLang structs) * Contains the payload type code base (whatever language your agent is in) * The code to create the payload based on all of the user supplied input (builder function) * Sync metadata about all of the commands associated with that payload type * The code for all of those commands (whatever language your agent is in) * Browser scripts for commands (JavaScript) * The code to take user supplied tasking and turn it into tasking for your agent #### Using the default container base Start your `Dockerfile` off with one of the above images: ``` From itsafeaturemythic/mythic_python_base:latest ``` On the next lines, just add in any extra things you need for your agent to properly build, such as: ``` RUN pip install python_module_name RUN shell_command RUN apt-get install -y tool_name ``` This happens all in a script for docker, so if a command might make a prompt for something (like apt-get), make sure to auto handle that or your stuff won't get installed properly The latest container versions and their associated `mythic_container` PyPi versions can be found here: [#current-payloadtype-versions](/version-4.0/customizing/payload-type-development/payload-type-info/container-syncing#current-payloadtype-versions). The `mythic_python_*` containers will always have the latest `PyPi` version installed if you're using the `:latest` version. If you're curious what else goes into these containers, look in the `docker-templates` folder within the Mythic repository. ### 3.2 Required Folder Structure The `Mythic/InstalledServices/[agent name]` folder is mapped to `/Mythic` in the docker container. Editing the files on disk results in the edits appearing in the docker container and visa versa. Within the `Dockerfile` you will then need to do whatever is needed to kick off your main program that imports either the MythicContainer PyPi package or the MythicContainer GoLang package. As some examples, here's what you can do for Python and GoLang: `Mythic/InstalledServices/[agent name]/main.py` \< if you plan on using Python as your definition language, this `main.py` file is what will get executed by Python 3.11 assuming you use the Dockerfile shown below. If you want a different structure, just change the `CMD` line to execute whatever it is you want. ``` FROM itsafeaturemythic/mythic_python_base:latest RUN python3 -m pip install donut-shellcode WORKDIR /Mythic/ CMD ["python3", "main.py"] ``` At that point, your `main.py` file should import any other folders/files needed to define your agent/commands and import the `mythic_container` PyPi package. Any changes you make to your Python code is automatically reflected within the container. Simply do `sudo ./mythic-cli start [agent name]` to restart the container and have python reprocess your files. If you want to do local testing without `docker`, then you can add a `rabbitmq_config.json` in the root of your directory (i.e. `[agent name]/rabbitmq_config.json`) that defines the environment parameters that help the container connect to Mythic: ``` { "rabbitmq_host": "127.0.0.1", "rabbitmq_password": "PqR9XJ957sfHqcxj6FsBMj4p", "mythic_server_host": "127.0.0.1", "webhook_default_channel": "#mythic-notifications", "debug_level": "debug", "rabbitmq_port": 5432, "mythic_server_grpc_port": 17444, "webhook_default_url": "", "webhook_default_callback_channel": "", "webhook_default_feedback_channel": "", "webhook_default_startup_channel": "", "webhook_default_alert_channel": "", "webhook_default_custom_channel": "", } ``` Things are a little different here as we're compiling binaries. To keep things in a simplified area for building, running, and testing, a common file like a `Makefile` is useful. This `Makefile` would be placed at `Mythic/InstalledServices/[agent name]/Makefile`. From here, that make file can have different functions for what you need to do. Here's an example of the `Makefile` that allows you to specify custom environment variables when debugging locally, but also support Docker building: ``` BINARY_NAME?=main DEBUG_LEVEL?="warning" RABBITMQ_HOST?="127.0.0.1" RABBITMQ_PASSWORD?="password here" MYTHIC_SERVER_HOST?="127.0.0.1" MYTHIC_SERVER_GRPC_PORT?="17444" WEBHOOK_DEFAULT_URL?= WEBHOOK_DEFAULT_CHANNEL?= WEBHOOK_DEFAULT_FEEDBACK_CHANNEL?= WEBHOOK_DEFAULT_CALLBACK_CHANNEL?= WEBHOOK_DEFAULT_STARTUP_CHANNEL?= build: go mod tidy go build -o ${BINARY_NAME} . cp ${BINARY_NAME} / run: cp /${BINARY_NAME} . ./${BINARY_NAME} run_custom: DEBUG_LEVEL=${DEBUG_LEVEL} \ RABBITMQ_HOST=${RABBITMQ_HOST} \ RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} \ MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST} \ MYTHIC_SERVER_GRPC_PORT=${MYTHIC_SERVER_GRPC_PORT} \ WEBHOOK_DEFAULT_URL=${WEBHOOK_DEFAULT_URL} \ WEBHOOK_DEFAULT_CHANNEL=${WEBHOOK_DEFAULT_CHANNEL} \ WEBHOOK_DEFAULT_FEEDBACK_CHANNEL=${WEBHOOK_DEFAULT_FEEDBACK_CHANNEL} \ WEBHOOK_DEFAULT_CALLBACK_CHANNEL=${WEBHOOK_DEFAULT_CALLBACK_CHANNEL} \ WEBHOOK_DEFAULT_STARTUP_CHANNEL=${WEBHOOK_DEFAULT_STARTUP_CHANNEL} \ ./${BINARY_NAME} ``` Pay attention to the `build` and `run` commands - once you're done building your code, notice that it's copied from the current directory to `/` in the Docker Image. This is because when the container starts, your source code is mapped into the Docker image, thus discarding any changes you made to that directory while building. This is also why the `run` function copies the binary back into the current directory and executes it there. The reason it's executed this way instead of from `/` is so that pathing and local folders are located where you expect them to be in relation to your binary. To go along with that, a sample Docker file for Golang is as follows: ``` FROM itsafeaturemythic/mythic_go_base:latest WORKDIR /Mythic/ COPY [".", "."] RUN make build CMD make run ``` It's very similar to the Python version, except it runs `make build` when building and `make run` when running the code. The Python version doesn't need a `Makefile` or multiple commands because it's an interpreted language. If your container/service is running on a different host than the main Mythic instance, then you need to make sure the `rabbitmq_password` is shared over to your agent as well. By default, this is a randomized value stored in the `Mythic/.env` file and shared across containers, but you will need to manually share this over with your agent either via an environment variable (`MYTHIC_RABBITMQ_PASSWORD` ) or by editing the `rabbitmq_password` field in your rabbitmq\_config.json file. You also need to make sure that the `MYTHIC_RABBITMQ_LISTEN_LOCALHOST_ONLY` is set to `false` and restart Mythic to make sure the `RabbitMQ` port isn't bound exclusively to 127.0.0.1. The containers will automatically sync all of their information with the Mythic server when they start, so the first time the Mythic server gets a message from a container it doesn't know about, it'll ask to sync. Similarly, as you do development and restart your Payload Type container, updates will automatically get synced to the main UI. ### 3.3 Folder name The folder that gets copied into `Mythic/InstalledServices` is what's used to create the `docker` image and container names. It doesn't necessarily have to be the same as the name of your agent / c2 profile (although that helps). Docker does not allow capital letters in container names. So, if you plan on using Mythic's `mythic-cli` to control and install your agent, then your agent's name can't have any capital letters in it. Only lowercase, numbers, and \_. It's a silly limitation by Docker, but it's what we're working with. ### 3.4 main.py and main.go The example services has a single container that offers multiple options (Payload Type, C2 Profile, Translation Container, Webhook, and Logging). While a single container can have all of that, for now we're going to focus on just the payload type piece, so delete the rest of it. For the `python_services` folder this would mean deleting the `mywebhook`, `translator`, and `websocket` folders. For the `go_services` folder, this would mean deleting the `http`, `my_logger`, `my_webhooks`, `no_actual_translation` folders. For both cases, this will result in removing some imports at the top of the remaining `main.py` and `main.go` files. For the `python_services` folder, we'll update the `basic_python_agent/agent_functions/builder.py` file. This file can technically be anywhere that `main.py` can reach and import, but for convenience it's in a folder, `agent_functions` along with all of the command definitions for the agent. Below is an example from that builder that defines the agent: ``` #from mywebhook.webhook import * import mythic_container import asyncio import basic_python_agent #import websocket.mythic.c2_functions.websocket #from translator.translator import * #from my_logger import logger mythic_container.mythic_service.start_and_run_forever() ``` ``` package main import ( basicAgent "GoServices/basic_agent/agentfunctions" //httpfunctions "GoServices/http/c2functions" //"GoServices/my_logger" //"GoServices/my_webhooks" //mytranslatorfunctions "GoServices/no_actual_translation/translationfunctions" "github.com/MythicMeta/MythicContainer" ) func main() { // load up the agent functions directory so all the init() functions execute //httpfunctions.Initialize() basicAgent.Initialize() //mytranslatorfunctions.Initialize() //my_webhooks.Initialize() //my_logger.Initialize() // sync over definitions and listen MythicContainer.StartAndRunForever([]MythicContainer.MythicServices{ //MythicContainer.MythicServiceC2, //MythicContainer.MythicServiceTranslationContainer, //MythicContainer.MythicServiceWebhook, //MythicContainer.MythicServiceLogger, MythicContainer.MythicServicePayload, }) } ``` ### 3.5 Agent Definition Check out [Payload Type Definition](/version-4.0/customizing/payload-type-development/payload-type-info/payload-type-definition) for the components that control how an agent builds and appears in Mythic. ## 4.0 Making your agent Installable To make your agent installable via `mythic-cli`, the repo/folder needs to be in a common format. This format just makes it easier for `mythic-cli` to add things to the right places. This is based on the External Agent format here ([https://github.com/MythicMeta/Mythic\_External\_Agent](https://github.com/MythicMeta/Mythic_External_Agent)). If you're creating a new payload type, then add your entire folder into the `Payload_Type` folder. Similarly, when you get around to making documentation for your agent, you can add it to the documentation folder. If there's things you don't want to include, then in the `config.json` file you can mark specific sections to exclude. ### 4.1 Mythic's Overview If you want your new C2 profile or Agent to show up on the overview page ([https://mythicmeta.github.io/overview/](https://mythicmeta.github.io/overview/)) then you need to reach out to `@its_a_feature_` on twitter or `@its_a_feature_` in the Bloodhound slack to get your agent added to the agents list here ([https://github.com/MythicMeta/overview/blob/main/agent\_repos.txt](https://github.com/MythicMeta/overview/blob/main/agent_repos.txt)). You could also make a PR to that file if you wanted too. Having your agent hosted on the `https://github.com/MythicAgents` organization means that it's easier for people to find your agent and we can collect stats on its popularity. For an example of what this means, check out the overview page and see the biweekly clone stats as well as the green chart icon for a historic list of view/clones of the repo. If you don't want to have your agent hosted on the MythicAgents organization, but still want to make it available on that site, that's fine too. Just let me know or update the PR for that file appropriately. ### 4.2. Agent Capabilities In addition to simply hosting the agent/c2 profile, there's now a sub-page that shows off all of the agent's capabilities so it's easier to compare and see which ones meet your needs. That page is here ([https://mythicmeta.github.io/overview/agent\_matrix.html](https://mythicmeta.github.io/overview/agent_matrix.html)) and is populated based on a `agent_capabilities.json` file in the root of your repository. This is just a `json` file that gets ingested at midnight every day and used to update that matrix. The format is as follows: The `os` key provides all the operating systems your agent supports. These are the things that would be available after installing your agent for the user to select when building a payload. The `languages` key identifies what languages your agent supports (typically only one, but could be multiple). The `features` section identifies which features your agent supports. For the `mythic` sub-key, the options are at the bottom of the matrix page, along with their descriptions and links to documentation for if you want to implement that feature in your agent. The `custom` sub-key is just additional features that your agent supports that you want to call out. The `payload_output` key identifies which output formats your agent supports as well as the `architectures` key identifying which architectures your agent can be built for. The `c2` key identifies which C2 Profiles your agent supports and the `supported_wrappers` key identifies which `wrapper` payloads your agent supports. As you might expect, the `mythic_version` is which Mythic version your agent supports and the `agent_version` is the current agent version in use. # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/adding-commands ## Adding New Commands So, you want to add a new command to a Payload Type. What does that mean, where do you go, what all do you have to do? Luckily, the Payload Type containers are the source of truth for everything related to them, so that's the only place you'll need to edit. If your payload type uses its own custom message format, then you might also have to edit your associated translation container, but that's up to you. Make a new `.py` file with your command class and make sure it gets imported before `mythic_container.mythic_service.start_and_run_forever` is called so that the container is aware of the command before syncing over. This new file should match the requirements of the rest of the [commands](/version-4.0/customizing/payload-type-development/adding-commands/commands#what-do-commands-track) Once you're done making edits, restart your payload type container via: `./mythic-cli start [payload type name]`. This will restart just that one payload type container, reloading the python files automatically, and re-syncing the data with Mythic. Make a new `.go` file with your new command struct instance. You can either do this as part of an `init` function so it gets picked up automatically when the package/file is imported, or you can have specific calls that initialize and register the command. Eventually, run `agentstructs.AllPayloadData.Get("agent name").AddCommand` so that the Mythic container is aware that the command exists. Make sure this line is executed before your `MythicContainer.StartAndRunForever` function call. This new file should match the requirements of the rest of the [commands](/version-4.0/customizing/payload-type-development/adding-commands/commands#what-do-commands-track) Once you're done making edits, restart your payload type container via: `./mythic-cli build [payload type name]`. This will rebuild and restart just that one payload type container and re-syncing the data with Mythic. # Container Syncing Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/payload-type-info/container-syncing ## What is it? When your container starts up, it connects to the rabbitMQ broker system. The first thing it does upon successfully connecting to the rabbitMQ broker system is to send its own synced data. Mythic then tries to look up the associated payload type and, if it can find it, will update the running status. This data is simply a JSON representation of everything about your payload - information about the payload type, all the commands, build parameters, command parameters, browser scripts, etc. ## Current Container Versions Latest versions can always be found on the Mythic [README](https://github.com/its-a-feature/Mythic). # Dynamic Build Parameters & C2 Variations Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/payload-type-info/dynamic-build-parameters ## Build Parameters Build parameters are defined on a payload type's definition and allow you to configure a payload build in a way outside of C2 specific configurations. These parameters are defined in an array with a few specific fields. Below are examples in Python and Go: ```python theme={"system"} build_parameters = [ BuildParameter( name="output_type", parameter_type=BuildParameterType.ChooseOne, choices=["WinExe", "Shellcode", "Service", "Source"], default_value="WinExe", description="Output as shellcode, executable, sourcecode, or service.", ), BuildParameter( name="shellcode_format", parameter_type=BuildParameterType.ChooseOne, choices=shellcode_format_options, default_value="Binary", description="Donut shellcode format options.", group_name="Shellcode Options", hide_conditions=[ HideCondition(name="output_type", operand=HideConditionOperand.NotEQ, value="Shellcode") ] ), BuildParameter( name="shellcode_bypass", parameter_type=BuildParameterType.ChooseOne, choices=shellcode_bypass_options, default_value="Continue on fail", description="Donut shellcode AMSI/WLDP/ETW Bypass options.", group_name="Shellcode Options", hide_conditions=[ HideCondition(name="output_type", operand=HideConditionOperand.NotEQ, value="Shellcode") ] ), BuildParameter( name="adjust_filename", parameter_type=BuildParameterType.Boolean, default_value=False, description="Automatically adjust payload extension based on selected choices.", ), BuildParameter( name="debug", parameter_type=BuildParameterType.Boolean, default_value=False, description="Create a DEBUG version.", ) ] ``` ```go theme={"system"} BuildParameters: []agentstructs.BuildParameter{ { Name: "mode", Description: "Choose the build mode option. Select default for executables, c-shared for a .dylib or .so file, or c-archive for a .Zip containing C source code with an archive and header file", Required: false, DefaultValue: "default", Choices: []string{"default", "c-archive", "c-shared"}, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_CHOOSE_ONE, }, { Name: "architecture", Description: "Choose the agent's architecture", Required: false, DefaultValue: "AMD_x64", Choices: []string{"AMD_x64", "ARM_x64"}, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_CHOOSE_ONE, }, { Name: "proxy_bypass", Description: "Ignore HTTP proxy environment settings configured on the target host?", Required: false, DefaultValue: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_BOOLEAN, GroupName: "egress", }, { Name: "garble", Description: "Use Garble to obfuscate the output Go executable.\nWARNING - This significantly slows the agent build time.", Required: false, DefaultValue: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_BOOLEAN, }, { Name: "debug", Description: "Create a debug build with print statements for debugging.", Required: false, DefaultValue: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_BOOLEAN, }, { Name: "egress_order", Description: "Prioritize the order in which egress connections are made (if including multiple egress c2 profiles)", Required: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_ARRAY, DefaultValue: []string{"http", "websocket", "dynamichttp", "httpx"}, GroupName: "egress", }, { Name: "egress_failover", Description: "How should egress mechanisms rotate", Required: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_CHOOSE_ONE, Choices: []string{"failover"}, DefaultValue: "failover", GroupName: "egress", }, { Name: "failover_threshold", Description: "How many failed attempts should cause a rotate of egress comms", Required: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_NUMBER, DefaultValue: 10, GroupName: "egress", }, { Name: "static", Description: "Statically compile the payload", Required: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_BOOLEAN, DefaultValue: false, SupportedOS: []string{agentstructs.SUPPORTED_OS_LINUX}, }, }, ``` Each build parameter has a few different pieces you can specify: * `name` - the name of the parameter (this is required) * `description` - the description of the parameter - this is displayed along with the name to give users more context about what the parameter is doing * `required` - this indicates if the user is required to specify something or not * `parameter_type` - this is the type of value for this and can be boolean, number, choose one, choose multiple, file, string, dictionary, date, array (of strings), array of files * `default_value` - the default value for the field * `choices` - a list of choices to present for a choose one or choose multiple display * `supported_os` - optional list of strings of operating systems that work with this parameter. This is the same "OS" selection that you specify as part of your supported os for the payload type definition. For example, if this is "Linux", but the user selected "macOS", then this option will NOT be presented to the user. * `group_name` - optional name you can specify to group parameters together logically in the UI * `hide_conditions` - an optional list of HideConditions (only one element in the hide condition needs to be true for the entire thing to be hidden) ### Hide Conditions Hide conditions on a build parameter allow you specify, based on the values of other build parameters, if the current build parameter should be hidden from view or not. A good example would be to hide build parameters related to shellcode options if another option specify the output type isn't shellcode. Hide Conditions are generally defined as: ```python theme={"system"} HideCondition(name="output_type", operand=HideConditionOperand.NotEQ, value="Shellcode") ``` * `name` - this specifies the name of the *OTHER* build parameter that you're looking at * `operand` - this specifies the "operand" that we're going to use. This can be one of the following: * EQ = "eq" * NotEQ = "neq" * IN = "in" * NotIN = "nin" * LessThan = "lt" * GreaterThan = "gt" * LessThanOrEqual = "lte" * GreaterThanOrEqual = "gte" * StartsWith = "sw" * EndsWith = "ew" * Contains = "co" * NotContains = "nco" * `value` - this is what we're comparing the current option's value to. In the above example, we would *HIDE* that build parameter if the `output_type` build parameter's value is *Not EQ* to the value *Shellcode*. * if you're using `in` or `nin`, then instead of `value` you'd specify `choices` ## C2 Parameter Deviations This is another really powerful addition in Mythic 3.4 - the ability for a payload type to define a variation to a supported c2 profile. This is part of the payload type's definition, but allows you to make light modifications to any parameter for a supported c2 profile. The only thing you can't do is *add* new parameter values - only remove or modify. ```python theme={"system"} c2_parameter_deviations = { "http": { "get_uri": C2ParameterDeviation(supported=False), "query_path_name": C2ParameterDeviation(supported=False), "headers": C2ParameterDeviation(supported=True, dictionary_choices=[ DictionaryChoice(name="User-Agent", default_value="Hello", default_show=True), DictionaryChoice(name="HostyHost", default_show=False, default_value=""), ]) } } ``` ```go theme={"system"} C2ParameterDeviations: map[string]map[string]agentstructs.C2ParameterDeviation{ "http": { "get_uri": { Supported: false, }, "query_path_name": { Supported: false, }, }, }, ``` The above shows an example of configuring the c2 parameter deviations in python and go. They're pretty straight forward, but they might look intimidating. They are dictionaries (map in go) where the first key is the name of the c2 profile. That value is another dictionary that maps the name of the parameter to a C2ParameterDeviation object. The C2ParameterDeviation object has a few fields: * `supported` - indicate if this field is supported or not. If it's not supported, then it won't be shown to the user. * `default_value` - your own default value instead of the one that's normally provided * `choices` - your own set of choices instead of those that are provided * `dictionary_choices` - your own set of dictionary choice objects instead of what's normally provided ## Mythic 4.0 parameter additions Build parameters can now separate stable wire values from labels shown to operators, control UI order, query choices dynamically, and collect structured JSON strings. ```python theme={"system"} from mythic_container.PayloadBuilder import ( BuildParameter, BuildParameterType, ComplexChoice, PTRPCDynamicQueryBuildParameterFunctionMessage, PTRPCDynamicQueryBuildParameterFunctionMessageResponse, ) async def query_targets( msg: PTRPCDynamicQueryBuildParameterFunctionMessage, ) -> PTRPCDynamicQueryBuildParameterFunctionMessageResponse: environment = msg.OtherParameters.get("environment", "production") return PTRPCDynamicQueryBuildParameterFunctionMessageResponse( Success=True, ComplexChoices=[ ComplexChoice(DisplayValue=f"Primary ({environment})", Value="primary"), ComplexChoice(DisplayValue=f"Backup ({environment})", Value="backup"), ], ) build_parameters = [ BuildParameter( name="environment", display_name="Deployment Environment", parameter_type=BuildParameterType.ChooseOne, choices=["prod", "dev"], choices_display_names={"prod": "Production", "dev": "Development"}, default_value="prod", ui_position=1, ), BuildParameter( name="target", display_name="Build Target", parameter_type=BuildParameterType.ChooseOne, dynamic_query_function=query_targets, ui_position=2, ), ] ``` `OtherParameters` contains the other values currently selected in the build form, which lets a dynamic query return choices conditioned on prior input. Return `Choices` for identical display/wire strings or `ComplexChoices` for independent `DisplayValue` and `Value` fields. The complete v4 fields added to these definitions are: * `display_name` — human-readable label; `name` remains the stable build key * `choices_display_names` — map stored choice values to UI labels * `ui_position` — explicit ordering within a group * `dynamic_query_function` — populate `ChooseOne` or `ChooseMultiple` values when the form needs them * `json_string_schema` — drive synchronized visual/source editing for `BuildParameterType.JSONString` See [JSONString Parameters](/version-4.0/customizing/payload-type-development/payload-type-info/json-string-parameters) for schema examples. ### Expanded hide-condition operands In addition to `eq`, `neq`, `in`, and `nin`, v4 supports numeric comparisons `lt`, `gt`, `lte`, and `gte`, and string tests `sw` (starts with), `ew` (ends with), `co` (contains), and `nco` (does not contain). Multiple conditions remain ORed: if any condition matches, Mythic hides the parameter. # JSONString Parameters Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/payload-type-info/json-string-parameters Render guided JSON forms for payload build and C2 profile parameters Mythic 4.0 adds the `JSONString` parameter type for payload build parameters and C2 profile parameters. The stored value remains a JSON **string**, while `json_string_schema` lets the Create Payload UI offer synchronized **Visual** and **Source** editors. The schema is declarative UI metadata, not JSON Schema draft-07/2020-12. Use the supported vocabulary below. ## Python example ```python theme={"system"} from mythic_container.PayloadBuilder import BuildParameter, BuildParameterType http_config_schema = { "type": "object", "label": "HTTP Configuration", "fields": [ {"name": "callback_host", "type": "string", "label": "Callback Host"}, {"name": "callback_port", "type": "number", "label": "Port"}, {"name": "use_ssl", "type": "boolean", "label": "Use TLS"}, { "name": "method", "type": "enum", "label": "HTTP Method", "choices": ["GET", "POST"], "choices_display_names": {"GET": "HTTP GET", "POST": "HTTP POST"}, }, { "name": "headers", "type": "string_map", "label": "Headers", "key_label": "Header", "value_label": "Value", }, { "name": "client_certificate", "type": "string", "label": "Client certificate", "show_when": {"field": "use_ssl", "in": [True]}, }, ], } build_parameters = [ BuildParameter( name="http_config", display_name="HTTP Configuration", parameter_type=BuildParameterType.JSONString, default_value='{"callback_host":"https://example.com","callback_port":443,"use_ssl":true,"method":"POST","headers":{}}', json_string_schema=http_config_schema, ) ] ``` The same schema can be supplied to `C2ProfileParameter(..., parameter_type=ParameterType.JSONString, json_string_schema=http_config_schema)`. ## Go example ```go theme={"system"} configSchema := map[string]interface{}{ "type": "object", "label": "HTTP Configuration", "fields": []map[string]interface{}{ {"name": "callback_host", "type": "string", "label": "Callback Host"}, {"name": "callback_port", "type": "number", "label": "Port"}, {"name": "use_ssl", "type": "boolean", "label": "Use TLS"}, { "name": "headers", "type": "string_map", "label": "Headers", "key_label": "Header", "value_label": "Value", }, }, } parameter := agentstructs.BuildParameter{ Name: "http_config", DisplayName: "HTTP Configuration", ParameterType: agentstructs.BUILD_PARAMETER_TYPE_JSON_STRING, DefaultValue: `{"callback_host":"https://example.com","callback_port":443,"use_ssl":true,"headers":{}}`, JsonStringSchema: configSchema, } ``` For a C2 profile, use `c2structs.C2_PARAMETER_TYPE_JSON_STRING` and the `JsonStringSchema` field. ## Schema vocabulary | `type` | Stored JSON value | Important fields | | ------------ | ---------------------------- | ---------------------------------------------- | | `object` | object | `fields`, where each field has a unique `name` | | `array` | array | `items` schema | | `enum` | one primitive value | `choices`, optional `choices_display_names` | | `string` | string | optional `placeholder` | | `number` | number | — | | `boolean` | boolean | — | | `string_map` | object of string keys/values | optional `key_label`, `value_label` | All nodes can include `label` and `description`. A field inside an object's `fields` can include: * `show_when: {field: "sibling_name", in: [values...]}` to control visibility; * `placeholder_when: {field: "sibling_name", map: {value: "placeholder"}}` for a contextual string placeholder. Hidden fields keep their current value. Consumers should tolerate a value that is present even when its editor control is hidden. ## Round-trip behavior The Source editor holds the exact string. Switching to Visual calls `JSON.parse`; invalid JSON blocks the switch. Each visual change is serialized back with formatted JSON. The top-level schema should therefore describe the JSON document itself, normally an `object`. The UI seeds missing values as `{}` for objects and maps, `[]` for arrays, `""` for strings, `0` for numbers, `false` for booleans, and the first enum choice when available. # Payload Type Definition Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/payload-type-info/payload-type-definition ## 1.0 Payload Definition Payload Type information must be set and pulled from a definition either in Python or in GoLang. Below are basic examples in Python and GoLang: ```python theme={"system"} from mythic_container.PayloadBuilder import * from mythic_container.MythicCommandBase import * from mythic_container.MythicRPC import * import json class Apollo(PayloadType): name = "apollo" file_extension = "exe" agent_type = AgentType.Agent author = "@djhohnstein, @its_a_feature_" mythic_encrypts = True supported_os = [ SupportedOS.Windows ] semver = "2.3.51" note = """ A fully featured .NET 4.0 compatible training agent. Version: {}. NOTE: P2P Not compatible with v2.2 agents! NOTE: v2.3.2+ has a different bof loader than 2.3.1 and are incompatible since their arguments are different """.format(semver) supports_dynamic_loading = True supports_multiple_c2_instances_in_build = False supports_multiple_c2_in_build = False c2_parameter_deviations = { "http": { "get_uri": C2ParameterDeviation(supported=False), "query_path_name": C2ParameterDeviation(supported=False), #"headers": C2ParameterDeviation(supported=True, dictionary_choices=[ # DictionaryChoice(name="User-Agent", default_value="Hello", default_show=True), # DictionaryChoice(name="HostyHost", default_show=False, default_value=""), #]) } } build_parameters = [ BuildParameter( name="output_type", parameter_type=BuildParameterType.ChooseOne, choices=["WinExe", "Shellcode", "Service", "Source"], default_value="WinExe", description="Output as shellcode, executable, sourcecode, or service.", ), BuildParameter( name="shellcode_format", parameter_type=BuildParameterType.ChooseOne, choices=shellcode_format_options, default_value="Binary", description="Donut shellcode format options.", group_name="Shellcode Options", hide_conditions=[ HideCondition(name="output_type", operand=HideConditionOperand.NotEQ, value="Shellcode") ] ), BuildParameter( name="shellcode_bypass", parameter_type=BuildParameterType.ChooseOne, choices=shellcode_bypass_options, default_value="Continue on fail", description="Donut shellcode AMSI/WLDP/ETW Bypass options.", group_name="Shellcode Options", hide_conditions=[ HideCondition(name="output_type", operand=HideConditionOperand.NotEQ, value="Shellcode") ] ), BuildParameter( name="adjust_filename", parameter_type=BuildParameterType.Boolean, default_value=False, description="Automatically adjust payload extension based on selected choices.", ), BuildParameter( name="debug", parameter_type=BuildParameterType.Boolean, default_value=False, description="Create a DEBUG version.", ) ] c2_profiles = ["http", "smb", "tcp", "websocket"] agent_path = pathlib.Path(".") / "apollo" / "mythic" agent_code_path = pathlib.Path(".") / "apollo" / "agent_code" agent_icon_path = agent_path / "agent_functions" / "apollo.svg" build_steps = [ BuildStep(step_name="Gathering Files", step_description="Copying files to temp location"), BuildStep(step_name="Compiling", step_description="Compiling with nuget and dotnet"), BuildStep(step_name="Donut", step_description="Converting to Shellcode"), BuildStep(step_name="Creating Service", step_description="Creating Service EXE from Shellcode") ] # here you can optionally define your own help function for getting help with your payload type's commands #async def command_help_function(self, msg: HelpFunctionMessage) -> HelpFunctionMessageResponse: # return HelpFunctionMessageResponse(output=f"we did it!\nInput: {msg}", success=False) async def build(self) -> BuildResponse: # this function gets called to create an instance of your payload resp = BuildResponse(status=BuildStatus.Success) return resp ``` There are a couple key pieces of information here: * line 6 defines the new class (our agent). This can be called whatever you want, but the important piece is that it extends the `PayloadType` class as shown with the `()`. * the rest defines the parameters for the payload type that you'd see throughout the UI. * the name is the name of the payload type * supported\_os is an array of supported OS versions * supports\_dynamic\_loading indicates if the agent allows you to select only a subset of commands when creating an agent or not * build\_parameters is an array describing all of the build parameters when creating your agent * c2\_profiles is an array of c2 profile names that the agent supports * c2\_parameter\_deviations allows you to modify the parameters of supported c2 profiles. In this case the apollo agent doesn't support GET requests (only POST), so we mark the `http` profile's GET parameters as unsupported. * the "translation container" is something we will talk about in another section, but this allows you to support your own, non-mythic message format, custom crypto, etc. * The last piece is the function that's called to **build** the agent based on all of the information the user provides from the web UI. The `PayloadType` base class is in the `PayloadBuilder.py` file. This is an abstract class, so your instance needs to provide values for all these fields. ```go theme={"system"} package agentfunctions import ( "bytes" "encoding/json" "fmt" agentstructs "github.com/MythicMeta/MythicContainer/agent_structs" "github.com/MythicMeta/MythicContainer/mythicrpc" "os" "os/exec" "path/filepath" "strings" ) var payloadDefinition = agentstructs.PayloadType{ Name: "basicAgent", FileExtension: "bin", Author: "@xorrior, @djhohnstein, @Ne0nd0g, @its_a_feature_", SupportedOS: []string{agentstructs.SUPPORTED_OS_LINUX, agentstructs.SUPPORTED_OS_MACOS}, Wrapper: false, SupportsDynamicLoading: false, Description: "A fully featured macOS and Linux Golang agent", SupportedC2Profiles: []string{"http", "websocket", "poseidon_tcp"}, MythicEncryptsData: true, BuildParameters: []agentstructs.BuildParameter{ { Name: "mode", Description: "Choose the build mode option. Select default for executables, c-shared for a .dylib or .so file, or c-archive for a .Zip containing C source code with an archive and header file", Required: false, DefaultValue: "default", Choices: []string{"default", "c-archive", "c-shared"}, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_CHOOSE_ONE, }, { Name: "architecture", Description: "Choose the agent's architecture", Required: false, DefaultValue: "AMD_x64", Choices: []string{"AMD_x64", "ARM_x64"}, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_CHOOSE_ONE, }, { Name: "proxy_bypass", Description: "Ignore HTTP proxy environment settings configured on the target host?", Required: false, DefaultValue: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_BOOLEAN, }, { Name: "garble", Description: "Use Garble to obfuscate the output Go executable.\nWARNING - This significantly slows the agent build time.", Required: false, DefaultValue: false, ParameterType: agentstructs.BUILD_PARAMETER_TYPE_BOOLEAN, }, }, BuildSteps: []agentstructs.BuildStep{ { Name: "Configuring", Description: "Cleaning up configuration values and generating the golang build command", }, { Name: "Compiling", Description: "Compiling the golang agent (maybe with obfuscation via garble)", }, }, } func build(payloadBuildMsg agentstructs.PayloadBuildMessage) agentstructs.PayloadBuildResponse { payloadBuildResponse := agentstructs.PayloadBuildResponse{ PayloadUUID: payloadBuildMsg.PayloadUUID, Success: true, UpdatedCommandList: &payloadBuildMsg.CommandList, } return payloadBuildResponse } ``` ### 1.1 Wrapper Payloads A wrapper payload type uses `agent_type = AgentType.Wrapper`, accepts the output of an earlier payload build plus its own build parameters, and emits a new payload. It does not select C2 profiles because it wraps an existing build. Normal builders return `build_metadata` describing their architecture and format, while wrappers declare conditional `wrapper_payload_requirements`. Mythic uses both definitions and the wrapper's selected build parameters to show only compatible payloads. See [Wrapper Compatibility](/version-4.0/customizing/payload-type-development/payload-type-info/wrapper-compatibility) for the Python and Go definitions and a compatibility diagram. An easy example is thinking of the `service_wrapper` - this wrapper payload type takes in the shellcode version of another payload and "wraps" it in the execution of a service so that it'll properly respond to the service control manager on windows. A similar example would be to take an agent and wrap it in an MSBuild format. These things don't have their own C2, but rather just package/wrap an existing agent into a new, more generic, format. To access the payload that you're going to wrap, use the `self.wrapped_payload` attribute during your `build` execution. This will be the base64 encoded version of the payload you're going to wrap. To access the payload that you're going to wrap, use the `payloadBuildMsg.WrappedPayload` attribute during your `build` execution. This will be the raw bytes of the payload you're going to wrap. If you want to fetch more details about the payload that's wrapped, you can use the `payloadBuildMsg.WrappedPayloadUUID` and Mythic Scripting/MythicRPC When you're done generating the payload, return the new result the same way as a normal payload in the [build](#30-building) process. ## 2.0 Build Parameters Build parameters define the components shown to the user when creating a payload. The `BuildParameter` class has a couple of pieces of information that you can use to customize and validate the parameters supplied to your build: ```python theme={"system"} class BuildParameterType(str, Enum): """Types of parameters available for building payloads Attributes: String: A string value ChooseOne: A list of choices for the user to select exactly one ChooseMultiple: A list of choices for the user to select 0 or more ChooseOneCustom: A list of choices for the user to select exactly one OR write in their own Array: The user can supply multiple values in an Array format Date: The user can select a Date in YYYY-MM-DD format Dictionary: The user can supply a dictionary of values Boolean: The user can toggle a switch for True/False File: The user can select a file that gets uploaded - a file UUID gets passed in during build FileMultiple: The user can select multiple files that get uploaded - an array of file UUIDs gets passed in during build TypedArray: The user can supply an array where each element also has a drop-down option of choices Number: An number JSONString A JSON string value with a json schema defined """ String = "String" ChooseOne = "ChooseOne" ChooseOneCustom = "ChooseOneCustom" ChooseMultiple = "ChooseMultiple" Array = "Array" Date = "Date" Dictionary = "Dictionary" Boolean = "Boolean" File = "File" FileMultiple = "FileMultiple" TypedArray = "TypedArray" Number = "Number" JSONString = "JSONString" class BuildParameter: """Build Parameter Definition for use when generating payloads Attributes: name (str): Name of the parameter for scripting and for when building payloads display_name (str): Human-friendly name of the parameter for display in the UI description (str): Informative description displayed when building a payload default_value (any): Default value to pre-populate randomize (bool): Should this value be randomized (requires format_string) format_string (str): A regex used for randomizing values if randomize is true parameter_type (BuildParameterType): The type of parameter this is required (bool): Is this parameter required to have a non-empty value or not verifier_regex (str): Regex used to verify that the user typed something appropriate choices (list[str]): Choices for ChooseOne parameter type choices_display_names (dict[str, str]): Optional map of choice value to display label dictionary_choices (list[DictionaryChoice]): Configuration options for the Dictionary parameter type crypto_type (bool): Indicate if this value should be used to generate a crypto key or not group_name (str): An optional name used to group parameters together in the UI supported_os (list[str]): An optional list of supported operating systems where this parameter applies hide_conditions (list[HideCondition]): An optional list of conditions where this parameter should be hidden from view (this is a series of OR not AND conditions) ui_position (int): An optional position/ordering of parameters when displayed in the UI dynamic_query_function: Provide a dynamic query function to be called when the user views that parameter option in the UI to populate choices for the ChooseOne or ChooseMultiple Parameter Types. json_string_schema (dict): Declarative schema describing the parameter's JSON shape so the Mythic UI can render a Visual editor. """ def __init__( self, name: str, display_name: str = "", parameter_type: BuildParameterType = None, description: str = None, required: bool = None, randomize: bool = None, format_string: str = "", crypto_type: bool = False, verifier_regex: str = None, default_value: any = None, choices: list[str] = None, choices_display_names: dict = None, dictionary_choices: list[DictionaryChoice] = None, value: any = None, verifier_func: callable = None, group_name: str = None, supported_os: list[str] = None, hide_conditions: list[HideCondition] = None, ui_position: int = 0, json_string_schema: dict = None, dynamic_query_function: Callable[ [PTRPCDynamicQueryBuildParameterFunctionMessage], Awaitable[PTRPCDynamicQueryBuildParameterFunctionMessageResponse]] = None, ): self.name = name self.display_name = display_name self.verifier_func = verifier_func self.parameter_type = ( parameter_type if parameter_type is not None else BuildParameterType.String ) self.description = description if description is not None else "" self.required = required if required is not None else True self.verifier_regex = verifier_regex if verifier_regex is not None else "" self.default_value = default_value if value is None: self.value = default_value else: self.value = value self.choices = choices self.dictionary_choices = dictionary_choices self.choices_display_names = choices_display_names self.crypto_type = crypto_type self.randomize = randomize self.format_string = format_string self.group_name = group_name self.supported_os = supported_os self.hide_conditions = hide_conditions self.ui_position = ui_position self.dynamic_query_function = dynamic_query_function if not callable(dynamic_query_function) and dynamic_query_function is not None: raise Exception("dynamic_query_function is not callable") self.json_string_schema = json_string_schema def to_json(self): return { "name": self.name, "display_name": self.display_name if self.display_name != "" else self.name, "parameter_type": self.parameter_type.value, "description": self.description, "required": self.required, "verifier_regex": self.verifier_regex, "default_value": self.default_value, "choices": self.choices, "choices_display_names": self.choices_display_names if self.choices_display_names is not None else {}, "dictionary_choices": [x.to_json() for x in self.dictionary_choices] if self.dictionary_choices is not None else None, "crypto_type": self.crypto_type, "randomize": self.randomize, "format_string": self.format_string, "group_name": self.group_name, "supported_os": self.supported_os, "hide_conditions": [x.to_json() for x in self.hide_conditions] if self.hide_conditions is not None else None, "ui_position": self.ui_position, "dynamic_query_function": self.dynamic_query_function.__name__ if callable(self.dynamic_query_function) else None, "json_string_schema": self.json_string_schema, } def __str__(self): return json.dumps(self.to_json(), sort_keys=True, indent=2) ``` ```go theme={"system"} type BuildParameterType = string const ( BUILD_PARAMETER_TYPE_STRING BuildParameterType = "String" BUILD_PARAMETER_TYPE_BOOLEAN = "Boolean" BUILD_PARAMETER_TYPE_CHOOSE_ONE = "ChooseOne" BUILD_PARAMETER_TYPE_CHOOSE_ONE_CUSTOM = "ChooseOneCustom" BUILD_PARAMETER_TYPE_CHOOSE_MULTIPLE = "ChooseMultiple" BUILD_PARAMETER_TYPE_ARRAY = "Array" BUILD_PARAMETER_TYPE_DATE = "Date" BUILD_PARAMETER_TYPE_DICTIONARY = "Dictionary" BUILD_PARAMETER_TYPE_NUMBER = "Number" BUILD_PARAMETER_TYPE_TYPED_ARRAY = "TypedArray" BUILD_PARAMETER_TYPE_FILE = "File" BUILD_PARAMETER_TYPE_FILE_MULTIPLE = "FileMultiple" BUILD_PARAMETER_TYPE_JSON_STRING = "JSONString" ) type BuildParameter struct { // Name - the name of the build parameter for use during the Payload Type's build function Name string `json:"name"` // DisplayName - Human-friendly name of the build parameter to be presented to the user during build DisplayName string `json:"display_name"` // Description - the description of the build parameter to be presented to the user during build Description string `json:"description"` // Required - indicate if this requires the user to supply a value or not Required bool `json:"required"` // VerifierRegex - if the user is supplying text and it needs to match a specific pattern, specify a regex pattern here and the UI will indicate to the user if the value is valid or not VerifierRegex string `json:"verifier_regex"` // DefaultValue - A default value to show the user when building in the Mythic UI. The type here depends on the Parameter Type - ex: for a String, supply a string. For an array, provide an array DefaultValue interface{} `json:"default_value"` // ParameterType - The type of parameter this is so that the UI can properly render components for the user to modify ParameterType BuildParameterType `json:"parameter_type"` // FormatString - If Randomize is true, this regex format string is used to generate a value when presenting the option to the user FormatString string `json:"format_string"` // Randomize - Should this value be randomized each time it's shown to the user so that each payload has a different value Randomize bool `json:"randomize"` // IsCryptoType -If this is True, then the value supplied by the user is for determining the _kind_ of crypto keys to generate (if any) and the resulting stored value in the database is a dictionary composed of the user's selected and an enc_key and dec_key value IsCryptoType bool `json:"crypto_type"` // Choices - If the ParameterType is ChooseOne or ChooseMultiple, then the options presented to the user are here. Choices []string `json:"choices"` // ChoicesDisplayNames - Human-friendly names for the choices presented to the user ChoicesDisplayNames map[string]string `json:"choices_display_names"` // DictionaryChoices - if the ParameterType is Dictionary, then the dictionary choices/preconfigured data is set here DictionaryChoices []BuildParameterDictionary `json:"dictionary_choices"` JsonStringSchema map[string]interface{} `json:"json_string_schema"` GroupName string `json:"group_name"` SupportedOS []string `json:"supported_os"` HideConditions []BuildParameterHideCondition `json:"hide_conditions"` UiPosition int `json:"ui_position"` DynamicQueryFunction PTRPCBuildParameterDynamicQueryFunction `json:"dynamic_query_function"` } ``` * `name` is the name of the parameter, if you don't provide a longer description, then this is what's presented to the user when building your payload * `parameter_type` describes what is presented to the user - valid types are: * `BuildParameterType.String` * During build, this is a string * `BuildParameterType.ChooseOne` * During build, this is a string * `BuildParameterType.ChooseOneCustom` * During build, this is a string * `BuildParameterType.ChooseMultiple` * During build, this is an array of strings * `BuildParameterType.Array` * During build, this is an array of strings * `BuildParameterType.Date` * During build, this is a string of the format `YYYY-MM-DD` * `BuildParameterType.Dictionary` * During build, this is a dictionary * `BuildParameterType.Boolean` * During build, this is a boolean * `BuildParameterType.File` * During build, this is a string UUID of the file (so that you can use a MythicRPC call to fetch the contents of the file) * `BuildParameterType.TypedArray` * During build, this is an array of arrays, always in the format `[ [ type, value], [type value], [type, value] ...]` * `required` indicates if there must be a value supplied. If no value is supplied by the user and no default value supplied here, then an exception is thrown before execution gets to the `build` function * `verifier_regex` is a regex the web UI can use to provide some information to the user about if they're providing a valid value or not * `default_value` is the default value used for building if the user doesn't supply anything * `choices` is where you can supply an array of options for the user to pick from if the parameter\_type is ChooseOne * `dictionary_choice`s are the choices and metadata about what to display to the user for key-value pairs that the user might need to supply * `value` is the component you access when building your payload - this is the final value (either the default value or the value the user supplied) * `verifier_func` is a function you can provide for additional checks on the value the user supplies to make sure it's what you want. This function should either return nothing or raise an exception if something isn't right ## 3.0 Building You have to implement the `build` function and return an instance of the `BuildResponse` class. This response has these fields: * `status` - an instance of BuildStatus (Success or Error) * Specifically, `BuildStatus.Success` or `BuildStatus.Error` * `payload` - the raw bytes of the finished payload (if you failed to build, set this to `None` or empty bytes like `b''` in Python. * `build_message` - any stdout data you want the user to see * `build_stderr` - any stderr data you want the user to see * `build_stdout` - any stdout data you want the user to see * `updated_filename` - if you want to update the filename to something more appropriate, set it here * For example: the user supplied a filename of `apollo.exe` but based on the build parameters, you're actually generating a dll, so you can update the filename to be `apollo.dll`. This is particularly useful if you're optionally returning a zip of information so that the user doesn't have to change the filename before downloading. If you plan on doing this to update the filename for a wide variety of options, then it might be best to leave the file extension field in your payload type definition blank `""` so that you can more easily adjust the extension. * `updated_command_list` - if you want to adjust the list of commands that are included by default in the payload - this is helpful if you need to include extra commands or dependencies based on what the user selected. * `build_metadata` - in v4, report the output architecture and format so Mythic can evaluate wrapper compatibility. Mythic records the selected operating system separately. See [Wrapper Compatibility](/version-4.0/customizing/payload-type-development/payload-type-info/wrapper-compatibility). The most basic version of the build function would be: ```python theme={"system"} async def build(self) -> BuildResponse: # this function gets called to create an instance of your payload return BuildResponse(status=BuildStatus.Success) ``` ```go theme={"system"} func build(payloadBuildMsg agentstructs.PayloadBuildMessage) agentstructs.PayloadBuildResponse { payloadBuildResponse := agentstructs.PayloadBuildResponse{ PayloadUUID: payloadBuildMsg.PayloadUUID, Success: true, } return payloadBuildResponse } ``` Once the `build` function is called, all of your `BuildParameters` will already be verified (all parameters marked as `required` will have a `value` of some form (user supplied or default\_value) and all of the verifier functions will be called if they exist). This allows you to *know* that by the time your `build` function is called that all of your parameters are valid. Your build function gets a few pieces of information to help you build the agent (other than the build parameters): From within your build function, you'll have access to the following pieces of information: * `self.uuid` - the UUID associated with your payload * This is how your payload identifies itself to Mythic before getting a new Staging and final Callback UUID * `self.commands` - a wrapper class around the names of all the commands the user selected. * Access this list via `self.commands.get_commands()` ```python theme={"system"} for cmd in self.commands.get_commands(): command_code += open(self.agent_code_path / "{}.js".format(cmd), 'r').read() + "\n" ``` * `self.agent_code_path` - a `pathlib.Path` object pointing to the path of the `agent_code` directory that holds all the code for your payload. This is something you pre-define as part of your agent definition. * To access "test.js" in that "agent\_code" folder, simply do:\ `f = open(self.agent_code_path / "test.js", 'r')`. * With `pathlib.Path` objects, the `/` operator allows you to concatenate paths in an OS agnostic manner. This is the recommended way to access files so that your code can work anywhere. * `self.get_parameter("parameter name here")` * The build parameters that are validated from the user. If you have a build\_parameter with a name of "version", you can access the user supplied or default value with `self.get_parameter("version")` * `self.selected_os` - This is the OS that was selected on the first step of creating a payload * `self.c2info` - this holds a list of dictionaries of the c2 parameters and c2 class information supplied by the user. This is a list because the user can select multiple c2 profiles (maybe they want HTTP and SMB in the payload for example). For each element in self.c2info, you can access the information about the c2 profile with `get_c2profile()` and access to the parameters via `get_parameters_dict()`. Both of these return a dictionary of key-value pairs. * the dictionary returned by `self.c2info[0].get_c2profile()` contains the following: * `name` - name of the c2 profile * `description` - description of the profile * `is_p2p` - boolean of if the profile is marked as a p2p profile or not * the dictionary returned by `self.c2info[0].get_parameters_dict()`contains the following: * `key` - value * where each `key` is the C2 parameter `name` and `value` is what the operator supplied. See [C2 Parameters](/version-4.0/customizing/c2-related-development/mythic-definition/2.1.2-c2-parameters). * If the C2 parameter has a value of `crypto_type=True`, then the "value" here will be a bit more than just a string that the user supplied. Instead, it'll be a dictionary with three pieces of information: `value` - the value that the user supplied, `enc_key` - a base64 string (or None) of the encryption key to be used, `dec_key` - a base64 string (or None) of the decryption key to be used. This gives you more flexibility in automatically generating encryption/decryption keys and supporting crypto types/schemas that Mythic isn't aware of. In the HTTP profile, the key `AESPSK` has this type set to True, so you'd expect that dictionary. * If the C2 parameter has a type of "Dictionary", then things are a little different. * Let's take the "headers" parameter in the `http` profile for example. This allows you to set header values for your `http` traffic such as User-Agent, Host, and more. When you get this value on the agent side, you get an array of values that look like the following:\ `{"User-Agent": "the user agent the user supplied", "MyCustomHeader": "my custom value"}`. You get the final "dictionary" that's created from the user supplied fields. * One way to leverage this could be: ```python theme={"system"} for c2 in self.c2info: c2_code = "" try: profile = c2.get_c2profile() c2_code = open( self.agent_code_path / "c2_profiles" / "{}.js".format(profile["name"]), "r", ).read() for key, val in c2.get_parameters_dict().items(): if key == "AESPSK": c2_code = c2_code.replace(key, val["enc_key"] if val["enc_key"] is not None else "") elif not isinstance(val, str): c2_code = c2_code.replace(key, json.dumps(val)) else: c2_code = c2_code.replace(key, val) except Exception as p: build_msg += str(p) pass ``` ```go theme={"system"} // PayloadBuildMessage - A structure of the build information the user provided to generate an instance of the payload type. // This information gets passed to your payload type's build function. type PayloadBuildMessage struct { // PayloadType - the name of the payload type for the build PayloadType string `json:"payload_type" mapstructure:"payload_type"` // Filename - the name of the file the user originally supplied for this build Filename string `json:"filename" mapstructure:"filename"` // CommandList - the list of commands the user selected to include in the build CommandList []string `json:"commands" mapstructure:"commands"` // build param name : build value // BuildParameters - map of param name -> build value from the user for the build parameters defined // File type build parameters are supplied as a string UUID to use with MythicRPC for fetching file contents // Array type build parameters are supplied as []string{} BuildParameters PayloadBuildArguments `json:"build_parameters" mapstructure:"build_parameters"` // C2Profiles - list of C2 profiles selected to include in the payload and their associated parameters C2Profiles []PayloadBuildC2Profile `json:"c2profiles" mapstructure:"c2profiles"` // WrappedPayload - bytes of the wrapped payload if one exists WrappedPayload *[]byte `json:"wrapped_payload,omitempty" mapstructure:"wrapped_payload"` // WrappedPayloadUUID - the UUID of the wrapped payload if one exists WrappedPayloadUUID *string `json:"wrapped_payload_uuid,omitempty" mapstructure:"wrapped_payload_uuid"` // SelectedOS - the operating system the user selected when building the agent SelectedOS string `json:"selected_os" mapstructure:"selected_os"` // PayloadUUID - the Mythic generated UUID for this payload instance PayloadUUID string `json:"uuid" mapstructure:"uuid"` // PayloadFileUUID - The Mythic generated File UUID associated with this payload PayloadFileUUID string `json:"payload_file_uuid" mapstructure:"payload_file_uuid"` } ``` Depending on the status of your build (success or error), either the message or build\_stderr values will be presented to the user via the UI notifications. However, at any time you can go back to the Created Payloads page and view the build message, build errors, and build stdout for any payload. When building your payload, if you have to modify files on disk, then it's helpful to do this in a "copy" of the files. You can make a temporary copy of your code and operate there with the following sample: ```python theme={"system"} agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid) # shutil to copy payload files over copy_tree(self.agent_code_path, agent_build_path.name) # now agent_build_path.name maps to the root folder for your agent code ``` ### 3.1 Build Steps The last thing to mention are build steps. These are defined as part of the agent and are simply descriptions of what is happening during your build process. The above example makes some RPC calls for `SendMythicRPCPayloadUpdatebuildStep` to update the build steps back to Mythic while the build process is happening. For something as fast as the `apfell` agent, it'll appear as though all of these happen at the same time. For something that's more computationally intensive though, it's helpful to provide information back to the user about what's going on - stamping in values? obfuscating? compiling? more obfuscation? opsec checks? etc. Whatever it is that's going on, you can provide this data back to the operator complete with stdout and stderr. ### 3.2 Execution flow So, what's the actual, end-to-end execution flow that goes on? A diagram can be found here: [#what-happens-for-building-payloads](../../../message-flow/#what-happens-for-building-payloads "mention"). 1. PayloadType container is started, it connects to Mythic and sends over its data (by parsing all these python files or GoLang structs) 2. An operator wants to create a payload from it, so they click the hazard icon at the top of Mythic, click the "Actions" dropdown and select "Generate New Payload". 3. The operator selects an OS type that the agent supports (ex. Linux, macOS, Windows) 4. The operator selects the payload type they want to build (this one) 1. edits all build parameters as needed 5. The operator selects all commands they want included in the payload 6. The operator selects all c2 profiles they want included 1. and for each c2 selected, provides any c2 required parameters 7. Mythic takes all of this information and sends it to the payload type container 8. The container sends the `BuildResponse` message back to the Mythic server. ## 4.0 On New Callback Starting with Mythic v3.2.12, PyPi version 0.4.1, and MythicContainer version 1.3.1, there's a new function you can define as part of your Payload Type definition. In addition to defining a `build` process, you can also define a `on_new_callback` (or `onNewCallbackFunction`) function that will get executed whenever there's a new callback based on this payload type. Below are examples in Python and in Golang for how to define and leverage this new functionality. One of the great things about this is that you can use this to automatically issue tasking for new callbacks. The below examples will automatically issue a `shell` command with parameters of `whoami`. These function calls get almost all the same data that you'll see in your [Create Tasking](/version-4.0/customizing/payload-type-development/create_tasking#create-tasking) calls, except they're missing information about a `Task`. That's simply because there's no task yet, this is the moment that a new callback is created. Mythic tracks an operator for all issued tasking. Since there's no operator directly typing out and issuing these tasks, Mythic associates the operator that built the payload with any tasks automatically created in this function. ```python theme={"system"} class Apfell(PayloadType): name = "apfell" ... async def build ... async def on_new_callback(self, newCallback: PTOnNewCallbackAllData) -> PTOnNewCallbackResponse: new_task_resp = await SendMythicRPCTaskCreate(MythicRPCTaskCreateMessage( AgentCallbackUUID=newCallback.Callback.AgentCallbackID, CommandName="shell", Params="whoami", )) if new_task_resp.Success: return PTOnNewCallbackResponse(AgentCallbackUUID=newCallback.Callback.AgentCallbackID, Success=True) return PTOnNewCallbackResponse(AgentCallbackUUID=newCallback.Callback.AgentCallbackID, Success=False, Error=new_task_resp.Error) ``` ```go theme={"system"} func onNewBuild(data agentstructs.PTOnNewCallbackAllData) agentstructs.PTOnNewCallbackResponse { newTasking, err := mythicrpc.SendMythicRPCTaskCreate(mythicrpc.MythicRPCTaskCreateMessage{ AgentCallbackID: data.Callback.AgentCallbackID, CommandName: "shell", Params: "whoami", }) if err != nil { logging.LogError(err, "failed to create new task") } if newTasking.Success { logging.LogInfo("created new task") } else { logging.LogError(err, "failed to create new tasking") } return agentstructs.PTOnNewCallbackResponse{ AgentCallbackID: data.Callback.AgentCallbackID, Success: true, Error: "", } } func Initialize() { agentstructs.AllPayloadData.Get("poseidon").AddPayloadDefinition(payloadDefinition) agentstructs.AllPayloadData.Get("poseidon").AddBuildFunction(build) agentstructs.AllPayloadData.Get("poseidon").AddOnNewCallbackFunction(onNewBuild) agentstructs.AllPayloadData.Get("poseidon").AddIcon(filepath.Join(".", "poseidon", "agentfunctions", "poseidon.svg")) } ``` ## 5.0 CustomRPCFunctions Payload types have an optional field that can be defined: ```python theme={"system"} class PTOtherServiceRPCMessage: """Request to call an RPC function of another C2 Profile or Payload Type Attributes: ServiceName (str): Name of the C2 Profile or Payload Type ServiceRPCFunction (str): Name of the function to call ServiceRPCFunctionArguments (dict): Arguments to that function Functions: to_json(self): return dictionary form of class """ def __init__(self, ServiceName: str = None, service_name: str = None, ServiceRPCFunction: str = None, service_function: str = None, ServiceRPCFunctionArguments: dict = None, service_arguments: dict = None, **kwargs): self.ServiceName = ServiceName if self.ServiceName is None: self.ServiceName = service_name self.ServiceRPCFunction = ServiceRPCFunction if self.ServiceRPCFunction is None: self.ServiceRPCFunction = service_function self.ServiceRPCFunctionArguments = ServiceRPCFunctionArguments if self.ServiceRPCFunctionArguments is None: self.ServiceRPCFunctionArguments = service_arguments for k, v in kwargs.items(): logger.error(f"unknown kwarg {k} {v}") def to_json(self): return { "service_name": self.ServiceName, "service_function": self.ServiceRPCFunction, "service_arguments": self.ServiceRPCFunctionArguments } def __str__(self): return json.dumps(self.to_json(), sort_keys=True, indent=2) class PTOtherServiceRPCMessageResponse: """Result of running an RPC call from another service Attributes: Success (bool): Did the RPC succeed or fail Error (str): Error message if the RPC check failed Result (dict): Result from the RPC Functions: to_json(self): return dictionary form of class """ def __init__(self, success: bool = None, error: str = None, result: dict = None, Success: bool = None, Error: str = None, Result: dict = None, **kwargs): self.Success = Success if self.Success is None: self.Success = success self.Error = Error if self.Error is None: self.Error = error self.Result = Result if self.Result is None: self.Result = result for k, v in kwargs.items(): logger.error(f"unknown kwarg {k} {v}") def to_json(self): return { "success": self.Success, "error": self.Error, "result": self.Result } def __str__(self): return json.dumps(self.to_json(), sort_keys=True, indent=2) custom_rpc_functions: dict[ str, Callable[[PTOtherServiceRPCMessage], Awaitable[PTOtherServiceRPCMessageResponse]]] = {} ``` ```go theme={"system"} // PTRPCOtherServiceRPCMessage - A message to call RPC functionality exposed by another Payload Type or C2 Profile type PTRPCOtherServiceRPCMessage struct { // Name - The name of the remote Payload type or C2 Profile Name string `json:"service_name"` //required // RPCFunction - The name of the function to call for that remote service RPCFunction string `json:"service_function"` // RPCFunctionArguments - A map of arguments to supply to that remote function RPCFunctionArguments map[string]interface{} `json:"service_arguments"` } // PTRPCOtherServiceRPCMessageResponse - The result of calling RPC functionality exposed by another Payload Type or C2 Profile type PTRPCOtherServiceRPCMessageResponse struct { // Success - An indicator if the call was successful or not Success bool `json:"success"` // Error - If the call was unsuccessful, this is an error message about what happened Error string `json:"error"` // Result - The result returned by the remote service Result map[string]interface{} `json:"result"` } CustomRPCFunctions map[string]func(message PTRPCOtherServiceRPCMessage) PTRPCOtherServiceRPCMessageResponse `json:"-"` ``` This dictionary of functions is a way for a Payload Type/C2 Profile to define custom RPC functions that are callable from other containers. This can be particularly handy if you have a Payload Build function that needs to ask a C2 Profile to configure something in a specific way on its behalf. The same thing also applies to C2 Profiles - C2 Profiles can ask Payload Type containers to do things. The definitions are particularly vague in the arguments needed (ex: a generic dictionary/map) because it's up to the function to define what is needed. # Turning a VM into a Container Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/payload-type-info/turning-a-vm-into-a-container There are scenarios in which you need a Mythic container for an agent, but you can't (or don't want) to use the normal docker containers that Mythic uses. This could be for reasons like: * You have a custom build environment that you don't want to recreate * You have specific kernel versions or operating systems you're wanting to develop with So, to leverage your own custom VM or physical computer into a Mythic recognized container, there are just a few steps. External agents need to connect to `mythic_rabbitmq` in order to send/receive messages. They also need to connect to the `mythic_server` to transfer files and potentially use gRPC. By default, these container is bound on localhost only. In order to have an external agent connect up, you will need to adjust this in the `Mythic/.env` file to have `RABBITMQ_BIND_LOCALHOST_ONLY=false` and `MYTHIC_SERVER_BIND_LOCALHOST_ONLY=false` and restart Mythic (`sudo ./mythic-cli restart`). 1. Install python 3.11+ (or Golang 1.23) in the VM or on the computer 2. `pip3 install mythic-container` (this has all of the definitions and functions for the container to sync with Mythic and issue RPC commands). Make sure you get the right version of this PyPi package for the version of Mythic you're using ([#current-payloadtype-versions](/version-4.0/customizing/payload-type-development/payload-type-info/container-syncing#current-payloadtype-versions)). Alternatively, `go get -u github.com/MythicMeta/MythicContainer` for golang. 3. Create a folder on the computer or VM (let's call it path `/pathA`). Essentially, your `/pathA` path will be the new `InstalledServices/[agent name]` folder. Create a sub folder for your actual agent's code to live, like `/pathA/agent_code`. You can create a Visual Studio project here and simply configure it however you need. 4. Your command function definitions and payload definition are also helpful to have in a folder, like `/pathA/agent_functions`. 5. Edit the `/pathA/rabbitmq_config.json` with the parameters you need ``` { "rabbitmq_host": "127.0.0.1", "rabbitmq_password": "PqR9XJ957sfHqcxj6FsBMj4p", "mythic_server_host": "127.0.0.1", "webhook_default_channel": "#mythic-notifications", "debug_level": "debug", "rabbitmq_port": 5432, "mythic_server_grpc_port": 17444, "webhook_default_url": "", "webhook_default_callback_channel": "", "webhook_default_feedback_channel": "", "webhook_default_startup_channel": "", "webhook_default_alert_channel": "", "webhook_default_custom_channel": "", } ``` 1. the `mythic_server_host` value should be the IP address of the main Mythic install 2. the `rabbitmq_host` value should be the IP address of the main Mythic install unless you're running rabbitmq on another host. 3. You'll need the password of rabbitmq from your Mythic instance. You can either get this from the `Mythic/.env` file, by running `sudo ./mythic-cli config get rabbitmq_password`, or if you run `sudo ./mythic-cli config payload` you'll see it there too. 6. External agents need to connect to `mythic_rabbitmq` in order to send/receive messages. By default, this container is bound on localhost only. In order to have an external agent connect up, you will need to adjust this in the `Mythic/.env` file to have `RABBITMQ_BIND_LOCALHOST_ONLY=false` and restart Mythic (`sudo ./mythic-cli restart`). You'll also need to set `MYTHIC_SERVER_BIND_LOCALHOST_ONLY=false`. 7. In the file where you define your payload type is where you define what it means to "build" your agent. 8. Run `python3.11 main.py` and now you should see this container pop up in the UI 9. If you already had the corresponding payload type registered in the Mythic interface, you should now see the red text turn green. You should see output similar to the following: ``` itsafeature@spooky my_container % python3 main.py INFO 2023-04-03 21:17:10,899 initialize 29 : [*] Using debug level: debug INFO 2023-04-03 21:17:10,899 start_services 267 : [+] Starting Services with version v1.0.0-0.0.7 and PyPi version 0.2.0-rc9 INFO 2023-04-03 21:17:10,899 start_services 270 : [*] Processing webhook service INFO 2023-04-03 21:17:10,899 syncWebhookData 261 : Successfully started webhook service INFO 2023-04-03 21:17:10,899 start_services 281 : [*] Processing agent: apfell INFO 2023-04-03 21:17:10,902 syncPayloadData 104 : [*] Processing command jsimport INFO 2023-04-03 21:17:10,902 syncPayloadData 104 : [*] Processing command chrome_tabs DEBUG 2023-04-03 21:17:10,915 SendRPCMessage 132 : Sending RPC message to pt_sync INFO 2023-04-03 21:17:10,915 GetConnection 84 : [*] Trying to connect to rabbitmq at: 127.0.0.1:5672 INFO 2023-04-03 21:17:10,999 GetConnection 98 : [+] Successfully connected to rabbitmq INFO 2023-04-03 21:17:11,038 ReceiveFromMythicDirectTopicExchange 306 : [*] started listening for messages on emit_webhook.new_callback INFO 2023-04-03 21:17:11,038 ReceiveFromMythicDirectTopicExchange 306 : [*] started listening for messages on emit_webhook.new_feedback INFO 2023-04-03 21:17:11,051 ReceiveFromMythicDirectTopicExchange 306 : [*] started listening for messages on emit_webhook.new_startup INFO 2023-04-03 21:17:13,240 syncPayloadData 123 : [+] Successfully synced apfell ``` If you mythic instance has a randomized password for `rabbitmq_password`, then you need to make sure that the password from `Mythic/.env` after you start Mythic for the first time is copied over to your vm. You can either add this to your `rabbitmq_config.json` file or set it as an environment variable (`MYTHIC_RABBITMQ_PASSWORD`). #### Caveats There are a few caveats to this process over using the normal process. You're now responsible for making sure that the right python version and dependencies are installed, and you're now responsible for making sure that the user context everything is running from has the proper permissions. One big caveat people tend to forget about is paths. Normal containers run on \*nix, but you might be doing this dev on Windows. So if you develop everything for windows paths hard-coded and then want to convert it to a normal Docker container later, that might come back to haunt you. ### Debugging Locally on server where Mythic is running Whether you're using a Docker container or not, you can load up the code in your `agent_code` folder in any IDE you want. When an agent is installed via `mythic-cli`, the entire agent folder (`agent_code` and `mythic`) is mapped into the Docker container. This means that any edits you make to the code is automatically reflected inside of the container without having to restart it (pretty handy). The only caveat here is if you make modifications to the python or golang definition files will require you to restart your container to load up the changes `sudo ./mythic-cli start [payload name]`. If you're making changes to those from a non-Docker instance, simply stop your `python3.11 main.py` and start it again. This effectively forces those files to be loaded up again and re-synced over to Mythic. Edits to go files will naturally need to be rebuilt though. #### Debugging Agent Code Locally If you're doing anything more than a typo fix, you're going to want to test the fixes/updates you've made to your code before you bother uploading it to a GitHub project, re-installing it, creating new agents, etc. Luckily, this can be super easy. Say you have a Visual Studio project set up in your `agent_code` directory and you want to just "run" the project, complete with breakpoints and configurations so you can test. The only problem is that your local build needs to be known by Mythic in some way so that the Mythic UI can look up information about your agent, your "installed" commands, your encryption keys, etc. To do this, you first need to generate a payload in the Mythic UI (or via Mythic's Scripting). You'll select any C2 configuration information you need, any commands you want baked in, etc. When you click to build, all of that configuration will get sent to your payload type's "build" function in `mythic/agent_functions/builder.py`. Even if you don't have your container running or it fails to build, no worries, Mythic will first save everything off into the database before trying to actually build the agent. In the Mythic UI, now go to your payloads page and look for the payload you just tried to build. Click to view the information about the payload and you'll see a summary of all the components you selected during the build process, along with some additional pieces of information (payload UUID and generated encryption keys). Take that payload UUID and the rest of the configuration and stamp it into your `agent_code` build. For some agents this is as easy as modifying the values in a Makefile, for some agents this can all be set in a `config` file of some sort, but however you want to specify this information is up to you. Once all of that is set, you're free to run your agent from within your IDE of choice and you should see a callback in Mythic. At this point, you can do whatever code mods you need, re-run your code, etc. #### Callbacks Aplenty Following from the previous section, if you just use the payload UUID and run your agent, you *should* end up with a new callback each time. That can be ideal in some scenarios, but sometimes you're doing quick fixes and want to just keep tasking the same callback over and over again. To do this, simply pull the callback UUID and encryption keys from the callback information on the active callbacks page and plug that into your agent. Again, based on your agent's configuration, that could be as easy as modifying a Makefile, updating a config file, or you might have to manually comment/uncomment some lines of code. Once you're reporting back with the callback UUID instead of the payload UUID and using the right encryption keys, you can keep re-running your build without creating new callbacks each time. # Wrapper Compatibility Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/payload-type-info/wrapper-compatibility Match wrapper payloads with v4 build metadata and conditional requirements Mythic 4.0 replaces wrapper payload type allowlists with metadata-driven compatibility. A normal payload builder reports what it produced; a wrapper declares the exact OS, architecture, and format combinations it accepts. ```mermaid theme={"system"} flowchart TD B["Payload builder"] -->|"build_metadata"| M["Mythic payload record"] W["Wrapper definition"] -->|"wrapper_payload_requirements"| R["Compatibility matcher"] M --> R P["Selected wrapper build parameters"] --> R R -->|"matching payloads only"| U["Wrapper payload picker"] ``` ## Report build metadata Mythic records the selected OS. The builder returns the resulting architecture and file format. ```python Python theme={"system"} from mythic_container.PayloadBuilder import ( BuildResponse, BuildStatus, PayloadBuildMetadata, PayloadBuildMetadataArchitecture, PayloadBuildMetadataFormat, ) return BuildResponse( status=BuildStatus.Success, payload=payload_bytes, build_metadata=PayloadBuildMetadata( architecture=PayloadBuildMetadataArchitecture.X64, format=PayloadBuildMetadataFormat.Shellcode, ), ) ``` ```go Go theme={"system"} return agentstructs.PayloadBuildResponse{ Success: true, Payload: &payloadBytes, BuildMetadata: &agentstructs.PayloadBuildMetadata{ Architecture: agentstructs.PAYLOAD_BUILD_ARCHITECTURE_X64, Format: agentstructs.PAYLOAD_BUILD_FORMAT_SHELLCODE, }, } ``` Common architecture values are `x86`, `x64`, `arm`, and `arm64`. Common formats are `exe`, `dll`, `shellcode`, `macho`, `elf`, `so`, and `dylib`. ## Declare wrapper requirements Multiple requirement rules are ORed. Every field in one `requires` object must match. `payload_type` is optional; omit it when any payload type with the required file metadata is acceptable. Optional `when` conditions bind a rule to the wrapper's own build parameter values. You can think of this like: *When* my build parameter's are configured like X, I *require* that the payload I'm trying to wrap have this configuration. ```python Python theme={"system"} from mythic_container.PayloadBuilder import ( PayloadType, WrapperPayloadRequirement, WrapperPayloadRequirementRequires, WrapperPayloadRequirementWhen, ) class ServiceWrapper(PayloadType): name = "service_wrapper" wrapper = True wrapper_payload_requirements = [ WrapperPayloadRequirement( when=[WrapperPayloadRequirementWhen("target_arch", "x64")], requires=WrapperPayloadRequirementRequires( os="Windows", architecture="x64", format="shellcode", payload_type="apollo", ), ), WrapperPayloadRequirement( when=[WrapperPayloadRequirementWhen("target_arch", "x86")], requires=WrapperPayloadRequirementRequires( os="Windows", architecture="x86", format="shellcode", ), ), ] ``` ```go Go theme={"system"} WrapperPayloadRequirements: []agentstructs.WrapperPayloadRequirement{ { When: agentstructs.WrapperPayloadRequirementWhenConditions{ {BuildParameterName: "target_arch", BuildParameterValue: "x64"}, }, Requires: agentstructs.WrapperPayloadRequirementRequires{ OS: agentstructs.SUPPORTED_OS_WINDOWS, Architecture: agentstructs.PAYLOAD_BUILD_ARCHITECTURE_X64, Format: agentstructs.PAYLOAD_BUILD_FORMAT_SHELLCODE, PayloadType: "apollo", }, }, }, ``` The resulting sync JSON is: ```json theme={"system"} { "wrapper_payload_requirements": [ { "when": {"target_arch": "x64"}, "requires": { "os": "Windows", "architecture": "x64", "format": "shellcode", "payload_type": "apollo" } } ] } ``` # Mythic 4.0 Source: https://docs.mythic-c2.net/version-4.0/home A collaborative, cross-platform command-and-control framework for red team operations. This documentation describes Mythic 4.0. If you are upgrading an existing deployment, start with [Mythic 3.4 -> 4.0 Updates](/version-4.0/updating/mythic-3.4-greater-than-4.0-updates). ## What is Mythic? Mythic is a multiplayer command-and-control platform for red team operations. Its plug-and-play architecture lets teams add payload types, communication profiles, translation services, eventing functions, and chat integrations without rebuilding the core server. The React interface and Go server use GraphQL, subscriptions, PostgreSQL, and RabbitMQ. Payload type, C2 profile, translation, web hook, eventing, log, and chat services run in separate containers and can live on the same host or on remote container hosts. A helpful view of the current state of the C2 Profiles and Agents for Mythic can be found here: ## What is new in 4.0? Mythic 4.0 adds scoped opaque API tokens, operation chat and AI chat containers, operator aliases, task references, richer build and C2 parameters, conditional wrapper compatibility, interactive eventing approvals, resumable byte-offset file transfers, an interactive text editor, richer credentials, managed C2-hosted files, and agent-initiated RPC. Migrate authentication, API actions, container libraries, wrappers, and integrations. Use standard operation chat or streamed AI-backed conversations. Resolve credentials and callback graph links safely during tasking. Adopt JSONString parameters, build metadata, and wrapper requirements. ## Why use Mythic? Mythic tracks the payload, build parameters, C2 configuration, loaded command versions, issuing operator, task references, artifacts, credentials, files, comments, and responses that form an operation. That context makes questions such as “which payload created this callback?”, “who issued this task?”, and “what changed on this host?” answerable from the same interface. Commands can map to [MITRE ATT\&CK](https://attack.mitre.org) techniques. Mythic carries those mappings into tasks and exposes them in matrix and [ATT\&CK Navigator](https://github.com/mitre-attack/attack-navigator) views. Operators can also search tasking, output, comments, files, credentials, and chat while working collaboratively. ## Resources and contributing * Browse the [Mythic source code](https://github.com/its-a-feature/Mythic). * Review the [community payload type and C2 profile overview](https://mythicmeta.github.io/overview/). * Join the `#mythic` channel in the [BloodHound Slack](https://slack.specterops.io). * Report documentation issues or contribute changes through the MythicMeta repositories. # Overview Source: https://docs.mythic-c2.net/version-4.0/installation ## Get the code Pull the code from the official GitHub repository: ``` $ git clone https://github.com/its-a-feature/Mythic --depth 1 --single-branch ``` This is made to work with docker and docker compose plugin, so they both need to be installed. If docker is not installed on your ubuntu machine, you can use the `./install_docker_ubuntu.sh` script to install it for you. If you're running on debian, use the `./install_docker_debian.sh` instead. You need to have Docker server version `20.10.22` or above (latest version is `23.0.1`) for Mythic and the docker containers to work properly. If you do `sudo apt upgrade` and `sudo apt install docker-compose-plugin` on a new version of Ubuntu or Debian, then you should be good. You can check your version with `sudo docker version`. Mythic is normally installed on Linux. If you want to install on macOS, you need to use orbstack (not Docker Desktop). This because macOS's Docker Desktop doesn't support host networking, which the C2 containers need to dynamically open up ports. It's recommended to run Mythic on a VM with at least 2CPU and 4GB Ram. ### Make the mythic-cli All configuration is done via the `mythic-cli` binary. However, to help with GitHub sizes, the `mythic-cli` binary is no longer distributed with the main Mythic repository. Instead, you will need to make the binary via `sudo make` **from the main Mythic folder**. This will create the build container for the mythic-cli, build the binary, and copy it into your main Mythic folder automatically. From there on, you can use the `mythic-cli` binary like normal. Make sure you use the `mythic-cli` binary **from the main Mythic folder**. If you're running Mythic on macOS, instead of running `sudo make` run `sudo make macos` and you'll get an architecture appropriate machO binary to use. ### Configure your installation Mythic configuration is all done via `Mythic/.env`, which means for your configuration you can either add/edit values there or add them to your environment. Mythic/.env doesn't exist by default. You can either let Mythic create it for you when you run `sudo ./mythic-cli start` for the first time or you can create it ahead of time with just the variables you want to configure. `mythic-cli status` is an easy way to pre-generate the `.env` file so you can modify it without it being used by any containers yet. If you need to run `mythic-cli` as root for Docker and you set your environment variables as a user, be sure to run `sudo -E ./mythic-cli` so that your environment variables are carried over into your sudo call. The following are the default values that Mythic will generate on first execution of `sudo ./mythic-cli start` unless overridden: ``` ALLOWED_IP_BLOCKS="0.0.0.0/0,::/0" COMPOSE_PROJECT_NAME="mythic" DEBUG_LEVEL="debug" DEFAULT_OPERATION_NAME="Operation Chimera" DEFAULT_OPERATION_WEBHOOK_CHANNEL= DEFAULT_OPERATION_WEBHOOK_URL= DOCUMENTATION_BIND_LOCALHOST_ONLY="true" DOCUMENTATION_HOST="mythic_documentation" DOCUMENTATION_PORT="8090" DOCUMENTATION_USE_BUILD_CONTEXT="false" DOCUMENTATION_USE_VOLUME="true" GLOBAL_DOCKER_LATEST="v0.0.3" GLOBAL_MANAGER="docker" GLOBAL_SERVER_NAME="mythic" HASURA_BIND_LOCALHOST_ONLY="true" HASURA_CPUS="2" HASURA_EXPERIMENTAL_FEATURES="streaming_subscriptions" HASURA_HOST="mythic_graphql" HASURA_MEM_LIMIT="2gb" HASURA_PORT="8080" HASURA_SECRET="random password" HASURA_USE_BUILD_CONTEXT="false" HASURA_USE_VOLUME="true" INSTALLED_SERVICE_CPUS="1" INSTALLED_SERVICE_MEM_LIMIT= JUPYTER_BIND_LOCALHOST_ONLY="true" JUPYTER_CPUS="2" JUPYTER_HOST="mythic_jupyter" JUPYTER_MEM_LIMIT= JUPYTER_PORT="8888" JUPYTER_TOKEN="mythic" JUPYTER_USE_BUILD_CONTEXT="false" JUPYTER_USE_VOLUME="true" JWT_SECRET="random password" MYTHIC_ADMIN_PASSWORD="random password" MYTHIC_ADMIN_USER="mythic_admin" MYTHIC_API_KEY= MYTHIC_DEBUG_AGENT_MESSAGE="false" MYTHIC_REACT_BIND_LOCALHOST_ONLY="true" MYTHIC_REACT_DEBUG="false" MYTHIC_REACT_HOST="mythic_react" MYTHIC_REACT_PORT="3000" MYTHIC_REACT_USE_BUILD_CONTEXT="false" MYTHIC_REACT_USE_VOLUME="true" MYTHIC_SERVER_BIND_LOCALHOST_ONLY="true" MYTHIC_SERVER_COMMAND= MYTHIC_SERVER_CPUS="2" MYTHIC_SERVER_DYNAMIC_PORTS="7000-7010,1080" MYTHIC_SERVER_DYNAMIC_PORTS_BIND_LOCALHOST_ONLY="false" MYTHIC_SERVER_GRPC_PORT="17444" MYTHIC_SERVER_HOST="mythic_server" MYTHIC_SERVER_MEM_LIMIT= MYTHIC_SERVER_PORT="17443" MYTHIC_SERVER_USE_BUILD_CONTEXT="false" MYTHIC_SERVER_USE_VOLUME="true" MYTHIC_SYNC_CPUS="2" MYTHIC_SYNC_MEM_LIMIT= NGINX_BIND_LOCALHOST_ONLY="false" NGINX_HOST="mythic_nginx" NGINX_PORT="7443" NGINX_USE_BUILD_CONTEXT="false" NGINX_USE_IPV4="true" NGINX_USE_IPV6="false" NGINX_USE_SSL="true" NGINX_USE_VOLUME="true" POSTGRES_BIND_LOCALHOST_ONLY="false" POSTGRES_CPUS="2" POSTGRES_DB="mythic_db" POSTGRES_DEBUG="false" POSTGRES_HOST="mythic_postgres" POSTGRES_MEM_LIMIT= POSTGRES_PASSWORD="random password" POSTGRES_PORT="5432" POSTGRES_USE_BUILD_CONTEXT="false" POSTGRES_USE_VOLUME="true" POSTGRES_USER="mythic_user" RABBITMQ_BIND_LOCALHOST_ONLY="true" RABBITMQ_CPUS="2" RABBITMQ_HOST="mythic_rabbitmq" RABBITMQ_MEM_LIMIT= RABBITMQ_PASSWORD="random password" RABBITMQ_PORT="5672" RABBITMQ_USE_BUILD_CONTEXT="false" RABBITMQ_USE_VOLUME="true" RABBITMQ_USER="mythic_user" RABBITMQ_VHOST="mythic_vhost" REBUILD_ON_START="true" WEBHOOK_DEFAULT_ALERT_CHANNEL= WEBHOOK_DEFAULT_CALLBACK_CHANNEL= WEBHOOK_DEFAULT_CUSTOM_CHANNEL= WEBHOOK_DEFAULT_FEEDBACK_CHANNEL= WEBHOOK_DEFAULT_STARTUP_CHANNEL= WEBHOOK_DEFAULT_URL= ``` A few important notes here: * `MYTHIC_SERVER_PORT` will be the port opened on the server where you're running Mythic. The `NGINX_PORT` is the one that's opened by Nginx and acts as a reverse proxy to all other services. The `NGINX_PORT` is the one you'll connect to for your web user interface and should be the only port you need to expose externally (unless you prefer to SSH port forward your web UI port). * The `allowed_ip_blocks` allow you to restrict access to everything within Mythic. This should be set as a series of netblocks with **NO** host bits set - i.e. `127.0.0.0/16,192.168.10.0/24,10.0.0.0/8` * `*_BIND_LOCALHOST_ONLY` - these settings determine if the associated container binds the port to `127.0.0.1:port` or `0.0.0.0:port`. These are all set to true (except for the nginx container) by default so that you're not exposing these services externally. If you want to have a services (agent, c2 profile, etc) on a host *other* than where the Mythic server is running, then you need to make sure that RABBITMQ\_BIND\_LOCALHOST\_ONLY and MYTHIC\_SERVER\_BIND\_LOCALHOST\_ONLY are both set to `false` so that your remote services can access Mythic. If you change these, you will need to run `sudo ./mythic-cli start` to make sure these changes are leveraged by Docker. The above configuration does *NOT* affect the port or SSL information related to your agents or callback information. It's strictly for your operator web UI. When the `mythic_server` container starts for the first time, it goes through an initialization step where it uses the password and username from `Mythic/.env` to create the `mythic_admin_user` user. Once the database exists, the `mythic_server` container no longer uses that value. ### mythic-cli The `mythic-cli` binary is used to start/stop/configure/install components of Mythic. You can see the help menu at any time with `mythic-cli -h`, `mythic-cli --help` or `mythic-cli help`. ``` Mythic CLI is a command line interface for managing the Mythic application and associated containers and services. Commands are grouped by their use and all support '-h' for help. For a list of available services to install, check out: https://mythicmeta.github.io/overview/ Usage: mythic-cli [command] Available Commands: add Add local service folder to docker compose backup Backup various volumes/data to a custom location on disk build Build/rebuild a specific container build_ui Build/rebuild the React UI completion Generate the autocompletion script for the specified shell config Display or adjust the configuration database Interact with the database health Check health status of containers help Help about any command install Install services via git or local folders load Load tar versions of Mythic images from ./saved_images/mythic_save.tar logs Get docker logs from a running service mythic_sync Install/Uninstall mythic_sync rabbitmq Interact with the rabbitmq service remove Remove local service folder from docker compose remove_container Remove running or exited containers restart Start all of Mythic restore Restore various volumes/data from a custom location on disk save Save tar versions of the specified container's images services List out installed services start Start Mythic containers status Get current Mythic container status stop Stop all of Mythic test Test mythic service connections uninstall uninstall services locally and remove them from disk update Check for Mythic updates version Print information about the mythic-cli and Mythic versions volume Interact with the mythic volumes Flags: -h, --help help for mythic-cli Use "mythic-cli [command] --help" for more information about a command. ``` ### Installing Agents / C2 Profiles By default, Mythic does not come with any Payload Types (agents) or C2 Profiles. This is for a variety of reasons, but one of the big ones being time/space requirements - all Payload Types and C2 Profiles have their own Docker containers, and as such, collectively they could eat up a lot of space on disk. Additionally, having them split out into separate repositories makes it much easier to keep them updated. Available Mythic Agents can be found on GitHub at [https://github.com/MythicAgents](https://github.com/MythicAgents) Available Mythic C2 Profiles can be found on GitHub at [https://github.com/MythicC2Profiles](https://github.com/MythicC2Profiles) To install a Payload Type or C2 Profile, use the `mythic-cli` binary with: ```bash theme={"system"} sudo ./mythic-cli install github ``` If you have an agent already installed, but want to update it, you can do the same command again. If you supply a `-f` at the end, then Mythic will automatically overwrite the current version that's installed, otherwise you'll be prompted for each piece. You won't be able to create any payloads within Mythic until you have at least one Agent and a matching C2 Profile installed ### Logging If you're wanting to enable SIEM-based logging, install the `basic_logger` via the mythic cli `sudo ./mythic-cli install github https://github.com/MythicC2Profiles/basic_logger`. This profile listens to the `emit_log` RabbitMQ queue and allows you to configure how you want to save/modify the logs. By default they just go to stdout, but you can configure it to write out to files or even submit the events to your own SIEM. ``` file_upload (file staged on mythic as part of tasking with the intent to get sent to the agent) file_manual_upload (file staged on mythic as part of a user manually hosting it) file_screenshot (file is a screenshot from the agent) file_download (file is downloaded from agent to mythic) artifact_new (new artifact created - think IOC) eventlog_new (new eventlog message) eventlog_modified (eventlog was modified, like resolving an issue or changing their message) payload_new (new payload created) task_mitre_attack (a task was associated with a new mitre attack technique) task_new (a new task was created) task_completed (a task completed) task_comment (somebody added/removed/edited a comment on a task) credential_new (a new credential was added to the store) credential_modified (a credential was modified) response_new (a new response for the user to see) keylog_new (a new keylog entry) callback_new (new callback registered) ``` Simply modify the code [here](https://github.com/MythicC2Profiles/basic_logger/blob/main/C2_Profiles/basic_logger/my_logger/initialize.go#L16-L39) to change from printing to stdout to whatever it is you want to do, then use `sudo ./mythic-cli build basic_logger` to rebuild with your updated code. ### Start Mythic If you came here right from the previous section, your Mythic instance should already be up and running. Check out the next section to confirm that's the case. If at any time you wish to stop Mythic, simply run `sudo ./mythic-cli stop` and if you want to start it again run `sudo ./mythic-cli start`. If Mythic is currently running and you need to make a change, you can run `sudo ./mythic-cli restart` again without any issue, that command will automatically stop things and then restart them. The default username is `mythic_admin`, but that user's password is randomly generated when Mythic is started for the first time. You can find this random value in the `Mythic/.env` file. Once Mythic has started at least once, this value is no longer needed, so you can edit or remove this entry from the `Mythic/.env` file. Mythic starts with NO C2 Profiles or Agents pre-installed. Due to size issues and the growing number of agents, this isn't feasible. Instead. use the `./mythic-cli install github [branch] [-f]` command to install an agent from a GitHub (or GitLab) repository. ### Troubleshooting installation and connection If something seems off, here's a few places to check: * Run `sudo ./mythic-cli status` to give a status update on all of the docker containers. They should all be up and running. If one is exited or has only been up for less than 30 seconds, that container might be your issue. All of the Mythic services will also report back a health check which can be useful to determine if a certain container is having issues. The status command gives a lot of information about what services are running, on which ports, and if they're externally accessible or not. ``` MYTHIC SERVICE WEB ADDRESS BOUND LOCALLY Nginx (Mythic Web UI) https://127.0.0.1:7443 false Mythic Backend Server http://127.0.0.1:17443 false Hasura GraphQL Console http://127.0.0.1:8080 true Jupyter Console http://127.0.0.1:8888 true Internal Documentation http://127.0.0.1:8090 true ADDITIONAL SERVICES IP PORT BOUND LOCALLY Postgres Database 127.0.0.1 5432 false React Server 192.168.53.152 3000 true RabbitMQ 127.0.0.1 5672 false Mythic Main Services CONTAINER NAME STATE STATUS PORTS mythic_documentation running Up 38 seconds (healthy) 8090/tcp -> 127.0.0.1:8090 mythic_graphql running Up 36 seconds (healthy) 8080/tcp -> 127.0.0.1:8080 mythic_jupyter running Up 41 seconds (healthy) 8888/tcp -> 127.0.0.1:8888 mythic_nginx running Up 35 seconds (healthy) 7443/tcp -> :::7443, 7443 mythic_postgres running Up 39 seconds (healthy) 5432/tcp -> :::5432, 5432 mythic_rabbitmq running Up 40 seconds (health: starting) 5672/tcp -> :::5672, 5672 mythic_server running Up 37 seconds (health: starting) 7000/tcp -> :::7000, 7001/tcp -> :::7001, 7002/tcp -> :::7002, 7003/tcp -> :::7003, 7004/tcp -> :::7004, 7005/tcp -> :::7005, 7006/tcp -> :::7006, 7007/tcp -> :::7007, 7008/tcp -> :::7008, 7009/tcp -> :::7009, 7010/tcp -> :::7010, 17443/tcp -> :::17443, 17444/tcp -> :::17444, 7000, 7001, 7002, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 17443, 17444 Installed Services CONTAINER NAME STATE STATUS PORTS no_translator running Up 43 seconds service_wrapper running Up 42 seconds ``` * To check the logs of any container, run `sudo ./mythic-cli logs [container_name]`. For example, to see the output of mythic\_server, run `sudo ./mythic-cli logs mythic_server`. This will help track down if the last thing that happened was an error of some kind. * If all of that looks ok, but something still seems off, it's time to check the browser. * First open up the developer tools for your browser and see if there are any errors that might indicate what's wrong. If there's no error though, check the network tab to see if there are any 404 errors. * If that's not the case, make sure you've selected a current operation (more on this in the Quick Usage section). Mythic uses websockets that pull information about your current operation to provide data. If you're not currently in an active operation (indicated at the top of your screen in big letters), then Mythic cannot provide you any data. #### mythic\_nginx restarting If you run into an issue where `mythic_nginx` is failing to start, you can look at its logs with `sudo ./mythic-cli logs mythic_nginx`. If you see `Address family not supported by protocol`, then it likely means that the nginx container is trying to use IPv4 and IPv6, but your host doesn't support one of them. To fix this, you can edit the .env file to adjust the following as necessary: ``` NGINX_USE_IPV4="true" NGINX_USE_IPV6="false" ``` Then restart the container with `sudo ./mythic-cli build mythic_nginx` and it should come up. ## Mythic Pre-built containers Starting with Mythic 3.2.16, Mythic pre-builds its main service containers and hosts them on GitHub. You might see `ghcr.io/itsafeature` in the FROM line in your Dockerfiles instead of the `itsafeaturemythic/` line which is hosted on DockerHub. When the Mythic repo gets a new `tag`, these images are pre-built, mythic-cli is updated, and the associated `push` on GitHub is updated with the new tag version. When you use the new `mythic-cli` to start Mythic, the `.env` variable `GLOBAL_DOCKER_LATEST` is used to determine which version of the Docker images to use. This variable is written out and saved as part of `mythic-cli` itself. so make sure when you do a `git pull` that you always run `sudo make` to get the latest `mythic-cli` as well. As part of this, there are four new variables for each container: * `*_USE_BUILD_CONTEXT` - This variable changes the `docker-compose` file to either set a `build-context` to read the local Dockerfile when building or to *not* use the local build context and just set the image to use to be the one hosted on GitHub. In most cases, you're fine to leave this as `false` and just use the image hosted on GitHub. If you wanted to use another image version or if you wanted add stuff to the image that gets generated for a container, you can set this to `true` and modify the Dockerfile associated with the service. * `*_USE_VOLUME` - This variable identifies if the local file system is mounted into the image at run-time or if a custom volume is created and mounted instead. When this is set to `true`, then a custom volume is created and mounted into the container at run time so that your local filesystem isn't used. When this is `false`, then your local filesystem is mounted like normal. One reason to mount the local file system instead of using a volume is if you wanted to make changes to something on disk and have it reflected in the container. Similarly, you can set this to `false` so that your database and downloaded files are all contained within the `Mythic` folder. Setting this to `true` will mean that volumes are used, so your saved files and database are in Docker's volume directory and not locally within the Mythic folder. It's just something to consider when it comes time to save things off or if you wanted to pull the files from disk. * `*_INSTALL_LOCATION` - This variable gives either the folder path or the "url;branch" that was used to install the agent in the first place. This is used by `mythic-cli` commands like `./mythic-cli update -s [agent name]` to determine if the agent has any updates available. * `*_REMOTE_IMAGE` - This variable tracks the remote image that's pre-built for the agent (if one exists). This is used in conjunction with the `*_INSTALL_LOCATION` to see if there are any updates for the agent. ## Agent Pre-built containers Mythic is pre-building its containers so that it's faster and easier to get going while still keeping all of the flexibility of Docker images. This cuts down on the install/build time of the containers and reduces the general size of the images due to multi-stage Docker builds. Agents on GitHub can also do this for free. It's pretty simple (all things considered) and provides a lot of flexibility to how you build your containers. You don't need to configure any special GitHub secrets - you just need to create the necessary yaml file as part of a certain directory of your repository so that things are kicked off on push and on tag. One of these changes is an automatic update to your `config.json` so that Mythic can also track the version associated with your agent. Specifically, you need to create the `.github/workflows/[name].yml` file so that GitHub will be able to handle your actions. An example from the `apollo` payload is shown below: ```yaml theme={"system"} # Pulled from Thanatos (https://github.com/MythicAgents/thanatos/blob/rewrite/.github/workflows/image.yml) - MEhrn00 # Name for the Github actions workflow name: Build and push container images on: # Only run workflow when there is a new release published in Github #release: # types: [published] push: branches: - 'master' tags: - "v*.*.*" # Variables holding configuration settings env: # Container registry the built container image will be pushed to REGISTRY: ghcr.io # Set the container image name to the Github repository name. (MythicAgents/apollo) AGENT_IMAGE_NAME: ${{ github.repository }} # Description label for the package in Github IMAGE_DESCRIPTION: ${{ github.repository }} container for use with Mythic # Source URL for the package in Github. This links the Github repository packages list # to this container image IMAGE_SOURCE: ${{ github.server_url }}/${{ github.repository }} # License for the container image IMAGE_LICENSE: BSD-3-Clause # Set the container image version to the Github release tag VERSION: ${{ github.ref_name }} #VERSION: ${{ github.event.head_commit.message }} RELEASE_BRANCH: master jobs: # Builds the base container image and pushes it to the container registry agent_build: runs-on: ubuntu-latest permissions: contents: write packages: write steps: - name: Checkout the repository uses: actions/checkout@v4 # ref: https://github.com/marketplace/actions/checkout - name: Log in to the container registry uses: docker/login-action@v3 # ref: https://github.com/marketplace/actions/docker-login with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: platforms: 'arm64,arm' - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v2 # the following are unique to this job - name: Lowercase the server container image name run: echo "AGENT_IMAGE_NAME=${AGENT_IMAGE_NAME,,}" >> ${GITHUB_ENV} - name: Build and push the server container image uses: docker/build-push-action@v5 # ref: https://github.com/marketplace/actions/build-and-push-docker-images with: context: Payload_Type/apollo file: Payload_Type/apollo/Dockerfile tags: | ${{ env.REGISTRY }}/${{ env.AGENT_IMAGE_NAME }}:${{ env.VERSION }} ${{ env.REGISTRY }}/${{ env.AGENT_IMAGE_NAME }}:latest push: ${{ github.ref_type == 'tag' }} # These container metadata labels allow configuring the package in Github # packages. The source will link the package to this Github repository labels: | org.opencontainers.image.source=${{ env.IMAGE_SOURCE }} org.opencontainers.image.description=${{ env.IMAGE_DESCRIPTION }} org.opencontainers.image.licenses=${{ env.IMAGE_LICENSE }} platforms: linux/amd64,linux/arm64 update_files: runs-on: ubuntu-latest needs: - agent_build permissions: contents: write packages: write steps: # Pull in the repository code - name: Checkout the repository uses: actions/checkout@v4 # ref: https://github.com/marketplace/actions/checkout # update names to lowercase - name: Lowercase the container image name run: echo "AGENT_IMAGE_NAME=${AGENT_IMAGE_NAME,,}" >> ${GITHUB_ENV} - name: Update package.json version uses: jossef/action-set-json-field@v2.1 with: file: config.json field: remote_images.apollo value: ${{env.REGISTRY}}/${{env.AGENT_IMAGE_NAME}}:${{env.VERSION}} # Push the changes to the Dockerfile - name: Push the updated base Dockerfile image reference changes if: ${{ github.ref_type == 'tag' }} uses: EndBug/add-and-commit@v9 # ref: https://github.com/marketplace/actions/add-commit with: # Only add the Dockerfile changes. Nothing else should have been modified add: "['config.json']" # Use the Github actions bot for the commit author default_author: github_actions committer_email: github-actions[bot]@users.noreply.github.com # Set the commit message message: "Bump Dockerfile tag to match release '${{ env.VERSION }}'" # Overwrite the current git tag with the new changes tag: '${{ env.VERSION }} --force' # Push the new changes with the tag overwriting the current one tag_push: '--force' # Push the commits to the branch marked as the release branch push: origin HEAD:${{ env.RELEASE_BRANCH }} --set-upstream # Have the workflow fail in case there are pathspec issues pathspec_error_handling: exitImmediately ``` 99% of this example should work for all agents and c2 profiles. Things to change: * `RELEASE_BRANCH` - This might need to change depending on if your branch name is `master` or `main`. * Updating the `remote_images.apollo` (line 112) to `remote_images.[your name]` so that your `config.json` is updated appropriately. If you have multiple installs (ex: a payload in the payload\_type folder and a c2 profile in the c2\_profiles folder), then you should include this action multiple times to add each entry to your `remote_images` dictionary. # A note about containers Source: https://docs.mythic-c2.net/version-4.0/installation/a-note-about-containers ## Mythic and containers Mythic uses docker containers to logically separate different components and functions. There are two main categories: 1. Mythic's main core. This consists of docker containers stood up with docker-compose: 1. mythic\_server - An Go gin webserver instance 2. mythic\_postgres - An instance of a postgresql database 3. mythic\_rabbitmq - An instance of a rabbitmq container for message passing between containers 4. mythic\_nginx - A instance of a reverse Nginx proxy 5. mythic\_graphql - An instance of a Hasura GraphQL server 6. mythic\_jupyter - An instance of a Jupyter notebook 7. mythic\_documentation - An instance of a Hugo webserver for localized documentation 2. Installed Services 1. Any folder in `Mythic/InstalledServices` will be treated like a docker container (payload types, c2 profiles, webhooks, loggers, translation containers, etc) To stop a specific container, run `sudo ./mythic-cli stop {container name}` . If you want to reset all of the data in the database, use `sudo ./mythic-cli database reset`. If you want to start/restart any specific payload type container, you can do `sudo ./mythic-cli start {payload_type_name}` and just that container will start/restart. If you want to start multiple, just do spaces between them: `sudo ./mythic-cli start {container 1} {container 2}`. Mythic's C2 containers share the networking with the host it's on. This allows C2 Containers to not worry about exposing specific ports ahead of time for each container since they can be dynamically set by users. However, this does mean that Mythic needs to run as `root` if any ports under 1024 need to be used. ### Docker-compose All of Mythic's containers share a single docker-compose file. When you install an agent or C2 Profile this docker-compose file will automatically be updated. However, you can always add/remove from this file via `mythic-cli` and list out what's registered in the docker-compose file vs what you have available on your system: ``` ./mythic-cli add apfell [+] Successfully updated docker-compose.yml /mythic-cli remove http [+] Successfully updated docker-compose.yml ``` This makes it easy to track what's available to you and what you're currently using. ## Architecture Operators connect via a browser to the main Mythic server, a Go `gin` web server. This main Mythic server connects to a PostgreSQL database where information about the operations lives. Each of these are in their own docker containers. When Mythic needs to talk to any payload type container or c2 profile container, it does so via RabbitMQ, which is in its own docker container as well. When an agent calls back, it connects through these c2 profile containers which have the job of transforming whatever the c2 profile specific language/style is back into the normal RESTful API calls that the Mythic server needs. # Connecting Source: https://docs.mythic-c2.net/version-4.0/installation/connecting By default, the server will bind to `0.0.0.0` on port `7443` with a self-signed certificate(unless otherwise configured). This IP is an alias meaning that it will be listening on all IPv4 addresses on the machine. Browse to either `https://127.0.0.1:7443` if you’re on the same machine that’s running the server, or you can browse to any of the IPv4 addresses on the machine that’s running the server. * Browse to the server with any modern web browser. You will be automatically redirected to the `/login` url. This url is protected by `allowed_ip_blocks` . * The default username is `mythic_admin` and the default password is randomized. The password is stored in `Mythic/.env` after first launch, but you can also view it with `sudo ./mythic-cli config get MYTHIC_ADMIN_PASSWORD`. You can opt to set this before you initially start if you want (or you can change this later through the UI) by setting that environment variable before staring Mythic for the first time. Mythic returns short-lived access and refresh tokens after login. The v4 UI reads the current access token and sends it as `Authorization: Bearer ` on protected HTTP and GraphQL requests. Protected endpoints no longer accept the `mythic` cookie as authentication. Custom clients should either exchange a username and password at `/auth` and refresh the resulting access token, or use a [scoped API token](/version-4.0/operational-pieces/api-tokens). Both token types use the same Bearer header. Do not use the removed `apitoken` header. ## Your connection is not private Warning If you're using Chrome and a self-signed certificate that's default generated by Mythic, you will probably see a warning like this when you try to connect: This is fine and expected since we're not using a LetsEncrypt or a proper domain certificate. To get around this, simply click somewhere within the window and type `thisisunsafe`. Your browser will now **Temporarily** accept the cert and allow you through. At some point in the future, your browser will decide to remind you that you're using a self-signed certificate. Mythic cannot actually read this error message due to Chrome's security policies. When this happens, simply refresh your page. You'll be brought back to the same big warning page as the image above and you can type `thisisunsafe` again to continue your operations. # Offline Installation Source: https://docs.mythic-c2.net/version-4.0/installation/offline-installation How to install Mythic and agents in an offline environment This guide will assume you can install Mythic on a box that has Internet access and then migrate to your offline testing/development environment. ### Setup 1. Install Mythic following the normal installation 2. With Mythic running, install any other agents or profiles you might need/want. ``` sudo ./mythic-cli install github https://github.com/MythicAgents/Apollo ``` 3\. Export your docker containers. Make sure you also save the tags. ``` docker save $(docker images -q) -o mythic_images.tar docker images | sed '1d' | awk '{print $1 " " $2 " " $3}' > mythic_tags ``` 4\. Download donut from pypi. (this is apollo specific, so there might be others depending on your agent) ``` mkdir Payload_Types/apollo/depends pip3 download donut -d Payload_Types/apollo/depends ``` Download Apollo dependencies (apollo specifically installs these dynamically within the Docker container at build-time, so pre-fetch these) ``` wget https://www.nuget.org/api/v2/package/Fody/2.0.0 -O Payload_Types/apollo/depends/fody.2.0.0.nupkg wget https://www.nuget.org/api/v2/package/Costura.Fody/1.6.2 -O Payload_Types/apollo/depends/costura.fody.1.6.2.nupkg ``` 5\. Tar Mythic directoy. ``` tar cfz mythic.tar.gz /Mythic ``` 6\. Push `mythic_images.tar`, `mythic_tags`, and `mythic.tar.gz` to your offline box. 7\. Import docker images and restore tags. ``` docker load -i mythic_images.tar while read REPOSITORY TAG IMAGE_ID; do echo "== Tagging $REPOSITORY $TAG $IMAGE_ID =="; docker tag "$IMAGE_ID" "$REPOSITORY:$TAG"; done < mythic_tags ``` 8\. Extract Mythic directory. ``` tar xfz mythic.tar.gz cd mythic ``` ``` from itsafeaturemythic/csharp_payload:0.1.1 COPY ["depends/donut-0.2.2.tar.gz", "donut-0.2.2.tar.gz"] COPY ["depends/costura.fody.1.6.2.nupkg", "costura.fody.1.6.2.nupkg"] COPY ["depends/fody.2.0.0.nupkg", "fody.2.0.0.nupkg"] RUN /usr/local/bin/python3.8 -m pip install /donut-0.2.2.tar.gz RUN mkdir /mythic_nuget RUN nuget sources add -name mythic_nuget -source /mythic_nuget RUN nuget sources disable -name nuget.org RUN nuget add /fody.2.0.0.nupkg -source /mythic_nuget RUN nuget add /costura.fody.1.6.2.nupkg -source /mythic_nuget ``` 10\. Start Mythic ``` sudo ./mythic-cli start ``` Normally, Mythic containers will try to re-build every time you bring them down and back up. This might not be great for an offline environment. The configuration variable, `REBUILD_ON_START`, can be set to `false` to tell Mythic that the containers should specifically NOT be rebuilt when restarted. # Updating Mythic Source: https://docs.mythic-c2.net/version-4.0/installation/updating-mythic ## How to update Mythic to a new version Upgrading from Mythic 3.4 to 4.0 changes authentication, API tokens, public actions, container RPC, eventing inputs, and wrapper definitions. Follow the dedicated [Mythic 3.4 -> 4.0 Updates](/version-4.0/updating/mythic-3.4-greater-than-4.0-updates) checklist before using the general steps below. In the Mythic UI, you can click the hamburger icon (three horizontal lines) in the top left to see the current Server version and UI version. There are three scenarios to updating Mythic: updates to the patch version (1.4.1 to 1.4.2), updates to the minor (1.4.1 to 1.5), or major version (1.4 to 2.0). In all updates, after a `git pull` you should run `make` to get the latest `mythic-cli` binary. ### Updating patches This is when you're on version 1.2 for example and want to pull in new updates (but not a new minor version like 1.3 or 1.4). In this case, the database schema should not have changed. 1. Pull in the latest code for your version (if you're still on the current version, this should be as easy as a `git pull`) 2. Make a new mythic-cli binary with `sudo make` 3. Restart Mythic to pull in the latest code changes into the docker containers with `sudo ./mythic-cli start` ### Updating minor or major versions This is when you're on version 1.2 for example and want to upgrade to version 1.3 or 2.1 for example. In this case, the database schema has changed. Starting with Mythic 3.1, we now have database migrations within PostgreSQL. You should be fine to git pull and rebuild everything. It's important that you rebuild so that server changes are pulled in for the various services that updated. This means that if you're on Mythic 3.0.0-rc\* and want to upgrade to Mythic 3.1.0, you'll automatically get database migrations to help with this. **Note:** I always highly recommend backing everything up if you plan to update a production system. Just in case something happens, you'll be able to revert. You will have some down time while this happens (the containers need to rebuild and start back up), so make sure whatever you're doing can handle a few seconds to a few minutes of down time. 1. Pull in the latest code for your version (if you're still on the current version, this should be as easy as a `git pull`) 2. Make a new mythic-cli binary with `sudo make` 3. Restart Mythic to pull in the latest code changes into the docker containers with `sudo ./mythic-cli start`. If you have in your `.env` to not rebuild on start, then you will need to change that first. Since Mythic now has all of the C2 Profiles and Payload Types split out into different GitHub Organizations ([https://github.com/MythicAgents](https://github.com/MythicAgents) and [https://github.com/MythicC2Profiles](https://github.com/MythicC2Profiles)), you might need to update those projects as well. ## Updating Agent or C2 Profile services Agents and C2 Profiles are hosted in their own repositories, and as such, might have a different update schedule than the main Mythic repo itself. So, you might run into a scenario where you update Mythic, but now the current Agent/C2Profiles services are no longer supported. You'll know if they're no longer supported because when the services check in, they'll report their current version number. Mythic has a range of supported version numbers for Agents, C2 Profiles, Translation services, and even scripting. If something checks in that isn't in the supported range, you'll get a warning notification in the UI about it. To update these (assuming that the owner/maintainer of that Agent/C2 profile has already done the updates), simply stop the services (`sudo ./mythic-cli stop agentname` or `sudo ./mythic-cli stop profileName`) and run the install command again. The install command should automatically determine that a previous version exists, remove it, and copy in the new components. Then you just need to either start those individual services, or restart mythic overall. Assuming the Agent/C2 maintainer is pre-building their images, you can do a short-hand update via `sudo ./mythic-cli update -s [agent name]`. This will fetch the tracked repo (if one exists) and check the `config.json` for the latest version. Specifying `-i` will automatically update if there's an update available. Specifying `sudo ./mythic-cli update -a -i` will go through to check all of your installed services for updates and install them if updates are available. ## Deleting Database If you want to wipe the database and upgrade, the following steps will help: Deleting the database means you will lose all history of your tasking, operators, callbacks, payloads, events, etc. This is **not** needed for normal updates. 1. Reset the database with `sudo ./mythic-cli database reset` 2. Make sure Mythic is stopped, `sudo ./mythic-cli stop` 3. Purge all of your containers, `sudo docker system prune -a` 4. Pull in the version you want to upgrade to (if you're wanting to upgrade to the latest, it's as easy as `git pull`) 5. Make a new mythic-cli binary with `sudo make`. 6. Delete your `Mythic/.env` file - this file contains all of the per-install generated environment variables. There might be new environment variables leveraged by the updated Mythic, so be sure to delete this file and a new one will be automatically generated for you. 7. Restart Mythic to pull in the latest code changes into the docker containers with `sudo ./mythic-cli start` # Installed Service Documentation Source: https://docs.mythic-c2.net/version-4.0/internal-documentation Serve documentation supplied by installed payload types and C2 profiles. Mythic's internal documentation service is separate from this versioned framework. It renders the Markdown shipped by installed payload types and C2 profiles so operators can read service-specific commands, parameters, and setup instructions without leaving the deployment. ## Access it Open **Help → Documentation** or browse to `/docs/` through Mythic's Nginx proxy. `DOCUMENTATION_PORT` in `Mythic/.env` controls the service's internal HTTP port; operators normally connect through Mythic's HTTPS port instead of exposing it directly. The service reads Markdown under `Mythic/documentation-docker/` and rebuilds during local development. Mythic 4.0's documentation UI adds light and dark themes, collapsible navigation, preserved sidebar state, and in-page navigation. Payload and C2 authors own the content displayed here. Update that content in the service repository or its installed documentation files; update the Mintlify repository for changes to the Mythic framework manual. # Overview Source: https://docs.mythic-c2.net/version-4.0/message-flow This page describes how messages flow within Mythic The following subpages have Mermaid sequence diagrams explaining how messages flow amongst the various microservices for Mythic when doing things like creating payloads, issuing tasks, and transferring files. # Agent Sends Message Source: https://docs.mythic-c2.net/version-4.0/message-flow/agent-sends-message ```mermaid theme={"system"} sequenceDiagram participant T as Translation Container participant M as Mythic participant C as C2 Profile Container participant A as Agent A ->>+ C: Agent Sends Message C ->>+ M: C2 Forwards to Mythic Note over M: Start Processing Message M ->> M: Base64 Decode and fetch UUID M ->>+ T: Send to Translator T ->> T: Decrypt Message T ->> T: Translate To Mythic JSON T -->>- M: Return JSON Message M ->> M: Process Message M ->> M: Process Delegates Note over M: Stop Processing Message M ->> M: Create Response M ->>+ T: Send to Translator T ->> T: Translate to Custom Format T ->> T: Encrypt Message T -->>- M: Return Final Blob M -->>- C: Return Final Blob C -->>- A: Return Final Message ``` Here we can see an agent sends a message to Mythic. The C2 Profile container is simply a fancy redirector that know show to pull the message off the wire, it doesn't do anything else than that. From there, Mythic starts processing the message. It pulls out the UUID so it can determine which agent/callback we're talking about. This is where a decision point happens: * If the Payload Type associated with the payload/callback for the UUID of the message has a translation container associated with it, then Mythic will send the message there. It's here that the rest of the message is converted from the agent's special sauce C2 format into the standard JSON that Mythic expects. Additionally, if the Payload Type handles encryption for itself, then this is where that happens. * If there is not translation container associated with the payload/callback for the UUID in the message, then Mythic moves on to the next step and starts processing the message. Mythic then processes the message according to the "action" listed. Mythic then potentially goes back to the translation container to convert the response message back to the agent's custom C2 spec before finally returning everything back through the C2 Profile docker container. # Building Payloads Source: https://docs.mythic-c2.net/version-4.0/message-flow/building-payloads ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant C as C2 Profile participant P as Payload Container participant B as Compiler O -->> O: Selects Payload Options O ->>+ M: Sends Build Request M -->> M: Looks up all Components M ->> M: Registers Payload M -->>- O: Indicate that build has started loop C2 Profiles M -->+ C: Invoke OPSEC Check C ->>- M: Return Result end M ->>+ P: Forward Build Parameters P ->> P: Parse Parameters loop build P -->> P: Stamp in Parameters P -->> B: Build Payload B -->> P: Return Payload or Error end Note over P: Finish Building P -->>- M: Send finished Payload M ->> M: Updates Payload build status M -->> O: Update with new Build Status ``` Here we can see that the operator selects the different payload options they desire in the web user interface and clicks submit. That information goes to Mythic which looks up all the database objects corresponding to the user's selection. Mythic then registers a payload in a `building` state. Mythic sends all this information to the corresponding Payload Type container to build an agent to meet the desired specifications. The corresponding `build` command parses these parameters, stamps in any required user parameters (such as callback host, port, jitter, etc) and uses any user supplied build parameters (such as exe/dll/raw) to build the agent. In the build process, there's a lot of room for customizing. Since it's all async through rabbitMQ, you are free to stamp code together, spin off subprocesses (like mono or go) to build your agent, or even make web requests to CI/CD pipelines to build the agent for you. Eventually, this process either returns an agent or some sort of error. That final result gets send back to Mythic via rabbitMQ which then updates the database and user interface to allow an operator to download their payload. ### Translation Container Version How does this process work if there's a translation container involved though? ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant C as C2 Profile participant T as Translation Container participant P as Payload Container participant B as Compiler O -->> O: Selects Payload Options O ->>+ M: Sends Build Request M -->> M: Looks up all Components M ->> M: Registers Payload M -->>- O: Indicate that build has started loop C2 Profiles M -->+ C: Invoke OPSEC Check C ->>- M: Return Result M -->+ T: Generate C2 Encryption Keys (if any) T ->>- M: Return Keys or None end M -->+ T: Generate build parameter Encryption Keys (if any) T ->>- M: Return Keys or None M ->>+ P: Forward Build Parameters P ->> P: Parse Parameters loop build P -->> P: Stamp in Parameters P -->> B: Build Payload B -->> P: Return Payload or Error end Note over P: Finish Building P -->>- M: Send finished Payload M ->> M: Updates Payload build status M -->> O: Update with new Build Status ``` Notice how the only real difference here is that IF the payload type definition says for MythicEncrypts=False and there's a translation container, then it's up to the translation container to generate any encryption keys. These keys can be part of a C2 Profile or they could be part of a payload type's build parameters. This is why you see this flow happening in two places. Other than that, when it comes to building a payload, the translation container has very little interaction. # Authenticated Container RPC Source: https://docs.mythic-c2.net/version-4.0/message-flow/container-rpc-authentication How v4 identity and scopes follow RabbitMQ work across containers Mythic 4.0 carries authenticated context across container RPC. This prevents a request from gaining authority merely because it moved from HTTP/GraphQL to RabbitMQ. ```mermaid theme={"system"} sequenceDiagram participant Client participant API as Mythic API participant Auth as Auth and scope checks participant MQ as RabbitMQ participant Container Client->>API: Bearer access/API token API->>Auth: Validate identity, operation, token scopes Auth-->>API: Signed request context API->>MQ: Publish request plus auth-context header MQ->>Container: Deliver scoped work Container->>Container: Library binds context to handler Container->>MQ: Publish RPC response with context MQ->>API: Correlated response API->>Auth: Enforce destination scope API-->>Client: Result ``` The context represents the initiating operator or bot, current operation, API-token/event-step attribution, and effective scopes. Compatible Mythic container libraries propagate it automatically through built-in RPC helpers. ## Developer requirements * Upgrade all container libraries before connecting them to v4. * Pass the handler context to container-library RPC helpers instead of creating a detached background context. * Preserve the Mythic auth-context RabbitMQ header in custom forwarding code. * Choose `custom_rpc_timeout` for legitimate long-running custom RPC work instead of retrying the same non-idempotent request. A missing or invalid context is an authentication failure, not a transient RabbitMQ failure. Blind retries can duplicate work and will not add the missing authority. # File Download Agent → Mythic Source: https://docs.mythic-c2.net/version-4.0/message-flow/file-download-agent-greater-than-mythic Register, transfer, and resume agent files with chunk numbers or byte offsets. An agent-to-Mythic download is sent inside a normal agent response under the `download` keyword inside of the `responses` array. Register the file first, keep the returned `file_id`, then send data with either numbered chunks or byte offsets. One task can transfer multiple files concurrently because each transfer has its own UUID. ## Choose one transfer mode | Mode | Registration fields | Data fields | Indexing | | --------------- | --------------------------------------- | ---------------------------- | -------------------------- | | Numbered chunks | `total_chunks`, optionally `chunk_size` | `chunk_num`, `chunk_data` | `chunk_num` begins at 1 | | Byte offsets | `total_size`, optionally `chunk_size` | `chunk_offset`, `chunk_data` | `chunk_offset` begins at 0 | Do not send `chunk_num` and `chunk_offset` together. `chunk_data` is base64-encoded in the agent message. ## Register an offset transfer ```json theme={"system"} { "action": "post_response", "responses": [{ "task_id": "task-uuid", "download": { "total_size": 10485760, "filename": "archive.zip", "full_path": "/tmp/archive.zip", "host": "workstation-01", "is_screenshot": false } }] } ``` Mythic returns a response containing `file_id`. ## Send data ```json theme={"system"} { "action": "post_response", "responses": [{ "task_id": "task-uuid", "download": { "file_id": "6f0e88d8-6f25-4de8-b9c4-4dcc4cf814f3", "chunk_offset": 0, "chunk_data": "BASE64_BYTES" } }] } ``` Mythic returns the chunk\_offset it just received with a success or error state. Mythic tracks bytes received and marks the file complete when the registered size has arrived. ## Register a chunk transfer ```json theme={"system"} { "action": "post_response", "responses": [{ "task_id": "task-uuid", "download": { "total_chunks": 12, "filename": "archive.zip", "full_path": "/tmp/archive.zip", "host": "workstation-01", "is_screenshot": false } }] } ``` Mythic returns a response containing `file_id`. ## Send data ```json theme={"system"} { "action": "post_response", "responses": [{ "task_id": "task-uuid", "download": { "file_id": "6f0e88d8-6f25-4de8-b9c4-4dcc4cf814f3", "chunk_num": 1, "chunk_data": "BASE64_BYTES" } }] } ``` Mythic returns the chunk\_num it just received with a success or error state. Mythic tracks bytes received and marks the file complete when the registered size has arrived. ## Resume an interrupted transfer After an interruption, register with the existing UUID and `resume: true`: ```json theme={"system"} { "action": "post_response", "responses": [{ "task_id": "task-uuid", "download": { "file_id": "6f0e88d8-6f25-4de8-b9c4-4dcc4cf814f3", "resume": true } }] } ``` For an offset transfer Mythic returns the next missing `chunk_offset`. For a numbered transfer it returns the next missing `chunk_num` and the established `chunk_size`. Continue from that value instead of restarting the file. ```mermaid theme={"system"} sequenceDiagram participant A as Agent participant C as C2 profile participant M as Mythic A->>C: Register metadata and total_size C->>M: Forward download registration M-->>C: file_id and chunk_offset 0 C-->>A: file_id and chunk_offset 0 loop Until interrupted A->>C: file_id, chunk_offset, chunk_data C->>M: Forward bytes M-->>A: next chunk_offset end A->>M: file_id and resume true M-->>A: next missing chunk_offset A->>M: Continue remaining bytes M-->>A: complete ``` Use `full_path`, `filename`, `host`, and `is_screenshot` on registration so Mythic can index and render the file correctly. The agent can update file metadata later through the normal response/RPC mechanisms. # File Upload Mythic->Agent Source: https://docs.mythic-c2.net/version-4.0/message-flow/file-upload-mythic-greater-than-agent What happens when you want to transfer a file from Mythic -> Agent? There's two different options: tracking a file via a UUID and pulling down chunks or just sending the file as part of your tasking. A Mythic->Agent upload is sent inside a normal agent response under the `upload` keyword inside of the `responses` array. ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant P as PayloadType participant H as HTTP Container participant A as Agent O ->>+ M: Upload file1 with task to ./test and get UUID M ->>+ P: Here's a task & file1's UUID P -->>- M: Tasking is ready M -->>- O: Tasking is Submitted A ->>+ H: Get Tasking H ->>+ M: Get Tasking M -->>- H: Here's your task H -->>- A: Here's your task A ->>+ H: Give me chunk1 of UUID with size X H ->>+ M: Give me chunk1 of UUID with size X M -->>- H: Here's chunk1, you have 3 total H -->>- A: Here's chunk1, you have 3 total A ->>+ H: Give me chunk2 of UUID with size X H ->>+ M: Give me chunk2 of UUID with size X M -->>- H: Here's chunk2, you have 3 total H -->>- A: Here's chunk2, you have 3 total A ->>+ H: Give me chunk3 of UUID with size X H ->>+ M: Give me chunk3 of UUID with size X M -->>- H: Here's chunk3, you have 3 total H -->>- A: Here's chunk3, you have 3 total A ->> A: Process file1 A ->>+ H: file1 (UUID) is now at /abs/path/to/test H ->>+ M: file1 (UUID) is now at /abs/path/to/test M --> M: update file info M -->- H: updated tracking success H -->- A: updated tracking success ``` This is an example of an operator uploading a file, it getting processed at the Payload Type's `create_tasking` function. When using files through the UI, the first thing the UI does is actually upload the files first to get unique UUIDs for them, then these values are passed along to the payload type as part of the tasking. This allows Mythic and the Agent to uniquely reference a file. The agent gets tasking, sees the file id, and submits more requests to fetch the file. Upon finally getting the full file, it resolves the relative upload path into an absolute path and sends an update back to Mythic to let it know that the file the operator said to upload to `./test` is actually at `/abs/pah/to/test` on the target host. Conversely, you can opt to not track the file (or track the file within Mythic, but not send the UUID down to the agent). In this case, you can't easily reference the same instance of the file between the Agent and Mythic: ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant P as PayloadType participant H as HTTP Container participant A as Agent O ->>+ M: Upload file2 with task to ./test2 and get UUID M ->>+ P: Here's a task & file2's UUID P ->> P: fetch contents and save them P -->>- M: Tasking is ready M -->>- O: Tasking is Submitted A ->>+ H: Get Tasking H ->>+ M: Get Tasking M -->>- H: Here's your task H -->>- A: Here's your task A ->> A: Process file2 A ->>+ H: file2 is now at /abs/path/to/test2 H ->>+ M: file2 is now at /abs/path/to/test2 M --> M: WTF is this? M -->- H: wtf is this? no update H -->- A: wtf is this? no update ``` You're able to upload and transfer the file just fine, but when it comes to reporting back information on it, Mythic and the Agent can't agree on the same file, so it doesn't get updated. You might be thinking that this is silly, of course the two know what the file is, it was just uploaded. Consider the case of files being deleted or multiple instances of a file being uploaded. # Operator Submits Tasking Source: https://docs.mythic-c2.net/version-4.0/message-flow/operator-submits-tasking There's a lot of moving pieces within Mythic and its agents, so it's helpful to take a step back and see how messages are flowing between the different components. ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant P as Payload Container O ->>+ M: Issues Task M ->> M: Validate Operator Can Submit Task M ->> M: Creates Preprocessing Task M -->>- O: Indicate new task exists M ->>+ P: Forward Task P ->> P: Parse Arguments P ->> P: Validate Arguments P ->> P: Perform OPSEC Pre-Check loop create_tasking P -->> M: Use RPC Functionality M -->> P: RPC Replies end Note over P: Finish Processing P ->> P: Perform OPSEC Post-Check P -->>- M: Send finished task M ->> M: Updates Task to Submitted or Error M -->> O: Update with new Task Status ``` Here we can see an operator issue tasking to the Mythic server. The Mythic server registers the task as "preprocessing" and informs the operator that it got the task. Mythic then sends the task off to the corresponding Payload Type container for processing. The container looks up the corresponding command python file, parses the arguments, validates the arguments, and passes the resulting parameters to the create\_tasking function. This function can leverage a bunch of RPC functionality going back to Mythic to register files, send output, etc. When it's done, it sends the final parameters back to Mythic which updates the Task to either `Submitted` or `Error`. Now that the task is out of the `preprocessing` state, when an agent checks in, it can receive the task. # P2P Messages Source: https://docs.mythic-c2.net/version-4.0/message-flow/p2p-messages More information on P2P message communication can be found [here](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/delegates). ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant P as PayloadType participant H as HTTP Container participant A as Agent1 participant B as Agent2 O ->>+ M: Deploy P2P Agent2 with ReverseTCP M ->>+ P: Here's a task P -->>- M: Tasking is ready M -->>- O: Tasking is Submitted A ->>+ H: Get Tasking H ->>+ M: Get Tasking M -->>- H: Here's your task H -->>- A: Here's your task A ->> A: Prep to catch P2P connection A ->> B: Execute Agent2 on remote box B ->> B: Start executing B ->> A: Connect to TCP Port A ->> A: Generate tempUUID for Agent2 B ->>+ A: Send Checkin Message (1) A ->>+ H: Message + Delegate msg for agent tempUUID (2) H ->>+ M: Message + Delegate msg for agent tempUUID M ->> M: Process Message M ->> M: Process Delegate msg M ->> M: Process Checkin Message for Agent2 M ->> M: Auto link Agent2 -> Agent1 (3) M ->> M: Generate Checkin Response for Agent2 M ->> M: Add Response as Delegate to Agent1 Response (4) M ->> M: Wrap up Agent1 Response M -->>- H: Agent1 Response H -->>- A: Agent1 Response A ->> A: Process Response A ->> A: Pull out Delegate Message A ->> A: update tempUUID with Agent2UUID (5) A ->> B: Send Checkin Response loop get tasking A ->>+ H: Get Tasking H ->>+ M: Get Tasking M ->> M: Checks tasks for Agent1 M ->> M: Checks tasks for reachable agents from Agent1 (6) M ->> M: Adds reachable tasks as Delegate Messages M -->>- H: Sends Tasks H -->>- A: Sends Tasks A ->> B: Sends Delegated Tasks end ``` 1\. P2P agents do their "C2 Comms" (which in this case is reaching out to agent1 over the decided TCP port) and start their Checkin/Encrypted Key Exchange/etc. 2\. When Agent1 gets a connection from Agent2, there's a lot of unknowns. Agent2 could be a new payload that isn't registered as a callback in Mythic yet. It could be an already existing callback that you're re-linking to or linking to for the first time from this callback. Either way, Agent1 doesn't know anything about Agent2 other than it connected to the right port, so it generates a temporary UUID to refer to that connection and waits for a message from Agent 2 (the first message sent through should always be from Agent2->Agent1 with a checkin message). Agent1 sends this information out with its next message as a "Delegate" Message of the form: ```json delegate data theme={"system"} { "action": "some action here", "delegates": [ { "message": "base64 agent message", "uuid": "some uuid Agent1 made up", "c2_profile": "ProfileName" } ] } ``` This "delegates" key sits at the same level as the "action" key and can go with any message (checkin, post\_response, get\_tasking). The "message" field is the checkin message from Agent2, the "uuid" field is the tempUUID that agent1 generated, and the "c2\_profile" is the name of the C2 profile that the two agents are using to connect. 3\. When Mythic parses this delegate message, it can automatically assume that there's a connection between Agent1 and Agent2 because Agent1's message has a Delegate from Agent 2. 4\. When Mythic is done processing Agent2's checkin message, it takes that result and adds it as a "delegate" message back for Agent1's message. 5\. When Agent1 gets its message back, it sees that there is a delegate message. That message is of the format: ```json delegate response from Mythic theme={"system"} { "action": "some action here", "delegates": [ { "message": "some new message base64 for Agent2", "uuid": "same UUID as the message agent -> mythic", "new_uuid": "optionally, a new uuid that Mythic knows Agent2 by" } ] } ``` 6\. You can see that the response format is a little different. We don't need to echo back the C2 Profile because the agent already knows that information. The "message" field is the Mythic response that goes back to Agent 2. The "uuid" field is the same tempUUID that the agent sent in the message to Mythic. The "new\_uuid" field though is Mythic indicating back to Agent1 that it doesn't know what `tempUUID` is, but the agent that sent that message actually has this UUID. That allows the agent to update its records. The main reason this is important is in the case where the connection between Agent1 and Agent2 goes away. Agent1 has to have some way of indicating to Mythic that Agent2 is no longer talking to it. Mythic only knows Agent2 by its UUID, so if Agent1 tried to report that it could no longer talk to tempUUID, Mythic would have no idea who that is. # Operator Guide Source: https://docs.mythic-c2.net/version-4.0/operational-pieces The Mythic 4.0 features operators use during an operation. Task callbacks, inspect output, and navigate callback graphs. Coordinate in standard channels or use streamed AI chat containers. Save scoped command and generic text expansions. Track uploads, downloads, previews, progress, and resumable transfers. Edit versioned UTF-8 text through supported agent tasks. Manage rich credential records and safely reference them in tasking. Search tasks, responses, files, credentials, tokens, and operational data. Render structured command output as text, tables, media, graphs, or tabs. Give one or more callbacks additional screen space. Review and download screenshots captured in the operation. Review the operation audit stream, warnings, and service events. Create opaque automation credentials with resource scopes. # Active Callbacks Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/active-callbacks Interact with callbacks and task output in the Mythic 4.0 interface. Open active callbacks with the phone icon. The page combines a virtualized, resizable callback table with configurable tasking views, callback browsers, task output, and C2 path information. ## Callback table Sort and filter the table, resize columns, or open a row's context menu. You can right click the table columns or the rows themselves to get a lot of options. The operation-scoped callback number shown in the UI is its `display_id`; v4 public tasking actions generally call this `callback_display_id`. Common fields include host, IP addresses, user, PID, operating system and architecture, integrity level, last check-in, description, payload, sleep information, and callback lock. High-integrity callbacks are visually distinguished when the payload reports `integrity_level` greater than 2. Row and bulk actions include: * open a tasking view or the single-callback workspace * edit the description and row color * lock or unlock the callback * open file, process, or custom browsers * inspect callback, payload, C2, and graph metadata * invoke an eventing workflow for one or more callbacks * hide a callback or issue its payload type's exit command Callback locks coordinate operators; they are not a substitute for operation permissions or API-token scopes. ## Tasking Select a callback, type a command, and press `Tab` to complete command names, parameter names, and supported choices. Use the arrow keys for history and `Ctrl+R` for reverse history search. [Operator aliases](/version-4.0/operational-pieces/operator-aliases) can expand reusable command lines, while [task references](/version-4.0/operational-pieces/understanding-commands/tasking-references) can resolve `@cred` and `@link` values immediately before submission. Tasks normally progress through these states: ```mermaid theme={"system"} stateDiagram-v2 [*] --> Creating Creating --> Parsing: payload type parses and checks parameters to make sure they're valid Parsing --> Submitted: payload type accepts task Submitted --> Processing: agent receives task Processing --> Processed: first response arrives Processed --> Completed: agent reports completion Processing --> Error Processed --> Error ``` The task header shows its operation-scoped display ID, issuing operator, command, display parameters, status, timestamps, comments, tags, and available actions. Open the display ID to share a focused single-task view. ## Output views Mythic 4.0 adds improved response streaming, pagination, filtering, completion tracking, and single-task navigation. Plain output can switch among `plaintext`, formatted JSON, `Markdown`, and `xterm-based` terminal rendering. Interactive commands use the terminal for keyboard input and ANSI output while Mythic continues to record responses and browser-originated input. Browser scripts can still transform structured output into tables, media, graphs, and tabs. Toggle the browser script per task when you need the raw response. Commands that implement the [file-editor protocol](/version-4.0/operational-pieces/file-editor) display a versioned editor directly in the response area. ## Callback workspaces Tasking views include the normal callback tab, expanded/split layouts, and console-like output. Callback tabs can also expose file and process browsers and C2 path diagrams. Use filters to reduce visible tasks without altering operation data. # API Tokens Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/api-tokens Create and use Mythic 4.0 opaque, scoped API tokens. API tokens are long-lived credentials for scripts, bots, eventing functions, and integrations. In Mythic 4.0 they are opaque values that begin with `mtk_`; they are not JWTs. Mythic shows a token value only once and stores only its hash. Copy it into a secret manager when it is created. A lost value cannot be recovered; create a replacement and delete the old token. ## Create a token Open operator settings, select the more dropdown next to your user or a bot account, click **+ API Tokens**, choose a descriptive name, and select the minimum required scopes. The creating identity can grant only scopes it already has. A write scope implies the corresponding read scope. Common scope forms are: * `callback.read` or `callback.write` for one resource family * `task.*` for all task scopes * `*` for every scope available to the identity Use the global wildcard only for trusted administration that cannot be expressed more narrowly. Operation membership, operator roles, callback locks, and block lists still apply after a scope check succeeds. You can also create a token through GraphQL: ```graphql theme={"system"} mutation CreateReadOnlyMonitor { createAPIToken( name: "response monitor" scopes: ["callback.read", "task.read", "response.read"] ) { status error token_value scopes } } ``` ## Use a token Access tokens and API tokens use the same standard header: ```bash theme={"system"} curl --insecure https://127.0.0.1:7443/graphql \ -H 'Authorization: Bearer mtk_REPLACE_WITH_TOKEN' \ -H 'Content-Type: application/json' \ --data '{"query":"query { whoami { status username current_operation_id scopes } }"}' ``` The legacy `apitoken: ` header is not accepted in v4. Never place a token in a URL, checked-in source file, task parameter, or log message. ## Inspect and revoke access * `whoami` reports the authenticated identity, current operation, and effective scopes. * `apiTokenScopeDefinitions` lists available resources and scope descriptions. * `scopeCheck` lets an integration verify required scopes before beginning work. * Deactivate a token temporarily or delete it permanently from operator settings. All API tokens created before upgrading from 3.4 must be regenerated. See [Mythic 3.4 -> 4.0 Updates](/version-4.0/updating/mythic-3.4-greater-than-4.0-updates) for the complete migration checklist. # Browser Scripts Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/browser-scripts Transform task responses into supported Mythic 4.0 output views. Browser scripts are operator-side JavaScript functions that transform a task and its response strings into structured UI data. A script can render plaintext, tables, authenticated media, graphs, or nested tabs. Manage scripts under **Extra Shortcuts → Browser Scripts**. Payload types can supply defaults for their commands; operators can enable, disable, clone, and modify their own copies. Use **Toggle BrowserScript** on a task to switch between the transformed and raw response. ## Function contract Every script exports a function with `task` and `responses` arguments and must return an object: ```javascript theme={"system"} function(task, responses) { if (task.status.toLowerCase().includes("error")) { return { plaintext: responses.join("") }; } const rows = responses.map((response) => { const item = JSON.parse(response); return { name: { plaintext: item.name }, pid: { plaintext: item.pid }, user: { plaintext: item.user } }; }); return { table: [{ title: "Processes", headers: [ { plaintext: "name", type: "string", fillWidth: true }, { plaintext: "pid", type: "number", width: 120 }, { plaintext: "user", type: "string", width: 220 } ], rows }] }; } ``` Top-level keys can be combined: | Key | Value | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plaintext` | A string rendered with plaintext, JSON, Markdown, and terminal view options. | | `table` | An array of table definitions with headers and rows. Cells can include text, tasking buttons, copy actions, and formatting. | | `media` | An array of objects containing an `agent_file_id`, optional `filename`, and optional `editable`. Mythic fetches previews and downloads with authenticated requests. | | `graph` | A graph definition for structured relationship output. | | `tabs` | An array of `{title, content}` objects; `content` uses the same supported keys recursively. | For example, display an operation file inline: ```javascript theme={"system"} return { media: [{ agent_file_id: data.file_id, filename: data.filename, editable: false }] }; ``` The legacy top-level `screenshot`, `download`, and `search` renderers were removed in v4. Return `media` for file previews/downloads, or use supported table cells and current routes. Custom fetches must add the current Bearer token; cookie-only media access no longer works. Payload authors should register scripts from the payload type so new operators receive them. See [Payload Type Browser Scripting](/version-4.0/customizing/payload-type-development/browser-scripting) for the complete table, graph, media, and tasking-button schemas. # Overview Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/c2-profiles ## What is it? Command and Control (C2) profiles are the way an agent actually communicates with Mythic to get tasking and post responses. There are two main pieces for every C2 profile: 1. Server code - code that runs in a docker container to convert the C2 profile communication specification (twitter, slack, dropbox, websocket, etc) into the corresponding REST endpoints that Mythic uses 2. Agent code - the code that runs in a callback to implement the C2 profile on the target machine. ## Where is it? C2 profiles can be found by going to Installed Services (headphone icon) from the left navigational bar. ## How do they work? Each C2 profile is in its own docker container, the status of which is indicated on the C2 Profiles tab. Each docker container has a python or golang service running in it that connects to a RabbitMQ message broker to receive tasking. This allows Mythic to modify files, execute programs, and more within other docker containers. Mythic 4.0 container messages carry authenticated context. Update C2 profiles to a compatible container library and preserve that context in any hand-written RabbitMQ forwarding. See [Container RPC Authentication](/version-4.0/message-flow/container-rpc-authentication). ## Parameters and hosted files C2 parameters can provide display names, labeled static choices, groups and UI ordering, expanded hide conditions, service-backed dynamic choices, and schema-driven `JSONString` values. Dynamic queries receive the other parameter values already selected, so one field can condition its choices on another. See [C2 Parameters](/version-4.0/customizing/c2-related-development/mythic-definition/2.1.2-c2-parameters). V4 also tracks files hosted by a C2 profile. Hosting records expose state and output and can be updated, retried, stopped, or removed from Mythic. ## Where can I find more documentation about them? The documentation container contains detailed information about the OPSEC considerations, traffic flow, and more for each container when you install the c2 profile. From the C2 Profiles tab, you can click the document icon to automatically open up the local documentation website to that profile. # Egress vs P2P Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/c2-profiles/egress-vs-p2p There are two kinds of C2 profiles - egress profiles that talk directly out of the target network or peer-to-peer (p2p) profiles that talk to neighboring agents. ## Egress Profiles The [http](https://github.com/MythicC2Profiles/http) and the [websocket](https://github.com/MythicC2Profiles/websocket) profiles are both examples of egress profiles. They talk directly out of the target network. Egress profiles have associated Docker containers that translate the profile's traffic into messages Mythic understands. See [C2 Class Definition](/version-4.0/customizing/c2-related-development/mythic-definition/2.1.1-c2-class-definition) to create your own. ## P2P Profiles Peer-to-peer profiles in general are a bit different. They don't talk directly out to the internet; instead, they allow agents to talk to each other such as via [smb](https://github.com/MythicC2Profiles/smb) and [tcp](https://github.com/MythicC2Profiles/tcp). This distinction between P2P and Egress for Mythic is made by a simple boolean indicating the purpose of the c2 container. ### P2P Visualizations P2P profiles announce their connections to Mythic via [P2P Connections](/version-4.0/customizing/hooking-features/linking-agents/action-p2p_info). When Mythic gets these messages, it can start mapping out what the internal mesh looks like. To help view this from an operator perspective, there is an additional views on the main Callbacks page. This view uses a directed graph to illustrate the connections between the agents. There's a central "Mythic Server" node that all egress agents connect to. When a route is announced, the view is updated to move one of the callbacks to be a child of another callback. # HTTP Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/c2-profiles/http ## What is it? The "HTTP" C2 profile speaks the exact same protocol as the Mythic server itself. All other C2 profiles will translate between their own special sauce back to this format. This profile has a docker container as well that you can start that uses a simple JSON configuration to redirect traffic on another port (with potentially different SSL configurations) to the main Mythic server. ## How does it work? This container code starts a small Go gin web server that accepts messages on the specified port and proxies all connections to the `/agent_message` endpoint within Mythic. This allows you to host the Mythic instance on port 7443 for example and expose the default HTTP profile on port 443 or 80. Clicking the "Configure" button gives a few options for how to edit and interact with the profile. #### Using SSL If you want to use SSL with this profile, edit the configuration to `use_ssl` to `true` and the C2 profile will automatically generate some self-signed certificates. If you want to use your own certificates though, you can upload them through the UI by clicking the "Manage Files" button next to the `http` profile and uploading your files. Then simply update the configuration with the names of the files you uploaded. ### Supported Payloads and Info This sections allows you to see some information about the C2 profile, including sample configurations. The name of a C2 profile cannot be changed once it's created, but everything else can change. The `Supported Payloads` shows which payload types can speak the language of this C2 profile. ### Profile Parameters This dialog displays the current parameters associated with the C2 profile. These are the values you must supply when using the C2 profile to create an agent. There are a few things to note here: * `randomize` - This specifies if you want to randomize the value that's auto-populated for the user. * `format_string` - This is where you can specify how to generate the string in the hint when creating a payload. For example, setting `randomize` to `true` and a `format_string` of `\d{10}` will generate a random 10 digit integer. * Every time you view the parameters, select to save an instance of the parameters, or go to create a new payload, another random instance from this format\_string will be auto-populated into that c2 profile parameter's hint field. # C2 Server Utilities Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/c2-profiles/opsec-checks ## C2 OPSEC Checks C2 Profiles can optionally provide some operational security checks before allowing a payload to be created. For example, you might want to prevent operators from using a known-bad named pipe name, or you might want to prevent them from using infrastructure that you know is burned. ### Where is it? These checks all happen within a single function per C2 profile with a function called `opsec`: ```python opsec checks theme={"system"} # The opsec function is called when a payload is created as a check to see if the parameters supplied are good # The input for "request" is a dictionary of: # { # "action": "opsec", # "parameters": { # "param_name": "param_value", # "param_name2: "param_value2", # } # } # This function should return one of two things: # For success: {"status": "success", "message": "your success message here" } # For error: {"status": "error", "error": "your error message here" } async def opsec(self, inputMsg: C2OPSECMessage) -> C2OPSECMessageResponse: response = C2OPSECMessageResponse(Success=True) response.Message = "Not Implemented, passing by default" response.Message += f"\nInput: {json.dumps(inputMsg.to_json(), indent=4)}" return response ``` From the code snippet above, you can see that this function gets in a request with all of the parameter values for that C2 Profile that the user provided. You can then either return success or error with a message as to why it passed or why it failed. If you return the error case, then the payload won't be built. ## C2 Server Configuration Checks C2 servers know the most about their configuration. You can pass in the configuration for an agent and check it against the server's configuration to make sure everything matches up or get additional insight into how to configure potential redirectors. ```python configuration checks theme={"system"} async def config_check(self, inputMsg: C2ConfigCheckMessage) -> C2ConfigCheckMessageResponse: response = C2ConfigCheckMessageResponse(Success=True) response.Message = "Not Implemented" response.Message += f"\nInput: {json.dumps(inputMsg.to_json(), indent=4)}" return response ``` ## C2 Server Redirect Rules C2 servers know the most about how their configurations work. You can pass in an agent's configuration and get information about how to generate potential redirector rules so that only your agent's traffic makes it through. ```python redirect rules theme={"system"} async def redirect_rules(self, inputMsg: C2GetRedirectorRulesMessage) -> C2GetRedirectorRulesMessageResponse: response = C2GetRedirectorRulesMessageResponse(Success=True) response.Message = "Not Implemented" response.Message += f"\nInput: {json.dumps(inputMsg.to_json(), indent=4)}" return response ``` # Save Parameters Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/c2-profiles/save-parameters ## What is it? An operator can provide non-default, but specific values for all of fields of a C2 profile and save it off as an instance. These instances can then be used to auto-populate all of the C2 profile's values when creating a payload so that you don't have to manually type them each time. ## Why have it? This is a nice time saver when creating multiple payloads throughout an operation. It's likely that in an operation you will have multiple different domains, domain fronts, and other external infrastructure. It's more convenient and less error prone to provide the specifics for that information once and save it off than requiring operators to type in that information each time when creating payloads. ## Where is it? The `Save Parameters` button is located next to each C2 profile by clicking the "headphones" icon at the left nav bar. ### How to create an instance To create a new named instance, select the `Save Instance` button to the right of a c2 profile and fill out any parameters you want to change. The name must be unique at the top though. # Comments Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/comments ## What are comments? Comments are a single text description that can be added to any task, file, credential, etc in an operation. All members of the operation can see and modify the comment, but the last person that adds or modifies it will show up as the one that added it. ## Where are they? Comments can be found in many places throughout Mythic. On almost any page where you see a task and output, you'll be able to see task comments. These comments can be added by selecting the dropdown for the task status and selecting comment. When there is a comment, you can click the chat bubble icon to show/hide them. Comments can be removed by editing the comment to be a blank string `""`. ### Searching Comments Comments are a nice way to highlight certain tasks and output as important for later use, but just like everything else, they can easily get lost in an operation. When searching across any primary object on the search page (tasks, files, credentials, etc), you can opt to search by comment as well. # Credentials Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/credentials Store, inspect, update, and reference credential material in Mythic 4.0. Open credentials with the key icon. Agents can register credentials from command responses, payload containers can create them through RPC, and operators can add or update them manually. ## Credential fields Every credential has a type, account, realm, credential value, and comment. Mythic 4.0 also supports: * `subtype` for a more precise classification within a credential type * `custom_display` for a safe, operator-friendly label * validity state and update history * dedicated JWT and Kerberos renderers Use `custom_display` to help operators identify a credential without placing the secret itself in views that do not require it. Credential updates retain one record instead of forcing an integration to create near-duplicates as metadata changes. The table identifies the creating task (or **Manual Entry**), operator, timestamps, type and subtype, realm, account, and validity. ## Use credentials in commands A `CredentialJson` command parameter presents matching operation credentials in the tasking UI. A developer can further limit choices by credential type for a specific command parameter. The selected value reaches the payload type's tasking function as a structured object. Mythic 4.0 also supports explicit references in string and structured parameters: ```text theme={"system"} @cred:42 @cred:42.account @cred:42.credential ``` Mythic resolves references immediately before tasking, records the resolution on the task, and sends containers both the resolved parameter and resolution metadata. See [Tasking References](/version-4.0/operational-pieces/understanding-commands/tasking-references). Agents can register credentials in a normal response, while payload type code can use MythicRPC for create, search, and update workflows. See [Hooking Features: Credentials](/version-4.0/customizing/hooking-features/credentials). # Event Feed Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/event-feed Review operational events, service messages, warnings, and resolutions. The event feed is Mythic's live operational audit stream. It records new callbacks, payload builds, operator login events, service state changes, API messages, warnings, and errors. Open it with the bell icon on the bottom left. Filter by message type, search message text, and focus on unresolved warnings. Errors appear as unresolved events until an operator records that the problem is resolved; the event remains in the history with its resolution state. Mythic 4.0 operation chat is separate from the event feed. Use [Operation Chat](/version-4.0/operational-pieces/operation-chat) for operator conversations and AI chat containers; use the event feed for system and audit events. # File Browser Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/file-browser Unified, Persistent File Browser ## What is it? The file browser is a visual, file browser representation of the directory listings that agents perform. Not all agents support this feature however. Mythic 4.0 refreshes this view with virtualized, resizable tables and incremental state updates so large persistent trees no longer require the entire browser to be rebuilt for every change. ## Where is it? From any callback dropdown in the "Active Callbacks" window, select "File Browser" and the view will be rendered in the lower-half of the screen. This information is a combination of the data across *all* of the callbacks, and is persistent. ## How do you use it? The view is divided into two pieces - a graphical hierarchy on the left and a more detailed view of a folder on the right. The top layer on the left will be the hostname and everything below it will correspond to the file structure for that host. You'll notice a green checkmark for the `C:` folder. The green checkmark means that an agent reported back information for that folder specifically (i.e. somebody tasked an `ls` of that folder or issued a `list` command via the button on the table side). This is in contrast the other folders in that tree - those folders are "implicitly" known because we have the full path returned for the folder we did access. If there is a red circle with an exclamation point, it means that you tried to perform an `ls` on the directory, but it failed. On the right hand side, the table view has a few pieces along the top: * The text field is the `path` associated with the information below with the corresponding hostname right above it. If you haven't received any information from any agent yet or you haven't clicked on a path, this will default to the current directory `.`. * The first buttons allow you to navigate back, forward, or up one directory based on your browsing. * The first button on the right is the `list` button. This looks for a command with `file_browser:list` set in the command's supported\_ui\_features loaded into the callback identified at the top of the tab. Then issues that command with the `host` and `path` shown in the first two fields. If you want to list the contents of a directory that you can't see in the UI, just modify these two values and hit `list`. * The second button is the `upload` button. This will look for the `file_browser:upload` set in the supported\_ui\_features for a command and execute that command loaded into that callback. In most cases this will cause a popup dialog where you can upload your file. * The list icon with an x on it indicates you preference for automatically issuing the `file_browser:list` task when you click into a folder that you haven't listed already (anything without a check mark or red exclamation point). This is off by default, but toggling it on can allow you to triage quite quickly. Just keep in mind that it does issue a lot of tasks as you click around. * The last field allows you to toggle viewing deleted files or not. ## Actions For each entry in the table menu on the right, there are some actions you can do by clicking the gear icon: The file browser only shows *some* information that's returned. There are portions that are Operating Specific though - like UNIX permissions, extended attributes, or SDDLs. This information doesn't make sense to display in the main table, so clicking the `View Permissions` action will display a popup with more specific information. The `Download History` button will display information about all the times that file has been downloaded. This is useful when you repeatedly download the same file over and over again (ex: downloading a user's Chrome Cookie's file every day). If you've downloaded a file, there will be a green download icon next to the filename. This will always point to the *latest* version of the file, but you can use the `download history` option to view all other instances in an easy pane. This popup will also show the comments associated with the tasks that issued the download commands. Right clicking a row provides more context options. Tasking to list a file/folder, download a file, or remove a file/folder. If a file is removed and reports back the removal to hook into the file browser, then the filename will have a small trash icon next to it and the name will have a strikethrough. Downloaded file records open in the operation [Files](/version-4.0/operational-pieces/files) workflow. A command that supports Mythic 4.0's interactive [File Editor](/version-4.0/operational-pieces/file-editor) can edit a remote UTF-8 file with version/conflict tracking instead of treating each change as a blind upload. # Interactive File Editor Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/file-editor Edit remote UTF-8 text through an agent with history and conflict detection Mythic 4.0 can render an interactive file editor inside task output when an agent command advertises `task_response:file_editor`. The editor supports: * UTF-8 text files up to 2 MB; * save, refresh, and explicit close actions; * version history for snapshots returned by the agent; * SHA-1 based conflict detection; * review of the exact staged edit before a forced overwrite; * read-only history after the parent task completes. ## Save flow Mythic does not put the edited contents directly in interactive tasking. It uploads the editor contents as a tracked Mythic file, then sends that `file_id`, the last known SHA-1, and a request ID to the agent. **Save anyway** is a deliberate force-overwrite path. Review the staged file and the agent-reported conflict before using it. ## Agent support The editor appears only for a command that implements the file-editor interactive message protocol. Commands that merely return file data or ordinary interactive terminal output continue to use their existing renderers. Agent developers should follow the [File Editor Protocol](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol). # Files Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/files Track, preview, edit, resume, and download operation files. Use the paperclip icon on the left to quickly navigate to the file search. Mythic associates each uploaded or downloaded file with its task, callback, host, operator, timestamps, remote path, hashes, and transfer state. ## Review files Select a row to inspect its SHA-1 and MD5 hashes, size, bytes received, task comment, tags, and file metadata. Preview supported files as media, text, hex, or a SQLite database without first saving them locally. Authenticated previews and downloads use the current Bearer token in v4. Select multiple completed downloads and choose **Zip & Download selected** to retrieve them as one archive. ## Transfer progress and resume Mythic 4.0 supports the existing numbered-chunk protocol and a byte-offset protocol. Offset transfers expose byte-level progress, and interrupted agent-to-Mythic downloads can resume from the next missing chunk or offset when the agent supplies the existing `file_id` with `resume: true`. ```mermaid theme={"system"} sequenceDiagram participant A as Agent participant M as Mythic A->>M: Register total_size and metadata M-->>A: file_id A->>M: chunk_offset plus chunk_data M-->>A: next missing chunk_offset Note over A,M: Connection is interrupted A->>M: existing file_id plus resume true M-->>A: next missing chunk_offset A->>M: Continue remaining bytes ``` See [Agent → Mythic File Downloads](/version-4.0/message-flow/file-download-agent-greater-than-mythic) for wire examples. ## Edit text files Commands that implement the v4 file-editor protocol can open a versioned UTF-8 document in the task response. The editor supports refresh, save, staged previews, conflicts, force-overwrite, history, and close. See [File Editor](/version-4.0/operational-pieces/file-editor). C2 profiles can also track files hosted through the profile. Operators can update, retry, stop, or remove a hosted file from the C2 profile workflow instead of treating hosting as an untracked side effect. # MITRE ATT&CK Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/mitre-att-and-ck ## What is it? MITRE ATT\&CK ([https://attack.mitre.org/](https://attack.mitre.org)) is an amazing knowledge base of adversary techniques. > MITRE ATT\&CK® is a globally-accessible knowledge base of adversary tactics and techniques based on real-world observations. The ATT\&CK knowledge base is used as a foundation for the development of specific threat models and methodologies in the private sector, in government, and in the cybersecurity product and service community. > > With the creation of ATT\&CK, MITRE is fulfilling its mission to solve problems for a safer world — by bringing communities together to develop more effective cybersecurity. ATT\&CK is open and available to any person or organization for use at no charge. ## Where is it? This is in development to bring into the new user interface. This is still tracked by the back-end and available via reporting, but the ATT\&CK matrix itself still needs to be ported over to the new React interface. ## How does this Task mapping happen? Commands can be automatically tagged with MITRE ATT\&CK Techniques (this is what populates the "Commands by ATT\&CK" output). To locate this, you just need to look at the associated python/golang files for each command. In addition to this file defining the general properties of the command (such as parameters, description, help information, etc). There's a field called `attackmapping` that takes an array of MITRE's `T#` values. For example, looking at the `apfell` agent's `download` command: ``` class DownloadCommand(CommandBase): cmd = "download" needs_admin = False help_cmd = "download {path to remote file}" description = "Download a file from the victim machine to the Mythic server in chunks (no need for quotes in the path)." version = 1 author = "@its_a_feature_" parameters = [] attackmapping = ["T1020", "T1030", "T1041"] argument_class = DownloadArguments browser_script = BrowserScript(script_name="download", author="@its_a_feature_") ``` When this command syncs to the Mythic server, those T numbers are stored and used to populate the ATT\&CK Matrix. When you issue this `download` command, Mythic does a lookup to see if there's any MITRE ATT\&CK associations with the command, and if there are, Mythic creates entries for the "Tasks by ATT\&CK" mappings. This is why you're able to see the exact command associated. ## How do I update this to add/remove mappings? Simply add your T# to the list like shown above, then run `sudo ./mythic-cli start [agent name]`. That'll restart the agent's container and trigger a re-sync of information. If the container is using golang instead of python for its Mythic connectivity, then you need to run `sudo ./mythic-cli build [agent name]` instead. # Operation Chat Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/operation-chat Standard and AI-backed chat channels for an operation Mythic 4.0 separates operator conversation from the Event Feed. Operation chat provides persistent channels for people and, when a chat container is installed, AI-backed channels that can stream responses and use tools. ## Channel types * **Standard channels** are operator-to-operator conversations. Members of the operation can search history, edit or delete their own messages, and track unread state. * **AI channels** are bound to an installed chat container and one of its advertised models. The channel stores model configuration separately from operator secrets. Channels can be locked, muted, or archived. Locking prevents new messages while preserving history; archiving removes a channel from the active list without deleting its messages. ## AI-backed conversations Depending on the selected chat container, an AI channel can provide: * streamed Markdown responses with cancellation and retry; * slash commands and clickable channel metadata; * Mythic and MCP tool-use cards with lazily loaded full output; * approval cards before a write-capable tool runs; * typed operator-input requests with selectable choices; * delegated sub-agent summaries and drill-down views. Chat container configuration and secrets may grant access to external model providers or tools. Review the container, choose narrowly scoped API tokens, and treat approval prompts as security boundaries. ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic chat participant C as Chat container participant T as Model or tool provider O->>M: Send channel message M->>C: ChatRequest with channel context C->>T: Provider request T-->>C: Streaming deltas or tool call alt tool requires approval C-->>M: Approval/input request M-->>O: Native approval card O->>M: Approve, deny, or provide input M->>C: New continuation ChatRequest end C-->>M: ChatResponse updates M-->>O: Streamed Markdown and metadata ``` ## API-token behavior Mythic creates per-channel bot API tokens for AI channels and scopes them to the operation and requested capabilities. Tokens are opaque and are not recoverable after creation. Invalidating a channel's bot token prevents that container from continuing to act until a replacement is created. For custom integrations, standard chat uses `chat.read`/`chat.write`; AI-backed chat uses `chat-ai.read`/`chat-ai.write`. ## Event Feed versus chat Use the **Event Feed** for server events, warnings, and audit context. Use **Operation Chat** for conversation. Resolving an Event Feed warning does not alter chat history, and archiving a chat channel does not remove event records. # Operations Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/operations ## What is an operation? Operations are collections of operators, payloads, tasks, artifacts, callbacks, and files. While payload types and c2 profiles are shared across an entire Mythic instance, operations allow fine grained control over the visibility and access during an assessment. ## Where are operations? Operation information can be found via the left navigation bar and clicking the pencil icon. If you're a global Mythic admin, you'll see *all* operations here. Otherwise, you'll only see operations that are associated with your account. ## How do you use operations? Every operation has at least one member - the lead operator. Other operators can be assigned to the operation with varied levels of access. * `operator` is your normal user. * `lead` is the lead of that operation * `spectator` can't do anything within Mythic. They essentially have Read-Only access across the entire operation. They can't create payloads, issue tasking, add comments, send messages, etc. They can search and view callbacks/tasking, but that's it. For more fine-grained control than that listed above, you can also create *block lists*. These are named lists of commands that an operator is *not allowed to execute* for a specific payload type. These block lists are then tied to specific operators. This offers a middle-ground between normal operator with full access and a spectator with no access. You can edit these block lists via the edit button. For the configure button for the operation, there are many options. You can specify a Slack webhook along with the channel. By default, whenever you create a payload via the "Create Payloads" page, it is tagged as alert-able - any time a new callback is created based on that payload, this slack webhook will be invoked. If you want to prevent that for a specific payload, go to the payloads page, select the "Actions" dropdown for the payload in question, and select to stop alerting. If you have the Slack webhook set on the operation overall, other payloads will continue to generate alerts, but not the ones you manually disable. You can always enable this feature again in the same way. For the operators edit button, you can edit who is assigned to the operation, what their roles are, and specify which (if any) block lists should be assigned to that user. ## Current Operations Because many aspects of an assessment are tied to a specific operation (payloads, callbacks, tasks, files, artifacts, etc), there are many things that will appear empty within the Mythic UI until you have an operation selected as your current operation. This lets the Mythic back-end know which data to fetch for you. Go to the operations page and, if you're assigned to an operation that you can see, you can select to "Make Current". # Operator Aliases Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/operator-aliases Reusable command and value expansions owned by each operator Operator aliases let you shorten frequently used tasking and chat commands without changing an agent. They are owned by the operator, can be enabled or disabled, and can be imported or exported from the operator settings page. ## Alias types ### Command aliases A command alias replaces the first command token and appends the supplied argument. If `triage` expands to `shell whoami && hostname`, then: ```text theme={"system"} triage /all ``` expands to: ```text theme={"system"} shell whoami && hostname /all ``` In AI chat, command aliases are invoked with `/name`. An alias can expand to another alias, but expansion stops after five levels and loops are rejected. ### Generic aliases A generic alias replaces `@name` anywhere in a string or JSON string value. For example, defining `@dc` as `10.10.10.10` lets you task: ```text theme={"system"} portscan -host @dc -ports 80,443,445 ``` References with a colon such as `@cred:12` are reserved task references, not generic aliases. Alias names must start with a letter and contain only lowercase letters, numbers, `_`, or `-`. ## Scope and precedence Aliases can be global to the operator or scoped to one payload type or AI chat container. A matching scoped alias takes precedence over the global alias with the same name. | Scope | Used in | | --------------------- | -------------------------------------- | | Global | All eligible tasking and chat contexts | | Callback payload type | Callbacks for that payload type | | AI chat container | Channels backed by that chat container | ## Portable configuration Exported aliases use a versioned JSON format: ```json theme={"system"} { "version": 1, "aliases": [ { "name": "triage", "alias": "shell whoami", "alias_type": "command", "active": true, "scope": {"type": "payload_type", "name": "apollo"} }, { "name": "dc", "alias": "10.10.10.10", "alias_type": "generic", "active": true } ] } ``` Imports report duplicates, invalid names, and missing payload/chat scopes instead of silently discarding them. # Overview Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/payload-types ## What is it? Payload types are the different kinds of agents that can be created and used with Mythic. ## Where are they located? Payload type information is located in the Installed Services page by clicking the headphone icon on the left nav bar. From this initial high-level view, a few important pieces of information are shown: * Container status indicates if the backing container is online or offline based on certain RabbitMQ Queues existing or not. This status is checked every 5 seconds or so. * The name of the payload type which must be unique * Which operating systems the agent supports * The container-library and payload-type semantic versions, supported C2 profiles, wrapper compatibility, and build capabilities To modify the Payload Type itself, you need to modify the corresponding class in the Payload Type's docker container. This class will extend the PayloadType class. ## Where can I find more documentation about them? The documentation container contains detailed information about the commands, OPSEC considerations, supported C2 profiles, and more for each payload type when you install it. From the Payload Types page, you can click the document icon to automatically open up the local documentation website to that agent. ## Mythic 4.0 build capabilities The payload builder supports saved, named build-parameter instances alongside saved C2 parameter instances. Build parameters can also expose friendly display names, labeled choices, explicit UI ordering, richer hide conditions, dynamic choices, and schema-driven `JSONString` editors. Normal payload builders should return output architecture and format as `build_metadata`. Wrapper payload types declare conditional `wrapper_payload_requirements`; Mythic uses those records and the wrapper's selected build parameters to page through only compatible payloads. See [Wrapper Compatibility](/version-4.0/customizing/payload-type-development/payload-type-info/wrapper-compatibility). # Containers Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/payload-types/containers ## Where are they? All installed docker containers are located at `Mythic/InstalledServices/` each with their own folder. The currently running ones can be checked with the `sudo ./mythic-cli status` . Check [A note about containers](/version-4.0/installation/a-note-about-containers) for more information about them. ## Why use containers? Containers allow Mythic to have each Payload Type establish its own operating environment for payload creation without causing conflicting or unnecessary requirements on the host system. ## When do containers come into play? Payload Type containers only come into play for a few special scenarios: * Payload Creation * Tasking * Processing Responses For more information on editing or creating new containers for payload types, see [Payload Type Development](/version-4.0/customizing/payload-type-development). # Screenshots Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/screenshots ## Where is it? Screenshots for an entire operation can be accessed paperclip icon and changing the search context to "Screenshots" from uploads or downloads. ## How to use it? The screenshots display as they're coming in and will indicate how many chunks are left before you have the full image. At any point you can click on the image and view what's available so far. # Search Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/search ## Where is it? The operational search feature can be found by clicking on any the search related shortcuts. From here you can search across callbacks, tasks, files, screenshots, keylogs, tokens, artifacts, and more. ## How is it used? The search bar checks for what you type as a case insensitive grep. # Socks Proxy Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/socks-proxy ## What is it? Socks proxy capabilities are a way to tunnel other traffic through another protocol. Within Mythic, this means tunneling other proxy-aware traffic through your normal C2 traffic. Mythic specifically leverages a modified Socks5 protocol (optionally with username/password for authentication). The Mythic server runs within a Docker container, and as such, you have to define which ports to expose externally. `Mythic/.env` has a special environment variable you can use to expose a range of ports at a time for this exact reason - `MYTHIC_SERVER_DYNAMIC_PORTS="7000-7010"`. By default this uses ports 7000-7010, but you can change this to any range you want and then simply restart Mythic to make the changes. If you configure `Mythic/.env` to have `MYTHIC_DOCKER_NETWORKING="host"`, then Mythic's core services will use `host` networking instead of `bridge` networking. This means that ports opened are automatically shared with the host (and you don't have to call out the ports specificially ahead of time). In this case, you can use UDPAssociate SOCKS functionality as well as not having to worry about which ports are exposed by Docker. ## Where is it? Click the "Socks" icon on the left or click the "Socks" tab from any search page. When you issue a command to start a socks proxy with Mythic, you specify an action "start/stop" and a port number. The port number you specify is the one you access remotely and leverage with your external tooling (such as proxychains). ## How does it work? 1. An operator issues a command to start socks on port 3333. This command goes to the associated payload type's container which does an RPC call to Mythic to open that port for Socks. 2. Mythic opens port 3333 in a go routine. 3. An operator configures proxychains to point to the Mythic server on port 3333. 4. An operator runs a tool through proxychains (ex: `proxychains curl https://www.google.com`) 5. Proxychains connects to Mythic on port 3333 and starts the Socks protocol negotiations. 6. The tool sends data through proxychains, and Mythic stores it in memory. In this temporary data, Mythic assigns each connection its own ID number. 7. The next time the agent checks in, Mythic takes this socks data and hands it off to the agent as part of the normal [Action: get\_tasking](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action_get_tasking) or [post\_response](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response) process. 8. The agent checks if it's seen that ID before. If it has, it looks up the appropriate TCP connection and sends off the data. If it hasn't, it parses the Socks data to see where to open the connection. Then sends the resulting data and same randomID back to Mythic via [Action: post\_response](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response). 9. Mythic gets the response, parses out the Socks specific data, and sends it back to proxychains The above is a general scenario for how data is sent through for Socks. The Mythic server itself doesn't look at any of the data that's flowing - it simply tracks port to Callback mappings and shuttles data appropriately. Your proxy connections are at the mercy of the latency of your C2 channel. If your checkin time is every 10s, then you'll get one message of traffic sent every 20s (round trip time). This breaks a LOT of protocols. Therefore, it's recommended that you change the sleep of your agent down to something very low (0 or as close to it). Don't forget to change the sleep interval of your agent *back* to your normal intervals when you're done with Socks so that you reduce the burden on both the server and your agent. # Expanded Callbacks Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/split-callbacks For any active callback, select the dropdown next to it (or right click the row) and select "Expand Callback". This will open a new tab for that callback where you can actually view the tasking full screen with metadata on the side. # Tags Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/tags ## What are tags? Mythic allows you to track types of tags as well as instances of tags. A tag type would be something like "contains credential" or "objective 1" - these take a name, a description, and a color to be displayed to the user. An instance of a tag would then include more detailed information such as the source of the information, the actual credential contained or maybe why that thing is tagged as "objective 1", and can even include a link for more information. ### Why should I bother with this? Tagging allows more logical grouping of various aspects of an operation. You can create a tag for "objective 1" then apply that tag to tasks, credentials, files, keylogs, etc. This information can then be used for easier deconflictions, attack path narratives, and even a way to signal information to other members of your assessment that something might be worth while to look at. ## Where are tags? The tag icon on the left navigation bar takes you to the tag management page where you can view/edit/create various types of tags and see how many times that tag is used in the current operation. ## Where can I see tags? Tags are available throughout the various Mythic pages - anywhere you see the tag icon you can view/edit/add tags. # Overview Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/understanding-commands Commands keep track of a wealth of information such as name, description, help information, if it needs admin permissions, the current version, any parameters, artifacts, MITRE ATT\&CK mappings, which payload type the command corresponds to, who created or last editing the command, and when. That is a lot of information, so let’s break that down a bit. # Basic Information Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/understanding-commands/basic-information ## Built-in Commands All PayloadTypes get 2 commands for free - `clear` and `help`. The reason these two commands are 'free' is because they don't actually make it down to the agent itself. Instead, they cause actions to be taken on the Mythic server. ### Clear The clear command does just that - it clears tasks that are sitting in the queue waiting for an agent to pick them up. It can only get tasks that are in the submitted stage, not ones that are already to the processing stage because that means that an agent has already requested it. `clear` - entering the command just like this will clear all of the tasks in that callback are in the appropriate stages. `clear all` - entering the command just like this will clear `all` tasks you've entered on that callback that are in the appropriate stages. `clear #` - entering the command just like this will attempt to clear the task indicated by the number after clear. If a command is successfully cleared by this command before an agent can get to it, then that task will get an automated response stating that it was cleared and which operator cleared it. The clear task itself will get back a list of all the tasks it cleared. ### **help** The `help` command allows users to get lists of commands that are currently loaded into the agent. Just `help` gives basic descriptions, but `help [command]` gives users more detailed command information. These commands look at the loaded commands for a callback and looks at the backing Python files for the command to give information about usage, command parameters, and elevation requirements. Agent developers are able to override this generic help format for their own agent if they wish. # MITRE ATT&CK in Commands Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/understanding-commands/mitre-att-and-ck MITRE ATT\&CK is a great way to track what both offense and defense are doing in the information security realm. To help Mythic operators keep track, each command can be tagged with its corresponding MITRE ATT\&CK information: There can be as many or as few mappings as desired for each command. This information is used in two different ways, but both located in the MITRE ATT\&CK button at the top. The "Fetch All Commands Mapped to MITRE" button takes this information to populate out what is the realm of `possible` with all of the payload types and commands registered within Mythic. This gives a coverage map of what could be done. Clicking each matrix cell gives a breakdown of which commands from which payload types achieve that objective. The "Fetch All Issued Tasks Mapped to MITRE" only shows this information for commands that have already been executed in the current operation. This shows what's been done, rather than what's possible. Clicking on a cell with this information loaded gives the exact task and command arguments that occurred with that task. # Parameters Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/understanding-commands/parameters Every command is different – some take no parameters, some take arrays, strings, integers, or a number of other things. To help accommodate this, you can add parameters to commands so that operators know what they need to be providing. You must give each parameter a name and they must be unique within that command. You can also indicate if the parameter is required or not. Parameters can be in conditional parameter "groups" - this allows you to say things like parameter X and parameter Y are mutually exclusive, but you should always supply parameter W. As an operator, if there are any parameter groups for a command and you don't provide enough parameters to determine which group to use, Mythic will throw a warning and ask you to use `shift + enter` to force the modal popup. From here, there's a dropdown at the top to change the group you're looking at to see which parameters to enter. If a command takes named parameters, but none are supplied on the command line, a GUI modal will pop up to assist the operator. At any time once you've typed a command name you can hit `shift+enter` to open up a modal and input parameters that way. Tab complete is also your friend on the command line. In order to modify the command or any of its components, you need to modify the corresponding python class in the Payload Type container. # Tasking References Source: https://docs.mythic-c2.net/version-4.0/operational-pieces/understanding-commands/tasking-references Resolve stored credentials, payload links, callbacks, and graph edges while tasking Tasking references let an operator select Mythic-tracked objects without copying their raw values into the command line. Mythic resolves references before the payload type's tasking functions run, records how each value was resolved, and sends the resolution metadata to the container. Set `resolve_task_references: true` when creating tasks through GraphQL. The Mythic UI enables resolution for supported tasking paths. ## Credential references Use a credential's operation-scoped database ID: ```text theme={"system"} @cred:42 ``` For a `CredentialJson` parameter, this resolves to the complete structured credential. In a string parameter, select one field: ```text theme={"system"} @cred:42.credential @cred:42.account @cred:42.realm @cred:42.type @cred:42.subtype @cred:42.custom_display @cred:42.id ``` The credential must exist, not be deleted, and belong to the current operation. ## Link references `@link` resolves structured `AgentConnect` parameters from a callback or payload. Use comma-separated `key=value` arguments: ```text theme={"system"} @link:callback=17,c2=tcp @link:payload=bc3d10a8-8d2a-4ced-890e-ccf835df4050,host=WORKSTATION01,c2=tcp ``` * `callback` is the callback display ID and must identify an active callback in the operation. * `payload` is a successfully built payload UUID. `host` records a payload-on-host relationship after task creation. * `c2` is optional when Mythic can choose an unambiguous C2 profile. ## Edge references For a structured `LinkInfo` parameter, reference a P2P callback graph edge: ```text theme={"system"} @link:edge=91 ``` The edge must be connected to the callback being tasked. Mythic resolves the opposite callback, its payload, C2 profile, and connection parameters. The wire form is `@link:edge=`. ## Container resolution metadata Each task includes `keyword_resolution` entries: ```json theme={"system"} { "raw": "@cred:42.account", "keyword": "cred", "selector": "42", "field": "account", "value_type": "string", "expanded_value": "alice", "parameter_names": ["username"] } ``` Container libraries expose helpers to reconstruct the operator's original expression when display parameters should avoid showing sensitive expanded values: ```python Python theme={"system"} safe_display = taskData.Task.RevertKeywords( taskData.args.get_arg("username"), "username", ) ``` ```go Go theme={"system"} value, _ := taskData.Args.GetArg("username") safeDisplay := taskData.Task.RevertKeywords(value, "username") ``` Resolution is operation-scoped and happens on the server. Payload type code should consume the expanded value and use the recorded metadata for display or audit context; it should not query a second time just to resolve the same reference. # Operators Source: https://docs.mythic-c2.net/version-4.0/operators Accounts, roles, invite links, aliases, and bot identities in Mythic 4.0. ## Passwords and authentication Every interactive operator authenticates with a username and password. On initial startup Mythic creates `mythic_admin` with `MYTHIC_ADMIN_PASSWORD` from the environment or `Mythic/.env`; if no value is supplied, Mythic generates one. View the generated value with `sudo ./mythic-cli config get MYTHIC_ADMIN_PASSWORD`. Passwords must be at least 12 characters. Ten consecutive failed attempts lock a normal account. The initial admin account is not locked, but failed authentication attempts are throttled. Protected v4 HTTP and GraphQL requests require an access token or API token in `Authorization: Bearer `. For unattended automation, create a narrowly [scoped API token](/version-4.0/operational-pieces/api-tokens). ## Operator permissions * **Admin** is a global role that can manage operators and access every operation. * **Operation admin** leads one operation and can manage its membership, unlock callbacks, and override its OpSec checks. * **Operator** can task and modify resources in assigned operations, subject to block lists and callback locks. * **Spectator** can read the assigned operation but cannot task callbacks or modify operational data. ## Invite links Admins can enable invite links with `MYTHIC_SERVER_ALLOW_INVITE_LINKS` or from global settings. Each link can assign the new operator to an operation and enforce a usage limit. Generated links, use counts, and revocation state are persisted, so an unused link remains manageable across server restarts until it is revoked or exhausted. Every use is recorded in the event feed. Review and revoke unused links from operator settings. ## Aliases Mythic 4.0 aliases let an operator save reusable command or generic text expansions. They can be scoped to payload types or chat containers, can reference other aliases, and can be imported or exported. See [Operator Aliases](/version-4.0/operational-pieces/operator-aliases). ## Bots Bot accounts cannot use the interactive login page. They authenticate with API tokens and can be assigned operation roles and block lists like other operators. Mythic creates an operation bot when an operation is created, and admins can create additional bot accounts. Grant bot tokens only the scopes needed by the automation. Token scopes do not elevate a bot beyond its operation membership or role. # Presentations / Webinars Source: https://docs.mythic-c2.net/version-4.0/presentations-webinars This page tracks presentations / webinars about Mythic All information is tracked in the MythicMeta organization on GitHub available here: [https://github.com/MythicMeta/Presentations/blob/main/README.md](https://github.com/MythicMeta/Presentations/blob/main/README.md) ## Webinars * Feb 23, 2022 - [Mythic 2.3 & Apollo 2.0 Updates](https://github.com/MythicMeta/Presentations/tree/main/Mythic2.3\&Apollo2.0Webinar) * Recording: [Zoom Webinar](https://specterops.zoom.us/rec/play/oF92jvM_xyMUEgPzW8FXpbD60DI8Aus5HoKGprB5RgeVOY86CgU0nvnc1hA5qbBF4MuWeB8QLKIJoHf4.EOlSl_vOVbjPAh-8?continueMode=true\&tk=2ISdV7O9BwJwH5VdPC6z0W0mwODOQN4C-RhBkyWyeWE.DQMAAAAS6CLt0xZ4WTc0Q3IxY1N6U0NLMmNxcERmS2p3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\&uuid=WN_vIiEl-TSS7qHT3CfwIXgmg&_x_zm_rtaid=_5rkfIN7RpC55NLJ8KjYhw.1645683612150.7db747332ba7d8da56df252edeef68a9&_x_zm_rhtaid=77) * Slides: [PDF](https://github.com/MythicMeta/Presentations/blob/main/Mythic2.3%26Apollo2.0Webinar/Feb23MythicApolloWebinar.pdf) ## Presentation Slides * Sep 18, 2025 - MCTTP * Slides [PDF](https://github.com/MythicMeta/Presentations/blob/main/MCTTP-2025/Architecting%20Mythic_The%20Evolution%20and%20Engineering%20of%20a%20C2%20Framework.pdf) # Quick Usage Source: https://docs.mythic-c2.net/version-4.0/quick-usage This is a quick primer on using Mythic for the first time This section will quickly go from first connection to running a basic agent. This walkthrough assumes you have the `apollo` agent and the `http` c2 profile installed. ### Operations When you log in with the admin account, you'll automatically have your current operation set to the default operation. Your current operation can be found by expanding the side navigation bar and clicking operations. When other operators sign in for the first time, they won't have an operation set to their current operation. ### Creating a Payload You need a payload to use. Click the hazard icon with a green plus on the left side bar. You'll be prompted to select which operating system. This is used to filter down possible payloads to generate. Next select the payload type you're wanting to build and fill out any necessary build parameters for the agent. Click Next. TReview values even when loading a saved instance because installed payload and C2 definitions can change between versions. Select any commands you want stamped into the payload initially (some agents support this type of dynamic loading, some don't). This will show commands not yet selected on the left and commands already selected on the right. There are some that might be pre-selected for you based on the agent developer (some are built in and can't be removed, some suggested, etc). If you hover over any of the commands you can see descriptive information about them. You can potentially load commands in later, but for this walkthrough select all of them. Click `Next`. For c2 profiles, select the `HTTP` profile at the top and click to include it. Change the `Callback host` parameter to be where you want the agent to connect to (if you're using redirectors, you specify that here), similarly specify the `Callback port` for where you want the agent to connect to. Click Next. You can save this named C2 parameter instance for later builds. Any dynamic choice comes from the currently online C2 service and may depend on values selected earlier in the form. The HTTP profile by default listens on port 80. If you want to connect to port 443 with SSL instead, you need to go to the C2 profile management page (click the headphones at the top) and adjust the configuration for the HTTP profile. Provide a name for the agent (a default one is auto populated) and provide a description that will auto populate the description field for any callbacks created based on this payload. Click `Next`. Once you click submit, you'll get a series of popups in the top giving feedback about the creation process. The blue notification popups will go away after a few seconds, but the green success or red error messages must be manually dismissed. This provides information about your newly created agent. If the server within the HTTP profile container wasn't running when you created the payload (it's not by default), the Mythic server will automatically start it for you as part of this creation process. ### Using the Payload Click the hazard icon (no green plus) on the side to go to the created payloads page. This is where you'll be able to see all of the payloads created for the current operation. You can delete the payload, view the configuration, or download the payload. For this walkthrough, download the payload (green download icon). Now move the payload over to your target system and execute it. Once you've done that, head to the Active Callbacks page left side via the phone icon. ### Callback Interaction This is where you'll be able to interact with any callback in the operation. Click the button for the row with your new agent (or double click the row in general) to bring up information in the bottom pane where you can type out commands and issue them to the agent. # Overview Source: https://docs.mythic-c2.net/version-4.0/reporting # Artifacts Source: https://docs.mythic-c2.net/version-4.0/reporting/artifacts ## What is it? Artifacts track potential indicators of compromise and other notable events occurring throughout an operation. ## Where is it? A list of all current artifacts can be found by clicking the fingerprint icon in the left nav bar. ## How to use it? This page tracks all of the artifacts automatically created by executing tasks. This should provide a good idea for both defenders and red teamers about the artifacts left behind during an operation and should help with deconfliction requests. ### When is it updated? Artifacts are created in a few different ways: 1. A command's tasking automatically creates an artifact. 2. An agent reports back a new artifact in an ad-hoc fashion # MITRE ATT&CK Source: https://docs.mythic-c2.net/version-4.0/reporting/mitre-att-and-ck ## What is it? [MITRE ATT\&CK](https://attack.mitre.org/) is a knowledge base of adversary tactics and techniques mapped out to various threat groups. It provides a common language between red teams and blue teams when discussing operations, TTPs, and threat hunting. For Mythic, this provides a great way to track all of the capabilities the agents provide and to track all of the capabilities so far exercised in an operation. For more information on MITRE ATT\&CK, check out the following: * [https://attack.mitre.org](https://attack.mitre.org/) * [https://twitter.com/mitreattack](https://twitter.com/mitreattack) * [https://attackevals.mitre.org/](https://attackevals.mitre.org/) ## Where is it? [MITRE ATT\&CK](https://attack.mitre.org/) integrations can be found by clicking the chart icon from the left navigation bar. ## How to use it? There are a few different ways to leverage this information. ### Commands by ATT\&CK Clicking the "Fetch All Commands Mapped to MITRE" action will highlight all of the matrix cells that have a command registered to that ATT\&CK technique. Clicking on a specific cell will bring up more specific information on which payload type and which command is mapped to that technique. All of this information comes from the [MITRE ATT\&CK](/version-4.0/operational-pieces/understanding-commands/mitre-att-and-ck) portion of commands. ### Tasks by ATT\&CK This is a slightly different view. This button will highlight and show the cells that have been exercised in the current operation. A cell will only be highlighted if a command was executed in the current operation with that ATT\&CK technique mapped to it. The cell view will show the exact command that caused the cell to be highlighted with a link (via task number) back to the full display of the task: # Reports Source: https://docs.mythic-c2.net/version-4.0/reporting/reports ## What is it? Mythic can generate JSON or XML style reports. If you need a PDF version, simply generate the XML, open it up locally, and then in your browser save it off to PDF. ## Where is it? Report generation is located from the checker flag icon from the left navigation bar. ## How to use it? You can select your output format, if you want to include MITRE ATT\&CK mappings inline with each tasking and if you want a MITRE ATT\&CK Summary at the end. You can also optionally exclude certain callbacks, usernames, and hostnames from being included in the generated report. The final generated report can be downloaded from this screen when it's ready via a toast notification. If you navigate away before it's done though, the report is also always available from the "files" section of the search page (click the paper clip icon and select "Uploads" instead of "Downloads"). # Database Schema Source: https://docs.mythic-c2.net/version-4.0/scripting/database-schema ## What is it? The database schema describes the current state of Mythic within the `mythic_postgres` Docker container. Mythic tracks *everything* in the Postgres database so that if an operator needs to close their browser or the server where Mythic runs reboots, nothing is lost. The benefit of having all data tracked within the database and simply streamed to the operator's interface means that all operators stay in sync about the state of the operation - each operator doesn't have to browse all of the file shares themselves to see what's going on and you don't have to grep through a plethora of log files to find that one task you ran that one time. ## Where is it? The database lives in the `postgres-docker` folder and is mapped into the `mythic_postgres` container as a volume. This means that if you need to move Mythic to a new server, simply stop mythic with `./mythic-cli stop`, copy the `Mythic` folder to its new home, and start everything back up again with `./mythic-cli start`. On the first start of Mythic, the database schema is loaded from a schema file located in `mythic-docker`: [https://github.com/its-a-feature/Mythic/blob/master/mythic-docker/src/database/schema.go](https://github.com/its-a-feature/Mythic/blob/master/mythic-docker/src/database/schema.go). ## How is it used? Since the database schema is the source of truth for all of Mythic, mythic scripting, and all of the operator's interfaces, it needs to be easily accessible in a wide range of cases. The `mythic_server` container connects directly to the `mythic_postgres` container to sync the containers and quickly react to agent messages. The `mythic_graphql` container (Hasura) also directly connects to the database and provides a GraphQL interface to the underlying data. This GraphQL interface is what both the React UI and mythic scripting use to provide a role-based access control (RBAC) layer on top of the database. ## How do I use it? How do you, as an operator or developer, find out more about the database schema? The easiest way is to click the hamburger icon in the top left of Mythic, select "Services", and then select the "GraphQL Console". This drops you into the Hasura Login screen; the password for Hasura can be found randomly generated in your `Mythic/.env` file. From here, the API tab, shown below, provides an easy way to dynamically explore the various queries, subscriptions, and mutations you can make to the database right here or via scripting. Since the Mythic Scripting simply uses this GraphQL interface as well, anything you put in that center body pane you can submit as a POST request to Mythic's grapqhl endpoint (shown above) to achieve the same result. The majority of the functions within Mythic Scripting are simply ease-of-use wrappers around these same queries. If you want to have even more fun exploring how the GraphQL interface manipulates the database schema, you can check out the built-in Jupyter Notebook and test out your modifications there as well. As shown in the two screenshots below, you can create scripts to interact with the GraphQL endpoints to return only the data you want. This scripting, combined with the Hasura GraphQL console allows operators to very easily get direct access and real-time updates to the database without having to know any specific SQL syntax or worry about accidentally making a schema change. # Hasura Console Source: https://docs.mythic-c2.net/version-4.0/scripting/hasura Explore and test Mythic 4.0 GraphQL queries, mutations, and subscriptions. Open **Services → GraphQL Console** from Mythic's main menu. The console is intended for schema exploration and administrative troubleshooting; scripts should connect to Mythic's proxied `/graphql` endpoint with a scoped Bearer token. To sign in to the administrative console, retrieve the local Hasura secret on the Mythic host: ```bash theme={"system"} sudo ./mythic-cli config get hasura_secret ``` The Hasura admin secret bypasses normal row permissions and API-token scopes. Do not embed it in integrations or share it as an operator credential. ## Explore the schema Use the **API** tab's documentation explorer to inspect current fields and input types. This is especially important for v4 action changes: action names are camelCase, `/api/v1.4` is gone, and public actions generally use operation-scoped display IDs. Test authentication against the proxied API with `whoami`: ```graphql theme={"system"} query CurrentIdentity { whoami { status error username current_operation_id scopes } } ``` An example scoped table query is: ```graphql theme={"system"} query RecentTasks { task(order_by: {timestamp: desc}, limit: 20) { display_id command_name status operator { username } callback { display_id host user } } } ``` Copy the generated operation into your GraphQL client, then use `Authorization: Bearer ` against `https://MYTHIC_HOST:7443/graphql`. # Scripting and GraphQL Source: https://docs.mythic-c2.net/version-4.0/scripting/home Authenticate to Mythic 4.0 and automate it through GraphQL. Mythic's UI and scripting clients use the same GraphQL queries, mutations, and subscriptions. GraphQL lets a caller request an exact result shape and subscribe to live operational updates without joining data from several REST endpoints. All GraphQL HTTP requests are sent to `/graphql`. Subscriptions use the corresponding WebSocket endpoint. Supported operations begin with `query`, `mutation`, or `subscription`. ## Authentication Protected HTTP and GraphQL endpoints require a Bearer token: ```http theme={"system"} Authorization: Bearer mtk_REPLACE_WITH_TOKEN Content-Type: application/json ``` The value can be a current access token returned by `/auth` or an opaque, scoped [API token](/version-4.0/operational-pieces/api-tokens). The v3 `apitoken` header and cookie-only authentication are not accepted. ```bash theme={"system"} curl --insecure https://127.0.0.1:7443/graphql \ -H 'Authorization: Bearer mtk_REPLACE_WITH_TOKEN' \ -H 'Content-Type: application/json' \ --data '{"query":"query { whoami { status username current_operation_id scopes } }"}' ``` Use read-only resource scopes for monitors and reports, and add write scopes only for mutations the integration performs. Query `scopeCheck` during startup when a tool requires a known set of permissions. ## Choosing an interface * Use the [Mythic Python package](/version-4.0/scripting/scripting) for common workflows and subscriptions. * Use the [Hasura console](/version-4.0/scripting/hasura) to explore the current schema and test queries. * Use direct GraphQL when another language already has a mature GraphQL client. The public schema is the source of truth for argument names. In v4, action names are camelCase and many actions accept operation-scoped `task_display_id` or `callback_display_id` rather than internal database IDs. # Python Scripting Source: https://docs.mythic-c2.net/version-4.0/scripting/scripting Automate Mythic 4.0 with the supported Python package. Install the v4-compatible package from PyPI: ```bash theme={"system"} python3 -m pip install 'mythic>=0.3.0rc8' ``` Use the newest compatible stable release when one is available. The source and examples live in [MythicMeta/Mythic\_Scripting](https://github.com/MythicMeta/Mythic_Scripting). ## Log in with an API token ```python theme={"system"} import asyncio from mythic import mythic async def main(): client = await mythic.login( server_ip="127.0.0.1", server_port=7443, apitoken="mtk_REPLACE_WITH_TOKEN", ssl=True, ) callbacks = await mythic.get_all_active_callbacks(mythic=client) for callback in callbacks: print(callback["display_id"], callback["host"], callback["user"]) asyncio.run(main()) ``` Create the token in Mythic first and give it only the scopes used by the script. The package sends API and access tokens through `Authorization: Bearer `. ## Log in with a password ```python theme={"system"} client = await mythic.login( server_ip="127.0.0.1", username="operator", password="replace-me", ssl=True, ) ``` Password login produces a refreshable session. Pass `create_apitoken=True` only when the script intentionally needs to mint and retain a long-lived token; by default that helper requests broad access, so creating a named token with explicit scopes is safer for production automation. ## Run a custom GraphQL query ```python theme={"system"} result = await mythic.execute_custom_query( mythic=client, query=""" query Identity { whoami { status username current_operation_id scopes } } """, ) print(result["whoami"]) ``` The Python package also exposes async subscription generators for callbacks, tasks, responses, files, and operational events. Prefer the supplied helpers for stable workflows; use `execute_custom_query` when you need a field that is not yet wrapped. # Mythic 2.1 -> 2.2 Updates Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.1-greater-than-2.2-updates If you want to look into all the new features available to you as a payload developer from an agent perspective, check out [Agents 2.1.\* -> 2.2.2](https://github.com/MythicMeta/Documentation/blob/primary/updating/mythic-2.1-greater-than-2.2-updates/agents-2.1.-greater-than-2.2.2). The rest of this page is for higher-level updates and UI changes. Either way, to leverage these updates and to upgrade your Mythic instance, you will need to delete your current database `sudo ./mythic-cli database reset` and then pull in the updates and `sudo ./mythic-cli mythic start` again. If you're coming from the 2.1.\* Mythic instances, your current agents and c2 profiles will NOT work. They will NOT work. This update changed a lot of the underlying ways those agents/c2 profiles communicated and synced with mythic, so you'll need to update those. In most cases, once the developer has announced their stuff is updated, you can use the `./mythic-cli install github [branch] [-f]` command to remove the old version and pull in the new version. When updating minor versions (2.1 to 2.2), make sure you drop your database with `sudo ./mythic-cli database reset` and delete your `Mythic/.env` file. ## Split out C2 / PayloadTypes One of the issues we've had with Mythic in the past is that people just install it and start it, without any additional configuration. There's no issue with this, but this does mean that all payload types and all c2 profiles have their containers pulled down and started. If you're not expecting it, this can be a lot of space. There are ways to configure this, but there needs to be a way thats a bit more aparent. So, to help with this (and to help with versioning/updates overall), all Payload Types and C2 Profiles are now split out from the main Mythic repository. * Agents - [https://github.com/MythicAgents](https://github.com/MythicAgents) * C2 Profiles - [https://github.com/MythicC2Profiles](https://github.com/MythicC2Profiles) To install an agent and c2 profile, you can use the `./mythic-cli install` script and point it to the address for an agent or c2 profile: ``` sudo ./mythic-cli install github https://github.com/MythicAgents/apfell ``` The hope is that by making this an explicit step will allow people to be more cognizent about which agents/c2 profiles they're installing, how much space they're using, and allow payload types/c2 profiles to be updated at a higher frequency than the Mythic server itself. The format required for these external agents/c2 profiles is documented and templated out for you here: [https://github.com/its-a-feature/Mythic\_External\_Agent](https://github.com/its-a-feature/Mythic_External_Agent). Simply mirror that folder structure and config file, then host your agent on github, and everybody will be able to install and leverage your code. If you're interested in creating new agents or c2 profiles for people to use, let me know on twitter, and I can add you to the appropriate organization so that you have full control over that project. The nice thing about having all of the agents/profiles aggregated under the same organization is that it makes it easier for people to find rather than a bunch of random github links. ## MythicMeta Organization In addition to moving all of the Payload Types and C2 Profiles into their own Mythic-based organizations on GitHub, a lot of the "meta" information for Mythic that was stored with it is now in its own Mythic organized called [MythicMeta](https://github.com/MythicMeta). Inside this organization you'll find the code for: * [Docker Templates](https://github.com/MythicMeta/Mythic_Docker_Templates) - This contains the code and files used to create the Docker Images that are used with the standard Mythic Agents. If you're curious what actually makes a `itsafeaturemythic/csharp_payoad:0.0.11` Docker image, this is where you'll find that. * [Payload Type PyPi Container](https://github.com/MythicMeta/Mythic_PayloadType_Container) - This is the code that's in the `mythic_payloadtype_container` PyPi package hosted on PyPi. * [C2 Profile PyPi Container](https://github.com/MythicMeta/Mythic_C2_Container) - This is the code that's in the `mythic_c2_container` PyPi package hosted on PyPi. * [Translator PyPi Container](https://github.com/MythicMeta/Mythic_Translator_Container) - This is the code that's in the `mythic_translator_container` PyPi package hosted on PyPi. * [Mythic Scripting](https://github.com/MythicMeta/Mythic_Scripting) - This is the code that's used for the `mythic` (and right now specifically the `mythic_rest`) scripting capabilities and hosted on PyPi. For any of these, if you don't want to leverage the standard Docker images, or if you want to turn your own VM into a supported container, feel free to leverage this code. ## Mythic-cli Mythic used to leverage a large number of bash scripts to accomplish the task of start/stoping docker-compose and the agent/c2 profiles, resetting the database, processing various configuration files, and more. While bash is on all Linux systems where Mythic can run, that doesn't mean that all of the additional support binaries exist (jq, realpath, openssl, docker, etc). This can result in a bit of a headache; plus, maintaining bash scripts is a nightmare. To get around this, Mythic now comes with a pre-compiled Golang binary, `mythic-cli`, with the source code available to all at the [Mythic\_CLI](https://github.com/MythicMeta/Mythic_CLI) repository. All of the documentation on this website should already be updated to show how to use the `mythic-cli` binary instead of the support scripts, but if you find a place that doesn't, be sure to report it. As part of this update, Mythic now leverages a single docker-compose file for all containers. Mythic used to leverage docker-compose for all of the core services and then regular `docker run` commands for the agents and c2 profiles. This worked initially, but now that more and more environment variables are randomized and configurable, there needs to be a way to centrally configure everything. So, starting with Mythic 2.2.4, all configuration happens within the `Mythic/.env` file and all containers are in the `Mythic/docker-compose.yml` file. When you install agents, Mythic will automatically parse and update this docker-compose file to add in the necessary information. Similarly, you can add/remove agents/c2 profiles at any time from this file via `mythic-cli {payload|c2} {add|remove} [name]`. If you already have agents installed that you want to register, the `add` command will allow you to update your docker-compose file without having to re-install your agent or c2 profile. You can also use the `mythic-cli {payload|c2} list` feature to show what containers exist within your docker-compose file and which ones exist on disk. This will take a little bit of time to get used to, but it will be easier for maintenance and expansion going forward than a bunch of bash scripts. ## C2 Profile Updates C2 profiles gained a new function, `opsec`, where they can take in all of the parameters an operator supplies when creating a payload and determine if they're safe or not. This is an optional function with more detail on [OPSEC checks for C2 profiles](/version-4.0/customizing/c2-related-development/server-side-coding/2.2.2-opsec-checks-for-c2-profiles) and the operator [overview](/version-4.0/operational-pieces/c2-profiles/opsec-checks). ## Payload Updates Payloads in Mythic got a few updates as well to make it easier for developers and analysis. ### deleted payloads On the Payloads page, if you delete a payload, then that payload can no longer be used to generate callbacks. If that payload tries to callback to Mythic, you'll get a warning in the UI and in the event feed letting you know that a deleted payload is trying to check in. This is helpful for if a payload gets "burned" and you want to make sure it can't flood your system with callbacks. ### rebuild payloads Once you've created a payload, on the Payloads page there is a new element in the actions dropdown for "trigger a new build". This will take that payload and task Mythic with generating it again. This is helpful when doing development so that you can quickly troubleshoot build errors without having to apply all of your same build settings each time. ### export/import payload builds If you want to take a specific build configuration and use it across Mythic installs or us it after you've done a `sudo ./mythic-cli database reset`, you can go to the Payloads page and select to export a payload configuration. This takes all the information as needed to generate a payload and saves it as a new JSON file through your browser. Then, at some time later, on the Payloads creation page, there's a new button on the top right to import a configuration. You can load in this JSON file and automatically trigger a new build of that payload. ### ephemeral payloads Sometimes it's helpful to see payload information for payloads that you didn't build manually. This can be for commands that might build new instances of payloads for spawning, lateral movement, or privilege escalation. These are "ephemeral" payloads within Mythic and are typically hidden from view. On the Payloads page, you can click a button to view ephemeral payloads so that you can see configurations, build stats, etc for these as well. Similarly, if you delete these then they can't be used to generate new callbacks. ### build\_stdout, build\_stderr Payloads can now track their stdout and stderr in addition to a build\_message. There are a lot of things that go into building dynamic payloads, so it's helpful to track stdout and stderr for later analysis or troubleshooting. This information can be seen from the Payloads page. This can be set during the `build` function as: ``` result = BuildResponse() result.build_stderr = "my stderr message" result.build_stdout = "my stdout message" result.build_message = "my normal message to the user" ``` ## Operator Updates ### password length All accounts within Mythic now must have a password that's at least 12 characters long. If you try to set a password that's shorter, then you'll get a warning message and the password setting will fail. ### Lockout & Unknown Users When a user ties to log in with a name that Mythic doesn't know, all operations will get a warning about it. If a user tries to log in 10 times unsuccessfully, their account will be locked and an admin account will need to re-enable it via the Settings tab in the top right. The initial admin account though can't be locked out (how else would anybody ever get in) - instead, this account goes into a throttle phase where you can only do one password attempt a minute. In both cases (throttle and lock out), all operations will get a notification that this is happening. ### Operator creation We removed the ability for operators to self-create an account via a "register" button and instead require an admin to pre-create accounts (or use scripting to do so) for all users. To do this via scripting, simply: ``` async def scripting(): # sample login mythic = mythic_rest.Mythic( username="mythic_admin", password="mythic_password", server_ip="192.168.53.128", server_port="7443", ssl=True, global_timeout=-1, ) print("[+] Logging into Mythic") await mythic.login() operator1 = await mythic.create_operator(mythic_rest.Operator(username="bob", password="mythic_password")) ``` ## Architecture Changes ### Multiple Workers One of the big issues we found for Mythic over time has been that with a large number of callbacks (200+) or with a medium number with a low sleep (like 15 agents at sleep 0), then the performance for Mythic (server and the UI) is pretty noticeably deteriorated. After digging into it, it turns out that Mythic was only ever using a single core due to how the event loop was leveraged. Mythic now spawns multiple worker processes and shares the load across the available CPUs. This presents a different set of problems from what we were doing before - for example, you can't cache requests or data in memory because that's not shared amongst the worker processes. So, to help with this, we added a small Redis database. This might seem weird that we now have two databases within Mythic - Redis and Postgres. The use cases and data stored on them is wildly different though. ### redis The Redis database is cleared each time Mythic starts and is used to hold temporary data that's needed by all of the different worker processes. Currently, this is two things: * JWT refresh tokens * SOCKS messages Access to this data needs to be super fast, but doesn't need to be persistent, which is why it's not stored in the Postgres database. ### socks There have been issues with the current SOCKS implementation within Mythic, so with the help of Thiago Mallart's pull request for Reverse Port Forwarding, updated the implementation within Mythic to not leverage an external binary, but instead do it all with threads in the main Mythic server. Once we put this implementation through more intensive testing, we will be pulling in Thiago's pull request for reverse port forwarding as well. ## Translation Containers One of the things Mythic strives to do is allowing an extensible and customizable framework for you to create an agent that functions however you want. While the current Mythic format allows you to generate an agent however you want, the messages that you use are still pretty heavily tied to Mythic's JSON format. Depending on your agent and language of choice, JSON might not be feasible. Even if JSON is feasible, you might not want to use Mythic's JSON messages. So, to help make it easier for your agents to do their own thing, we're introducing the idea of "Translation" containers. These containers simply act as a way to "translate" between your custom format and the JSON that Mythic needs. In addition to just doing a translation of messages, this container can also handle encryption, decryption, and generation of crypto keys. If you issue commands to your agent and don't want to adhere to Mythic's format for hooking into features (like the web browser), but do want to utilize that feature, you can use the `process_response` key in your post\_response messages ([Process Response](/version-4.0/customizing/payload-type-development/process-response)) and have your own custom messages sent back to your Command's Python file and then use Mythic's RPC functionality to register the same information. This really does give you the ability to do pretty much everything custom, while still hooking into Mythic. Translation containers can sit side-saddle with your Payload Type locally in Mythic as well as in your Github repository so that it's easy to bundle and install them together. More information about translation containers can be found on the [Translation Containers](/version-4.0/customizing/payload-type-development/translation-containers) page. ## Event Feed The event feed for Mythic got a bunch of updates to help with some of the issues people have faced recently. ### limit fetches There have been instances where people have tens of thousands of messages in the event feed, which was causing page loads to be really slow. So, Mythic now only fetches the latest 100 messages and has a button at the top to fetch the previous 100. This allows you to scroll back without having to load it all in at once. ### jump to next error Since all of the messages aren't loaded into the browser at once, it can be hard to find a warning message from Mythic. So, in addition to a button to fetch the previous 100 messages, there's a button to fetch the next most recent error. This makes it easy to find the unresolved errors while still making things much more performant in the browser. ### debug events When developing, it's often very helpful to see messages as they travel through Mythic. There are a lot of things going on between getting messages, decrypting, potentially sending to translation containers, processes requests, bundling it all back up, etc. If you pass in an environment variable of `MYTHIC_DEBUG=True` when starting Mythic, then all of these stages will send event messages to the event feed with context and information. This can be extremely helpful during development, but will be very overwhelming during production. ### grouped messages One of the issues we saw people face is an overwhelming number of alert messages. Specifically, when Mythic is too open to the internet and gets scanned, Mythic will report back that it can't find the agent messages in all of this scanning traffic. As an operator, you should still be notified that something is sending messages to you that Mythic can't process, but we don't want to bog down the system. So, Mythic will now "group" like messages. This only happens for "warning" messages, but instead of creating a new event feed entry and a new popup in the UI, Mythic will now increment a count assocaited with the message. If you mark this warning as "resolved" and you get another one of these messages, then you will get another event entry and another popup. ## Going Forward Updates There were a lot of updates for this Mythic update, and there were a few things put in place to support some of the updates going forward. ### GraphQL As Mythic expands and provides more context tracking and features, the back-end database will continue to expand. The current UI relies on a REST-style back-end. While this was easy to set up and useful initially, it means that we have to add a bunch more web routes as we add new features and makes maintaining it / testing it more tedious. If we need data in a different format or a different sub-section of data, we have to create new routes and expose them that way. Instead, if we just had a way to expose one endpoint and have the client describe the data it wants, then we can more easily add features to the UI without having to adjust the main Mythic server as well. This is where GraphQL comes into play. This takes a little bit to get used to, but Mythic now includes the Hasura docker container to expose parts of the Postgres database via a GraphQL engine. This has its own permission model and relies on the main Mythic server for authentication. ### Nginx As we continue to add more components to Mythic (Documentation container, Mythic server, GraphQL, etc), it's not practical to keep exposing multiple ports that you then need to lock down separately. Instead, we can open a single port externally and proxy connections back to all the components that need them. To accomplish this, we now include an Nginx docker container as a reverse proxy. You can still reach Mythic via the normal `https://mythic_ip:7443`, but now your connections are transparently proxied back to the documentation container and the graphql container for you. ### React UI Now that Mythic (formerly Apfell) has been out for a few years, I decided to take a look back at how the UI worked. I learned a lot over the years and decided that it was time to redo the UI. It currently is a mixture of Jinja2, Vue, JavaScript, Jquery, and requires adding new templates/routes to the Mythic Server in order to create new pages. It's not particularly easy to go through and trace data or update for people. So, to help with this (and to leverage the new GraphQL components), we're slowly adding a new UI via React. Using React makes the UI more extensible and makes it easier for other people to contribute. This is going to be a slow process, so for a while there will be two UIs available to you. If you browse to Mythic like normal, you'll see the old/current UI and would never know that there's a new UI as well. If you browse to `/new/login` then you'll be presented the login for the new UI and can start seeing where Mythic is going in the future. We've already started implementing some of our newer features in this React UI such as: * better Graph views of callbacks via dagre (built on d3) rather than the current manual D3 force directed graphs * representing some sleep info in the table view for callbacks * indicating if callbacks have direct routes to Mythic, if there are linked agents, and if there's still a route at all # Overview Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.1-greater-than-2.2-updates/agents-2.1.-greater-than-2.2.2 ## Collapsed Files The first big update for the payload type containers is to make things easier to update. To do this, many of the files within `agentName/mythic` are now in a PyPi package (`mythic-payloadtype-container`) hosted at ([https://github.com/MythicMeta/Mythic\_PayloadType\_Container](https://github.com/MythicMeta/Mythic_PayloadType_Container)). Specifically, your `agentName/mythic` folder will now look like: * agent\_functions/ * browser\_scripts/ * mythic\_service.py * payload\_service.sh * rabbitmq\_config.json All of those other files can be deleted. * If you are using a DockerImage from the `itsafeaturemythic` repo, update to the latest: * csharp\_payload==0.0.14 * python38\_payload==0.0.7 * xgolang\_payload==0.0.12 * leviathan\_payload==0.0.7 * If you're rolling your own, make sure you use the `mythic_payloadtype_container==0.0.45` PyPi package. ### mythic\_service.py This is the main file that kicks off and runs all of the container code for sending heartbeats and interacting with Mythic. This is now a super simple script: ``` #!/usr/bin/env python3 from mythic_payloadtype_container import mythic_service mythic_service.start_service_and_heartbeat(debug=False) ``` If you want more debugging information from this container, simply set `debug=True`. ### payload\_service.sh This file just changes to the right directory inside of the Docker container, makes sure that the new Mythic directory is in your Python environment path, and kicks off that service file we just covered: ``` #!/bin/bash cd /Mythic/mythic export PYTHONPATH=/Mythic:/Mythic/mythic python3.8 mythic_service.py ``` ## Dockerfile The Docker files and Docker images for Mythic agents have all been updated. Instead of housing all of this information inside of the main Mythic repo, these have all been moved to the MythicMeta organization under [Mythic\_Docker\_Templates](https://github.com/MythicMeta/Mythic_Docker_Templates). They all now include a python `requirements.txt` file so it's easy to see what software is needed if you want to create your own Docker image or turn a VM into the Mythic "container" for your agent. For agents, the requirements.txt is as follows: ``` dynaconf==3.1.4 aio-pika mythic-payloadtype-container==0.0.45 ``` The `mythic-payloadtype-container` version of `0.0.45` corresponds to container version 9. Mythic now tracks the range of supported versions for all of the containers that connect up to it. This makes it easier to determine if you've updated mythic but don't have updated containers or visa versa. ### dynaconf This is a super awesome addition that allows you to configure your PayloadType container with the `rabbitmq_config.json` file *OR* via environment variables of the same names, but prefixed with `MYTHIC_`. So, if your rabbitmq instance isn't on the same host as your payload type container (or VM), you could either: * Set the `host` key to the ip of your rabbitmq instance * Set a `MYTHIC_HOST` environment variable to the ip of your rabbitmq instance Either one of those will be pulled in (the environment configuration supersedes the json configuration). ## Building Payloads The next set of changes come to how you build your agent. Nothing major changed, just some additional features and conventions changed since we're using a PyPi package now. ### builder.py Your main agent defintion file in the `agentName/mythic/agent_functions/` (typically called builder.py) has some updates. Replace your previous import: ``` from PayloadBuilder import * ``` with a new set of imports: ``` from mythic_payloadtype_container.PayloadBuilder import * from mythic_payloadtype_container.MythicCommandBase import * ``` There are two new things for your agent class that extends the functionality of your `PayloadType` - mythic\_encrypts and translation\_container. #### mythic\_encrypts This will be covered more in the `Translation Container` section, but your agent can now handle its own encryption/decryption if it wants. To signal this to Mythic, add `mythic_encrypts = False` in the same set of parameters where you declare the agent name, extension, and author. #### translation\_container If you want to have your own message format, do your own encryption, or generate your own keys, you need to have a "translation container". This will be covered more in depth in the [`Translation Container`](/version-4.0/customizing/payload-type-development/translation-containers) section, but if you have one for your payload type, declare it here with the name of the container. For example, if my translation container will be called "translator", then I'd add `translation_container = "translator"` to the same set of parameters where you declare the agent name, extension, and author. There's a pre-existing Docker container you can use for this, `FROM itsafeaturemythic/python38_translator_container:0.0.3` or you can look at the corresponding GitHub repo ([https://github.com/MythicMeta/Mythic\_Docker\_Templates/tree/master/Docker\_Translation\_base\_files](https://github.com/MythicMeta/Mythic_Docker_Templates/tree/master/Docker_Translation_base_files)) to create your own. Translation containers, just like the other containers, have a simple `mythic_service.py` script to kick them off which consists of: ``` #!/usr/bin/env python3 from mythic_translator_container import mythic_service mythic_service.start_service_and_heartbeat(debug=False) ``` ### building #### C2 Parameters Nothing fundamentally has changed with parsing C2 Profile Parameters for your agent, but there's some additional components. You still get an array (`self.c2info`) where each entry is related to one of the c2 profiles that the user is trying to add to your payload. For each of those entries you can still call the `get_c2profile()` function to get information about the profile and `get_parameters.dict().items()` to iterate over each key,value pair of parameter values. This is where the change happens: **Crypto** When doing crypto with Mythic, there used to be a lot of hard coded components, such as needing a parameter name to be exactly `AESPSK`. Now, that is no longer the case. C2 Profiles can declare any parameter to be a crypto related one simply by putting `crypto=True` in that C2 Profile's python definition file. As the payload creator though, what this means for you is that you don't just get a single base64 string back. The point here is to make crypto more modular and expansive, so you might get matching encryption/decryption keys, you might get a public key and a private key, you might get blank values to use for plaintext, etc. To suppor this, if a C2 profile parameter has `crypto=True`, then the `value` you get when iterating will be a `dictionary`. This dictionary has three components: ``` { "value": "string of whatever the user supplied when creating the agent", "enc_key": "base64 of the encryption key associated with that value", "dec_key": "base64 of the decryption key associated with that value" } ``` What does this look like in practice? For the `http` profile, the crypto parameter gives the user a choice to select `aes256_hmac` or `none`. One of those two values will be the `value`, then there will either be `None` or the base64 of an aes256 key for the `enc_key` and `dec_key` components. You can then use these values however you need inside of your agent. **Arrays / Dictionaries** C2 Profiles can also provide a parameter type of `dictionary` which, when creating the agent, allows the operator to provide Key-Value pairs. This is useful for things like specifying Header values for HTTP (User-Agent, Host, etc). Instead of trying to deal with nested data structures for arrays vs single values in these dictionaries, the end result is an array of tuples. So, when creating your agent and looping through C2 parameter values, you might get an `array` of key-value pairs. **Example** Let's look at what this stuff all means with an example. For the `apfell` agent, stamping in C2 profile parameter values used to look like this: ``` for c2 in self.c2info: profile = c2.get_c2profile() c2_code = open( self.agent_code_path / "c2_profiles" / "{}.js".format(profile["name"]), "r", ).read() for key, val in c2.get_parameters_dict().items(): c2_code = c2_code.replace(key, val) all_c2_code += c2_code base_code = base_code.replace("C2PROFILE_HERE", all_c2_code) ``` Now that we have a "dictionary" type for our `http` Profile's `headers` and we have the new crypto components, let's see what the section of code now looks like: ``` for c2 in self.c2info: c2_code = "" try: profile = c2.get_c2profile() c2_code = open( self.agent_code_path / "c2_profiles" / "{}.js".format(profile["name"]), "r", ).read() for key, val in c2.get_parameters_dict().items(): if isinstance(val, dict): c2_code = c2_code.replace(key, val["enc_key"] if val["enc_key"] is not None else "") elif not isinstance(val, str): c2_code = c2_code.replace(key, json.dumps(val)) else: c2_code = c2_code.replace(key, val) except Exception as p: build_msg += str(p) all_c2_code += c2_code base_code = base_code.replace("C2PROFILE_HERE", all_c2_code) ``` Here we're checking if the value is an instance of `dict` which means we're looking at crypto information, we check if the value is a `str` which is the normal other data, and if it's neither of those, then it's the array of dictionaires for our header values. You can of course also check if the `key` matches the name of the C2 Profile Parameter values (`AESPSK` and `headers`) and do your check that way too. #### Build status The last piece that's updated about building is that you can now be more explicit about what's going on. Rather than only being able to report back success/failure and a single message (which can get really messy with debug output), you can now set the following in your `BuildResponse` object: * build\_message - this is a simple standard message you display to the user when things build correctly (either `myBuildResp.build_message = "congrats, new agent created"` or `myBuildResp.set_build_message("congrats, new agent created")`. * build\_stderr - this is error information if something goes wrong (either `myBuildResp.build_stderr = "compile error here"` or `myBuildResp.set_build_stderr("compile error here")`. * build\_stdout - this is helpful stdout information that you might not want to present to the user on success, but would be helpful to look at later on (either `myBuildResp.build_stdout = "additional debugging info here"` or `myBuildResp.set_build_stdout("additional debugging info here")`. ) The old style of payloads used `message` instead of `build_message`, so you will have to change that one. ## Commands There are a lot of small updates with commands that give us a lot of new features, so let's dive into those. These sections are talking about all of the individual command files you have in `agentName/mythic/agent_functions/commandName.py`. This section will use Apfell's `shell` command as an example. * 2.1 version - [https://github.com/its-a-feature/Mythic/blob/master/Payload\_Types/apfell/mythic/agent\_functions/shell.py](https://github.com/its-a-feature/Mythic/blob/master/Payload_Types/apfell/mythic/agent_functions/shell.py) * 2.2 version - [https://github.com/MythicAgents/apfell/blob/master/Payload\_Type/apfell/mythic/agent\_functions/shell.py](https://github.com/MythicAgents/apfell/blob/master/Payload_Type/apfell/mythic/agent_functions/shell.py) ### PyPi imports Just like with the payload building, we need to change our imports at the top. There are two main changes here. First, the `from CommandBase import *` needs to change to `from mythic_payloadtype_container.MythicCommandBase import *`. Secondly, if you were importing various files for RPC, such as `from MythicResponseRPC import *`, those have all been collapsed into a single file now, so you'd need to import `from mythic_payloadtype_container.MythicRPC import *`. ### CommandParameter Not much changed here, but there are a few things to call out: * CommandParameter's now have a `ui_position` attribute you can set to order your arguments in a specific way * The `name` attribute does *NOT* have to match the value of the args key. The `name` attribute is what's presented to the user for the short name of the parameter, the `description` is what's displayed when the user hovers over that short name, and the dictionary key that's associated with the `CommandParameter` object overall is what's used when sending information down to the agent. Let's take an example: ``` self.args = { "file": CommandParameter( name="Select a File", type=ParameterType.File, description="file to upload" ), } ``` In this case, the user would see in their popup modal `Select a File` with a file selection button. If they hovered their mouse over `Select a File`, they'd see the description, `file to upload`, and when the data goes down to the agent, the agent will get `{"file": "uuid here"}`. When interacting with these kinds of mis-matched names from the `create_tasking` function, you want to reference the dictionary key value, not the `name` value (i.e. `task.args.get_arg("file")`). * All the different kinds of Parameter types can be found on the Public Github [https://github.com/MythicMeta/Mythic\_PayloadType\_Container](https://github.com/MythicMeta/Mythic_PayloadType_Container). When using a parameter type of `ChooseOne` or `ChooseMultiple`, choices is an array of choices for the user. If your command needs you to pick from the set of commands (rather than a static set of values), then there are a few other components that come into play. If you want the user to be able to select any command for this payload type, then set `choices_are_all_commands` to True. Alternatively, you could specify that you only want the user to choose from commands that are already loaded into the callback, then you'd set `choices_are_loaded_commands` to True. As a modifier to either of these, you can set `choice_filter_by_command_attributes` to filter down the options presented to the user even more based on the parameters of the Command's attributes parameter. This would allow you to limit the user's list down to commands that are loaded into the current callback that support MacOS for example. An example of this would be: ``` CommandParameter(name="test name", type=ParameterType.ChooseMultiple, description="so many choices!", choices_are_all_commands=True, choice_filter_by_command_attributes={"supported_os": [SupportedOS.MacOS]}) ``` ### CommandOPSEC Just like how there's a `TaskArgments` class and a `CommandBase` class, there's a `CommandOPSEC` class now that you can implement for your commands. This will expand over time, but for now there are a few things you can do: * There are currently three tracked attributes about a command - `injection_method`, `process_creation`, and `authentication` which all are free-form text fields that you can set to help describe what all your command might be doing on host that's an OPSEC consideration * implement the `opsec_pre` and `opsec_post` functions. This is more detailed, so let's take the next few sections to walk through an example. Your instance of CommandOpsec, is then tied to your command in the same way your instance of TaskArgument is - simply add `opsec_class = ShellOPSEC` (but the name of your Subclass) to the same area where you have `cmd`, `needs_admin`, and `description` in your command class. #### opsec\_pre This function `async def opsec_pre(self, task: MythicTask)` if implemented, is called *before* a task's `create_tasking` function. The point of this function is to do some operational security pre-flight tests before passing execution on to your `create_tasking` function. Let's take an example - you're operating in an environment and are about to run a command that does spawn and inject. Before you do that command, you want to query what you know so far about the environment to see if there are any EDR products running that might alert on that activity. So, before even passing execution to the `create_tasking` function, in the `opsec_pre` function, you can use RPC calls back to Mythic to query information. In our example, let's query the `Process` table to see if we have any process data for the host where our task is running. We can do this with a simple RPC call: ``` processes = await MythicRPC().execute("search_database", task_id=task.id, table="process", host=task.callback.host) ``` `processes` now has a response object back from Mythic that holds some information: * processes.status - this is a MythicStatus value that indicates `Success` or `Error` for the RPC call overall * processes.error\_message - if the status is `MythicStatus.Error`, then this is populated with the error message * processes.response - this is the actual response we got back from Mythic. This will vary wildly with the function call that you did. Now that we have the basics, let's expand this example out to say that if we don't have any process data, we should block the function execution until we do have process data. If we do have processes, we should do another query to see if any of the processes match something dangerous, like `Microsoft Defender`: ``` async def opsec_pre(self, task: MythicTask): processes = await MythicRPC().execute("search_database", task_id=task.id, table="process", host=task.callback.host) if processes.status == MythicStatus.Success: if len(processes.response) == 0: task.opsec_pre_blocked = True task.opsec_pre_message = f"This spawns {self.process_creation} and there is no process data on the host yet." task.opsec_pre_message += "\nRun \"list_apps\" first to check for dangerous processes" task.opsec_pre_bypass_role = "operator" return else: processes = await MythicRPC().execute("search_database", task_id=task.id, table="process", name="Microsoft Defender", host=task.callback.host) if len(processes.response) > 0: task.opsec_pre_blocked = True task.opsec_pre_message = f"Microsoft Defender spotted on the host in running processes. Don't spawn commands this way" else: task.opsec_pre_blocked = True task.opsec_pre_message = f"Failed to query processes from Mythic:\n{processes}" ``` You'll notice that we are setting a few fields on the task depending on what we see: * task.opsec\_pre\_blocked = True - we set this if we want to stop execution here and report something back to the user * task.opsec\_pre\_message - this is the message we want to send to the user All of the RPC calls that are available during create\_tasking are available here as well. * task.opsec\_pre\_bypass\_role - we can set this to "operator" or "lead" based on who should be allowed to bypass this opsec issue. If a message is blocked, the task status in the UI will indicate it's blocked. When you click on the task status there will be two additional menu options - submit a bypass request and view the message. #### opsec\_post This is very similar to `opsec_pre`, except it happens *after* the `create_tasking` call. This is useful for when you're generating artifacts as part of your tasking (such as generating new DLLs) and want to make sure they're properly sanitized or obfuscated before allowing an agent to pick it up. All of the same components apply, it's just `opsec_post_*`. ### Command Attributes This will expand over time, but currently we're adding in an `attributes` component to Commands that describes non-opsec related attributes. Currently, this only includes two things: if a command is spawn\_and\_injectable and what kind of operating systems the command supports. This looks like the following: ``` attributes = CommandAttributes( spawn_and_injectable=True, supported_os=[SupportedOS.MacOS] ) ``` The `supported_os` attribute lists the same supported OS types as the Payload Type. Most of the time, these two will match up; however, if your agent can compile to multiple different operating systems, this is one way to make it so that during payload creation, the user can only see the commands that are associated with the kind of payload they're trying to make. The spawn\_and\_injectable variable helps provide some quality of life to operators in case they try to inject a command like "exit" or "cd" into a remote process, which doesn't really make sense. ### supported\_ui\_features There are a bunch of features within the Mythic UI that look for specific agent functions to call. Historically, this was indicated with a series of boolean values - `is_exit`, `is_process_list`, etc. That was fine initially, but doesn't make it easy to expand. So, all of these are now grouped up into a new attribute called `ui_features` in a way that's more explicit about *where* a function will be referenced. These are now mapped as follows: ``` is_exit = True -> supported_ui_features = ["callback_table:exit"] is_file_browse = True -> supported_ui_features = ["file_browser:list"] is_process_list = True -> supported_ui_features = ["process_browser:list"] is_download_file = True -> supported_ui_features = ["file_browser:download"] is_remove_file = True -> supported_ui_features = ["file_browser:remove"] is_upload_file = True -> supported_ui_features = ["file_browser:upload"] ``` This also more easily allows us to expand this going forward for future UI elements and allows us to eventually leverage a single command in multiple areas more explicitly. If none of those `is_*` attributes are `True`, then you don't even need to supply the `supported_ui_features` attribute. This now allows us to go from: ``` is_exit = False is_file_browse = True is_process_list = False is_download_file = False is_remove_file = False is_upload_file = False ``` to ``` supported_ui_features = ["file_browser:list"] ``` which is way more descriptive about what and where the command is used. ### create\_tasking The `create_tasking` function got a few updates as well. Firstly, all of the calls for RPC functionality has changed. It used to be the case where you had to know which RPC function you want to execute, you had to know which RPC file had it, and you had to know all of the parameters for it. This gets complicated fast, and there isn't always a clear location for a function. For example, where would a `search_database` function go? To help with this, there is now a single RPC file, `from mythic_payloadtype_container.MythicRPC import *` that has all of the functionality within it. Every function call will be of the form: ``` response = await MythicRPC().execute("function name", argument1=val, argument2=val, ...etc) ``` You can programmatically get all of the available functions, their prototypes, and their docstrings by running the `get_functions` function. More information and all of the current 2.2.2 functions can be found on the [MythicRPC](/version-4.0/updating/mythic-2.1-greater-than-2.2-updates/agents-2.1.-greater-than-2.2.2/mythicrpc) page: ``` from mythic_payloadtype_container.MythicRPC import * import sys resp = await MythicRPC().get_functions() print(resp.response) sys.stdout.flush() ``` This will go through to query the Mythic server for all of the functions that are exposed via RPC and give detailed information for them. #### stdout/stderr For each task, you can now track the stdout and stderr for later reference. Simply set it via: ``` task.stdout = "something here" task.stderr = "my error messages here" ``` Then, you can see this via the UI when you select a task's status. This is helpful when you're doing commands that might result in additional compilation or analysis (such as load creating new modules). #### display\_params For the agent and for Mythic, using structured arguments and output is extremely beneficial. However, from the operator standpoint, this is hard to display, hard to quick glance at, and quickly bloats the screen. To help with this, tasks can set their own `display_params` by simply doing: ``` task.display_params = task.args.get_arg("my arg") + " some other words" ``` Then, once your `create_tasking` function returns, the commandline information that the operator sees is updated to this new custom value. If you select the status for the task though, you can select to "view all parameters" - this will create a new popup with information for three different stages of parameters: ``` original params - this is what's sent to the Mythic server when issuing a task, whatever form that happens to be display params - this is the same as original params by default and changes if you update it via task.display_params final params - this is what actually gets sent down to the agent (this is hepful to see in case you do some manipluation or setting of parameters via the argument parsing or create_tasking) ``` ### process\_response The `process_response` function is now finally callable within the Command files. The point of this function is to have a custom, programmatic execution based on the output of a command. When reporting data back from your agent, in your `post_response` array (the same place you'd set `user_output`), you can specify `process_response` with whatever data you want. This is then wrapped up with the task information into an `AgentResponse` object with two attributes: * task - this is just the same task information you get in `create_tasking` * response - this is the data you put in the `process_response` key. An example of using this to update a callback's sleep information is: ``` async def process_response(self, response: AgentResponse): resp = await MythicRPC().execute("update_callback", sleep_info=response.response) ``` This function also has access to all of the same RPC functionality that the `create_tasking` has. Here we're simply updating the callback's `sleep_info` with the result of the response. This allows us to programmatically update it based on if the agent was successful or not, without requiring Mythic to expose a custom response attribute for this field. ### Dynamic Parameter Values [Dynamic Parameter Values](/version-4.0/customizing/payload-type-development/dynamic-parameter-values) ### Sub-Tasking / Task Callbacks [Sub-tasking](/version-4.0/customizing/payload-type-development/sub-tasking-task-callbacks) ### Tags [Tags](/version-4.0/operational-pieces/tags) ### Script\_Only Commands To go along with the sub-tasking and task callback functionality, you might run into scenarios where you want to provide some sort of dynamic check or execution within the Command file, but don't it to actually be a task that gets sent down to the agent. You can now specify `script_only=True` in your Command file and this will be exactly the case. Commands that are marked as `script_only=True` will NOT appear when you go to build a payload because these commands are transparent to your agent. They WILL appear in the type hints when you start typing commands though and they appear in the list of full available commands when you view metadata about a callback. You might be wondering why this is useful? Consider the following: `psexec` is a command you want to implement in your agent, but you implement this functionality manually rather than simply running the Microsoft psexec binary. You can create a `script_only` `psexec` command that when a user types it, will spin of further sub-tasks to check that the computer is reachable, that the port is open, that you have access, copies over the file, creates the service, then cleans it all up. The `psexec` command in that case is kind of like a conductor that controls all the other tasks, switches based on success/error for each sub-task, and ultimately accomplishes the task dynamically without needing the command itself to be a compiled specific task in your agent. # MythicRPC Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.1-greater-than-2.2-updates/agents-2.1.-greater-than-2.2.2/mythicrpc ## MythicRPC in 2.2.7+ The RPC functionality within Mythic as of 2.2.7 is more dynamic, allowing more functionality to be added to the back-end and automatically usable by the Payload Type containers without requiring new PyPi packages or new Docker images. To facilitate this, you can always get the latest RPC available functionality within your tasking via: ``` from mythic_payloadtype_container.MythicRPC import * import sys async def create_tasking(self, task: MythicTask) -> MythicTask: resp = await MythicRPC().get_functions() print(resp.response) sys.stdout.flush() return task ``` That will print out all of the information for the available functions if you're ever in doubt about what's available or how to call the functions. When you find a function you want to call, you do it as follows: ``` file_resp = await MythicRPC().execute("create_file", task_id=task.id, file=base64.b64encode(task.args.get_arg("file_id")).decode(), saved_file_name=original_file_name, delete_after_fetch=False, ) ``` where you always call `await MythicRPC().execute` with the first parameter being the name of the function to call and all of the other arguments being passed in like normal function arguments. ## 2.3.0+ Functions The function set is moving towards a standard nomenclature - `create_*` for when you want to create/register/add something to the database, `get_*` for when you want to fetch something from the database, and `delete_*` when you want to remove something from the database or mark it as deleted. The current set of functionality for 2.2.8 is as follows: ``` create_file(task_id: int, file: str, delete_after_fetch: bool = True, saved_file_name: str = None, is_screenshot: bool = False, is_download: bool = False, remote_path: str = None, host: str = None) -> dict Creates a FileMeta object in Mythic's database and writes contents to disk with a random UUID filename. This file can then be fetched via the returned file UUID. :param task_id: The ID number of the task performing this action (task.id) :param file: The base64 contents of the file to register :param delete_after_fetch: Should Mythic delete the file from disk after the agent fetches it. This also marks the file as deleted in the UI. This is useful if the file is a temporary file that doesn't necessarily need long-term tracking within Mythic. :param saved_file_name: The name of the file (if none supplied, a random UUID4 value will be used) :param is_screenshot: Is this file a screenshot reported by the agent? If so, this will cause it to show up in the screenshots page. :param is_download: Is this file the result of downloading something from the agent? If so, this will cause it to show up in the Files page under Downloads :param remote_path: Does this file exist on target? If so, provide the full remote path here :param host: If this file exists on a target host, indicate it here in conjunction with the remote_path argument :return: Dict of a FileMeta object Example: this takes two arguments - ParameterType.String for `remote_path` and ParameterType.File for `file` async def create_tasking(self, task: MythicTask) -> MythicTask: try: original_file_name = json.loads(task.original_params)["file"] if len(task.args.get_arg("remote_path")) == 0: task.args.add_arg("remote_path", original_file_name) elif task.args.get_arg("remote_path")[-1] == "/": task.args.add_arg("remote_path", task.args.get_arg("remote_path") + original_file_name) file_resp = await MythicRPC().execute("create_file", task_id=task.id, file=base64.b64encode(task.args.get_arg("file")).decode(), saved_file_name=original_file_name, delete_after_fetch=False, ) if file_resp.status == MythicStatus.Success: task.args.add_arg("file", file_resp.response["agent_file_id"]) task.display_params = f"{original_file_name} to {task.args.get_arg('remote_path')}" else: raise Exception("Error from Mythic: " + str(file_resp.error)) except Exception as e: raise Exception("Error from Mythic: " + str(sys.exc_info()[-1].tb_lineno) + str(e)) return task ``` ``` get_file(task_id: int = None, callback_id: int = None, filename: str = None, limit_by_callback: bool = True, max_results: int = 1, file_id: str = None, get_contents: bool = True) -> dict Get file data and contents by name (ex: from create_file and a specified saved_file_name parameter). The search can be limited to just this callback (or the entire operation) and return just the latest or some number of matching results. :param task_id: The ID number of the task performing this action (task.id) - if this isn't provided, the callback id must be provided :param callback_id: The ID number of the callback for this action - if this isn't provided, the task_id must be provided :param filename: The name of the file to search for (Case sensitive) :param file_id: If no filename specified, then can search for a specific file by this UUID :param limit_by_callback: Set this to True if you only want to search for files that are tied to this callback. This is useful if you're doing this as part of another command that previously loaded files into this callback's memory. :param max_results: The number of results you want back. 1 will be the latest file uploaded with that name, -1 will be all results. :param get_contents: Boolean of if you want to fetch file contents or just metadata :return: An array of dictionaries representing the FileMeta objects of all matching files. When "get_contents" is True, each entry in this array will also have a "contents" key with the base64 representation of the associated file if it hasn't been deleted, or None if it has. For an example- resp = await MythicRPC().execute("get_file", task_id=task.id, filename="myAssembly.exe") resp.response <--- this is an array resp.response[0] <--- this is the most recently registered matching file where filename="myAssembly.exe" resp.response[0]["filename"] <-- the filename of that first result resp.response[0]["contents"] <--- the base64 representation of that file All of the possible dictionary keys are available at https://github.com/its-a-feature/Mythic/blob/master/mythic-docker/app/database_models/model.py for the FileMeta class ``` ``` update_file(file_id: str, comment: str = None, delete_after_fetch: bool = None, contents: bytes = None, filename: str = None) -> dict: Given a file identifier, update certain attributes of the file. :param file_id: This is the string UUID identifier for the file :param comment: If you want to update the comment on the file, supply the text here :param delete_after_fetch: If you want this file to be deleted after an agent fetches it, set this to True. It's false by default. :param contents: Supply the raw bytes of the file if you want to update the contents. :param filename: Supply a new filename for the file :return: Success or error code ``` ``` get_payload(payload_uuid: str, get_contents: bool = True) -> dict Get information about a payload and its contents :param payload_uuid: The UUID for the payload you're interested in :param get_contents: Whether or not you want to fetch the contents of the file or just the metadata :return: dictionary representation of the Payload object Example: async def create_tasking(self, task: MythicTask) -> MythicTask: try: gen_resp = await MythicRPC().execute("create_payload_from_uuid", task_id=task.id, payload_uuid=task.args.get_arg("template")) if gen_resp.status == MythicStatus.Success: # we know a payload is building, now we want it while True: resp = await MythicRPC().execute("get_payload", payload_uuid=gen_resp.response["uuid"]) if resp.status == MythicStatus.Success: if resp.response["build_phase"] == "success": task.args.add_arg("template", resp.response["file"]["agent_file_id"]) task.display_params = f"new Apfell payload ({resp.response['uuid']}) with description {resp.response['tag']}" break elif resp.response["build_phase"] == "error": raise Exception( "Failed to build new payload: " + str(resp.error) ) else: await asyncio.sleep(1) if resp.status == MythicStatus.Error: raise Exception("Failed to get information about new payload:\n" + resp.error) else: raise Exception("Failed to generate new payload:\n" + gen_resp.error) except Exception as e: raise Exception("Error trying to call RPC:\n" + str(e)) return task ``` ``` search_payloads(callback_id: int, payload_types: [str] = None, include_auto_generated: bool = False, description: str = "", filename: str = "", build_parameters: dict = None) -> dict: """ Search payloads based on payload type, if it was auto generated, the description, the filename, or build parameter values. Note: This does not search payloads that have been deleted. :param callback_id: The ID of the callback this search is for, this is what's used to limit your search to the right operation. :param payload_types: The names of the associated payload type if you want to restrict results :param include_auto_generated: Boolean if you want to include payloads that were automatically generated as part of tasking :param description: If you want to search for payloads with certain information in their description, this functions like an igrep search :param filename: If you want to search for payloads with certain filenames, this functions like an igrep search :param build_parameters: If you want to limit your search based on certain build parameters (maybe shellcode for example), then you can specify this dictionary of {"agent name": {"build_param_name": "build_param_value"}} :return: An array of dictionaries where each entry is one matching payload. Each dictionary entry contains the following: uuid - string description -string operator - string creation_time - string payload_type - string operation - string wrapped_payload - boolean (true if this payload wraps another payload) deleted - boolean build_container - string build_phase - string build_message - string build_stderr - string build_stdout - string callback_alert - boolean (true if this payload will attempt to hit the operation's webhook when a new callback is generated) auto_generated - boolean (true if this payload is auto generated by a task) task - dictionary of information about the associated task file - dictionary of information about the associated file os - string """ ``` ``` encrypt_message(message: dict, target_uuid: str, c2_profile_name: str): Given a dictionary agent message, submit it to Mythic to encrypt with a target callback/payload's encryption keys :param message: the dictionary message :param target_uuid: the UUID of the payload/stager/callback that will receive the encrypted message :param c2_profile_name: the name of the c2 profile that this message will be sent over :return: The final base64 and encrypted message ``` ``` decrypt_message(message: str, c2_profile_name: str): Given an encrypted message from an agent, decrypt it based on the C2 profile that received it :param message: The base64 of the message from an agent :param c2_profile_name: the name of the c2 profile where this message came from :return: the dictionary representation of the message for Mythic ``` ``` get_commands(callback_id: int = None, loaded_only: bool = False, payload_type_name: str = None, commands: [str] = None, os: str = None): Get an array of dictionaries of all the possible commands for the specified callback or payload type :param callback_id: the id of the callback in question :param loaded_only: specify this as True to only include commands currently loaded into this callback :param payload_type_name: specify this to fetch all possible commands for a specific payload type :param commands: specify an array of command names along with the payload_type_name to fetch information about only the listed commands for the specified payload type :param os: Specify the OS that's associated with the payload_type_name so that commands can be filtered :return: an array of dictionaries representing all of the requested commands for that payload type. When returning all possible commands for this callback, commands are still filtered by their supported_os attributes ``` ``` add_commands_to_payload(payload_uuid: str, commands: [str]): Register additional commands that are in the payload. This is useful if a user selects command X to include in a payload, but command X needs command Y. A common example would be if command X is a script_only command or will end up delegating additional commands. :param payload_uuid: The UUID of the payload that you're adding commands to. :param commands: An array of command names that should be added to this payload. :return: Success or Error ``` ``` get_tasks(task_id: int, host: str = None) -> dict Get all of the currently running tasks on the current host or on a specific host :param task_id: The ID number of the task performing this action (task.id) :param host: The name of the host to check for running tasks :return: An array of dictionaries representing the tasks running ``` ``` get_responses(task_id: int) -> dict For a given Task, get all of the user_output, artifacts, files, and credentials that task as created within Mythic :param task_id: The TaskID you're interested in (i.e. task.id) :return: A dictionary of the following format: { "user_output": array of dictionaries where each dictionary is user_output message for the task, "artifacts": array of dictionaries where each dictionary is an artifact created for the task, "files": array of dictionaries where each dictionary is a file registered as part of the task, "credentials": array of dictionaries where each dictionary is a credential created as part of the task. } ``` ``` create_payload_from_uuid(task_id: int, payload_uuid: str, generate_new_random_values: bool = True, new_description: str = None, remote_host: str = None, filename: str = None) -> dict Given an existing Payload UUID, generate a new copy with a potentially new description, new filename, new random values, and specify that it'll exist on a certain host. This is useful for spawn or lateral movement tasks where you want to potentially change up IOCs and provide new, more informative, descriptions for callbacks. :param task_id: The ID number of the task performing this action (task.id) :param payload_uuid: The UUID of the payload we're interested in :param generate_new_random_values: Set this to True to generate new random values for C2 Profile parameters that are flagged as randomized :param new_description: Provide a custom new description for the payload and callbacks associated from it. If you don't provide one, a generic one will be generated :param remote_host: Indicate the hostname of the host this new payload is deployed to. If one isn't specified, you won't be able to link to it without first telling Mythic that this payload exists on a certain host via the Popup Modals. :param filename: New filename for the payload. If one isn't supplied, a random UUID will be generated :return: dictionary representation of the payload that was created Example: async def create_tasking(self, task: MythicTask) -> MythicTask: try: gen_resp = await MythicRPC().execute("create_payload_from_uuid", task_id=task.id, payload_uuid=task.args.get_arg("template")) if gen_resp.status == MythicStatus.Success: # we know a payload is building, now we want it while True: resp = await MythicRPC().execute("get_payload", payload_uuid=gen_resp.response["uuid"]) if resp.status == MythicStatus.Success: if resp.response["build_phase"] == "success": task.args.add_arg("template", resp.response["file"]["agent_file_id"]) task.display_params = f"new Apfell payload ({resp.response['uuid']}) with description {resp.response['tag']}" break elif resp.response["build_phase"] == "error": raise Exception( "Failed to build new payload: " + str(resp.error) ) else: await asyncio.sleep(1) if resp.status == MythicStatus.Error: raise Exception("Failed to get information about new payload:\n" + resp.error) else: raise Exception("Failed to generate new payload:\n" + gen_resp.error) except Exception as e: raise Exception("Error trying to call RPC:\n" + str(e)) return task ``` ``` create_processes(task_id: int, processes: dict) -> dict Create processes in bulk. The parameters in the "processes" dictionary are the same as those in the `create_process` RPC call. :param task_id: The ID number of the task performing this action (task.id) :param processes: Dictionary of the processes you want to create - the key value pairs are the same as the parameters to the `create_process` RPC call. :return: Success or Error (nothing in the `response` attribute) ``` ``` create_process(task_id: int, host: str, process_id: int, parent_process_id: int = None, architecture: str = None, name: str = None, bin_path: str = None, user: str = None, command_line: str = None, integrity_level: int = None, start_time: str = None, description: str = None, signer: str = None) -> dict Create a new process within Mythic. :param task_id: The ID number of the task performing this action (task.id) :param host: The host where this process exists :param process_id: The process ID :param parent_process_id: The process's parent process ID :param architecture: The architecture for the process (x86, x64, arm, etc) :param name: The name of the process :param bin_path: The path to the binary that's executed :param user: The user context that the process is executing :param command_line: The command line that's spawned with the process :param integrity_level: The integrity level of the process :param start_time: When the process started :param description: The description of the process :param signer: The process' signing information :return: Success or Error (nothing in the `response` attribute) ``` ``` create_artifact(task_id: int, artifact_type: str, artifact: str, host: str = None) -> dict Create a new artifact for a certain task on a host :param task_id: The ID number of the task performing this action (task.id) :param artifact_type: What kind of artifact is this (Process Create, File Write, etc). If the type specified doesn't exist, it will be created :param artifact: The actual artifact that was created :param host: Which host the artifact was created on. If none is provided, the current task's host is used :return: Success or error (nothing in the `response` attribute) ``` ``` create_keylog(task_id: int, keystrokes: str, user: str = None, window_title: str = None) -> dict Create a new keylog entry in Mythic. :param task_id: The ID number of the task performing this action (task.id) :param keystrokes: The keys that are being registered :param user: The user that performed the keystrokes. If you don't supply this, "UNKNOWN" will be used. :param window_title: The title of the window where the keystrokes came from. If you don't supply this, "UNKNOWN" will be used. :return: Success or Error (nothing in the `response` attribute) ``` ``` create_output(task_id: int, output: str) -> dict Add a message to the output for a task that the operator can see :param task_id: The ID number of the task performing this action (task.id) :param output: The message you want to send. :return: Status of if you successfully posted or not (nothing in the `response` attribute) Example: async def create_tasking(self, task: MythicTask) -> MythicTask: resp = await MythicRPC().execute("create_output", task_id=task.id, output="hello") if resp.status != MythicStatus.Success: task.status = MythicStatus.Error raise Exception(resp.error) return task ``` ``` create_event_message(task_id: int, message: str, warning: bool = False) -> dict Create a message in the Event feed within the UI as an info message or as a warning :param task_id: The ID number of the task performing this action (task.id) :param message: The message you want to send :param warning: If this is True, the message will be a "warning" message :return: success or error (nothing in the `response` attribute) ``` ``` create_credential(task_id: int, credential_type: str, account: str, realm: str, credential: str, metadata: str = '', comment: str = None) -> dict Create a new credential within Mythic to be leveraged in future tasks :param task_id: The ID number of the task performing this action (task.id) :param credential_type: The type of credential we're storing (plaintext, hash, ticket, certificate, token) :param account: The account associated with the credential :param realm: The realm for the credential (sometimes called the domain) :param credential: The credential value itself :param metadata: Any additional metadata you want to store about the credential :param comment: Any comment you want to store about it the credential :return: Success or Error (nothing in the `response` attribute) ``` ``` create_file_browser(task_id: int, host: str, name: str, full_path: str, permissions: dict = None, access_time: str = '', modify_time: str = '', comment: str = '', is_file: bool = True, size: str = '', success: bool = True, files: [] = None, update_deleted: bool = False) -> dict Add file browser content to the file browser user interface. :param task_id: The ID number of the task performing this action (task.id) :param host: Which host this data is from (useful for remote file listings) :param name: Name of the file/folder that was listed :param full_path: Full path of the file/folder that was listed (useful in case the operator said to ls `.` or a relative path) :param permissions: Dictionary of permissions. The key/values here are completely up to you and are displayed as key/value pairs in the UI :param access_time: String representation of when the file/folder was last accessed :param modify_time: String representation of when the file/folder was last modified :param comment: Any comment you might want to add to this file/folder :param is_file: Is this a file? :param size: Size of the file (can be an int or something human readable, like 10MB) :param success: True/False if you successfully listed this file. A False value (like from an access denied) will appear as a red X in the UI :param files: Array of dictionaries of information for all of the files in this folder (or an empty array of this is a file). Each dictionary has all of the same pieces of information as the main folder itself. :param update_deleted: True or False indicating if this file browser data should be used to automatically update deleted files for the listed folder. This defaults to false, but if set to true and there are files that Mythic knows about for this folder that the passed-in data doesn't include, it will be marked as deleted. :return: success or error (nothing in the `response` attribute) ``` ``` create_payload_on_host(task_id: int, payload_uuid: str, host: str) -> dict Register within Mythic that the specified payload exists on the specified host as a result of this tasking :param task_id: The ID number of the task performing this action (task.id) :param payload_uuid: The payload that will be associated with the host :param host: The host that will have the payload on it :return: success or error (nothing in the `response` attribute) ``` ``` create_logon_session(task_id: int, LogonId: int, host: str = None, **kwargs) -> dict Create a new logon session for this host :param task_id: The ID number of the task performing this action (task.id) :param LogonId: The integer logon identifier value that uniquely identifies this logon session on this host :param host: The host where this logon session exists :param kwargs: The `Mythic/mythic-docker/app/database_models/model.py` LogonSession class has all of the possible values you can set when creating/updating logon sessions. There are too many to list here individually, so a generic kwargs is specified. :return: Success or Error (nothing in the `response` attribute) ``` ``` create_callback_token(task_id: int, TokenId: int, host: str = None) -> dict Associate a token with a callback for usage in further tasking. :param task_id: The ID number of the task performing this action (task.id) :param TokenId: The token you want to associate with this callback :param host: The host where the token exists :return: Success or Error (nothing in the `response` attribute) ``` ``` create_token(task_id: int, TokenId: int, host: str = None, **kwargs) -> dict Create or update a token on a host. The `TokenId` is a unique identifier for the token on the host and is how Mythic identifies tokens as well. A token's `AuthenticationId` is used to link a Token to a LogonSession per Windows documentation, so when setting that value, if the associated LogonSession object doesnt' exist, Mythic will make it. :param task_id: The ID number of the task performing this action (task.id) :param TokenId: The integer token identifier value that uniquely identifies this token on this host :param host: The host where the token exists :param kwargs: The `Mythic/mythic-docker/app/database_models/model.py` Token class has all of the possible values you can set when creating/updating tokens. There are too many to list here individually, so a generic kwargs is specified. :return: Dictionary representation of the token created ``` ``` delete_token(TokenId: int, host: str) -> dict Mark a specific token as "deleted" on a specific host. :param TokenId: The token that should be deleted :param host: The host where this token exists :return: success or error (nothing in the `response` attribute) ``` ``` create_agentstorage(unique_id: str, data: bytes): Allow Payload Types and Translation containers to store arbitrary data within the database that doesn't fit somewhere else in Mythic's current schema :param unique_id: A unique string identifier :param data: :return: {"unique_id": "unique id here", "data": "base64 of data here"} ``` ``` get_agentstorage(unique_id: str): Allow Payload Types and Translation containers to fetch arbitrary data within the database that doesn't fit somewhere else in Mythic's current schema :param unique_id: A unique string identifier :return: {"unique_id": "unique id here", "data": "base64 of data here"} ``` ``` delete_agentstorage(unique_id: str): Allow Payload Types and Translation containers to delete arbitrary data within the database that doesn't fit somewhere else in Mythic's current schema :param unique_id: A unique string identifier :return: Success or Error ``` ``` delete_file_browser(task_id: int, file_path: str, host: str = None) -> dict Mark a file in the file browser as deleted (typically as part of a manual removal via a task) :param task_id: The ID number of the task performing this action (task.id) :param file_path: The full path to the file that's being removed :param host: The host where the file existed. If you don't specify a host, the callback's host is used :return: Success or Error (nothing in the `response` attribute) ``` ``` delete_logon_session(LogonId: int, host: str) -> dict Mark a specified logon session as "deleted" on a specific host :param LogonId: The Logon Session that should be deleted :param host: The host where the logon session used to be :return: Success or Error (nothing in the `response` attribute) ``` ``` delete_callback_token(task_id: int, TokenId: int, host: str = None) -> dict Mark a callback token as no longer being associated :param task_id: The ID number of the task performing this action (task.id) :param TokenId: The Token you want to disassociate from the task's callback :param host: The host where the token exists :return: Success or Error (nothing in the `response` attribute) ``` ``` update_callback(task_id: int, user: str = None, host: str = None, pid: int = None, ip: str = None, external_ip: str = None, description: str = None, integrity_level: int = None, os: str = None, architecture: str = None, domain: str = None, extra_info: str = None, sleep_info: str = None) -> dict Update this task's associated callback data. :param task_id: The ID number of the task performing this action (task.id) :param user: The new username :param host: The new hostname :param pid: The new process identifier :param ip: The new IP address :param external_ip: The new external IP address :param description: The new description :param integrity_level: The new integrity level :param os: The new operating system information :param architecture: The new architecture :param domain: The new domain :param extra_info: The new "extra info" you want to store :param sleep_info: The new sleep information for the callback :return: Success or error (nothing in the `response` attribute) ``` ``` update_task_status(task_id: int, status: str, completed: bool = None): Update a task's status to a custom value and optionally mark a task as completed :param task_id: The task you want to update (i.e. task.id in you create_tasking) :param status: The string value of the status you want to set :param completed: Optional boolean value to mark the task as completed :return: Status indicating success or error on if the task was updated or not. ``` ``` search_database(table: str, task_id: int = None, callback_id: int = None, **kwargs) -> dict Search the Mythic database for some data. Data is searched by regular expression for the fields specified. Because the available fields depends on the table you're searching, that argument is a generic python "kwargs" value. :param task_id: The ID number of the task performing this action (task.id) - if this isn't supplied, callback_id must be supplied :param callback_id: The ID number of the callback performing this action - if this isn't supplied, task_id must be supplied :param table: The name of the table you want to query. Currently only options are: process, token, file_browser. To search files (uploads/downloads/hosted), use `get_file` :param kwargs: These are the key=value pairs for how you're going to search the table specified. For example, searching processes where the name of "bob" and host that starts with "spooky" would have kwargs of: name="bob", host="spooky*" :return: an array of dictionaries that represent your search. If your search had no results, you'll get back an empty array ``` ``` control_socks(task_id: int, port: int, start: bool = False, stop: bool = False) -> dict Start or stop SOCKS 5 on a specific port for this task's callback :param task_id: The ID number of the task performing this action (task.id) :param port: The port to open for SOCKS 5 :param start: Boolean for if SOCKS should start :param stop: Boolean for if SOCKS should stop :return: Status message of if it completed successfully (nothing in the `response` attribute) Example: async def create_tasking(self, task: MythicTask) -> MythicTask: if task.args.get_arg("action") == "start": resp = await MythicRPC().execute("control_socks", task_id=task.id, start=True, port=task.args.get_arg("port")) if resp.status != MythicStatus.Success: task.status = MythicStatus.Error raise Exception(resp.error) else: resp = await MythicRPC().execute("control_socks", task_id=task.id, stop=True, port=task.args.get_arg("port")) if resp.status != MythicStatus.Success: task.status = MythicStatus.Error raise Exception(resp.error) return task ``` ``` update_loaded_commands(task_id: int, commands: [str], add: bool = None, remove: bool = None): Add or Remove loaded commands for the callback associated with task_id :param task_id: The task doing the modifications :param commands: The list of command names to add/remove :param add: Boolean set to True if you want to add the commands to the callback associated with task_id :param remove: Boolean set to True if you want to remove teh commands from the callback assocaited with task_id :return: Status for success or error ``` ``` create_subtask(parent_task_id: int, command: str, params_string: str = None, params_dict: dict = None, files: dict = None, subtask_callback_function: str = None, subtask_group_name: str = None, tags: [str] = None, group_callback_function: str = None) -> dict: Issue a new task to the current callback as a child of the current task. You can use the "subtask_callback_function" to provide the name of the function you want to call when this new task enters a "completed=True" state. If you issue create_subtask_group, the group name and group callback functions are propagated here. You MUST provide params_string or params_dict to this function, but you don't provide both. :param parent_task_id: The id of the current task (task.id) :param command: The name of the command you want to use :param params_string: The string parameters you want to issue to that command (this gets passed to the command's parse_arguments function) :param params_dict: THe dictionary of parameters you want to issue to that command (this will get converted into a string and passed to that command's parse_arguments function) :param files: If you want to pass along a file to the task, provide it here (example provided) :param subtask_callback_function: The name of the function to call on the _parent_ task when this function exits :param subtask_group_name: An optional name of a group so that tasks can share a single callback function :param tags: A list of strings of tags you want to apply to this new task :param group_callback_function: If you're grouping tasks together, this is the name of the shared callback function for when they're all in a "completed=True" state :return: Information about the task you just created If the command for your subtask normally takes a parameter of type File, then we need to do something a little bit differently for you to pass that along to the subtask. Let's say you want to call the "upload" command which takes a `path` argument which is a string and a `file` argument which is a type of File. To call this as a subtask you'd need to pass in: MythicRPC().execute("create_subtask", parent_task_id=task.id, command="upload", params_dict={"path": "/wherever", "file": "filename"}, files={"file": "base64 file contents"}) Notice here that in the parameters piece, the "file" value is the filename and in the "files" parameter, we associated it with the file contents. This allows us to save off the filename in the task's "original_params" while still getting access to the contents in the "params" value. """ create_subtask_group(parent_task_id: int, tasks: [], subtask_group_name: str = None, tags: [] = None, group_callback_function: str = None) -> dict Create a group of subtasks at once and register a single callback function when the entire group is done executing. :param parent_task_id: The id of the parent task (i.e. task.id) :param tasks: An array of dictionaries representing the tasks to create. An example is shown below. :param subtask_group_name: The name for the group. If one isn't provided, a random UUID will be used instead :param tags: An optional list of tags to apply to all of the subtasks created. :param group_callback_function: The name of the function to call in the _parent_ task when all of these subtasks are done. :return: An array of dictionaries representing information about all of the subtasks created. ``` ## Calling C2 RPC Functions Your Payload Type container can actually call RPC functions defined within your C2 profile as well. You must have Mythic 2.2.8 and a PayloadType container version of at least 9 to leverage this functionality. These just have a slightly different format: ``` resp = await MythicRPC().execute_c2rpc(c2_profile="http", function_name="test", task_id=task.id, message="hi") ``` The key things to notice here are: 1. the function you execute is called `execute_c2rpc` instead of just `execute` 2. The `message` parameter is always a string. If you need to send a dictionary to your C2 profile, use `json.dumps({dictionary here})` (make sure you `import json` at the top) 3. Your response back will have the same `status` , `response`, and `error` as the other kind of RPC functions. ### Defining your own C2 RPC Functions If you are creating your own C2, you can create your own C2 RPC functions! Inside of your `mythic/c2_functions` folder for your C2 Profile, create a file called `C2_RPC_functions.py` (it might already exist for you). This is where you can create as many RPC function endpoints as you want! They just have the following format: ``` from mythic_c2_container.C2ProfileBase import * import sys # request is a dictionary: {"action": func_name, "message": "the input", "task_id": task id num} # must return an RPCResponse() object and set .status to an instance of RPCStatus and response to str of message async def test(request): response = RPCResponse() response.status = RPCStatus.Success response.response = "hello" return response ``` # Overview Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.2-greater-than-2.3-updates If you want to look into all the new features available to you as a payload developer from an agent perspective, check out [Agents 2.2 -> 2.3](/version-4.0/updating/mythic-2.2-greater-than-2.3-updates/agents-2.2-greater-than-2.3). The rest of this page is for higher-level updates and UI changes. Either way, to leverage these updates and to upgrade your Mythic instance, you will need to delete your current database `sudo ./mythic-cli database reset` and then pull in the updates and `sudo ./mythic-cli mythic start` again. If you're coming from the 2.2.\* Mythic instances, your current agents will NOT work. They will NOT work. This update changed a lot of the underlying ways those agents communicated and synced with mythic, so you'll need to update those. In most cases, once the developer has announced their stuff is updated, you can use the `./mythic-cli install github [branch] [-f]` command to remove the old version and pull in the new version. When updating minor versions (2.2 to 2.3), make sure you drop your database with `sudo ./mythic-cli database reset` and delete your `Mythic/.env` file. ## Tasking an Agent Tasking agents from the web interface has changed slightly. When you start typing, there is no more automatic autocomplete dialog that pops up to help you out. Instead, as you're typing a command (or before you've typed any letters) you can press `Tab` to cycle through available commands. For example, the `apfell` agent has a `shell` and `shell_elevated` command. If you start typing `shel` and hit tab, you'll first get `shell`, then `shell_elevated`, then back to `shell` again. Once you have a command and you type a space, you can start tab-completing the command's parameters. The `apfell` agent's `shell` command takes one parameter, a `String` called `command`. If you type `shell ` and hit tab, the web interface will start providing the command parameters for you. In this case, you'd get `shell -command`. At this point, without hitting `space` again, if you continue hitting tab, and if the `shell` command had more parameters, the `Tab` button would cycle through the available parameters until you hit `space` and start typing out the values. At any time, if a command has parameters, you can hit `shift+enter` and cause the tasking modal to pop up. ### Parameter Groups To provide a form of conditional parameters for commands, Mythic now supports `parameter groups`. This isn't a new concept - Microsoft's PowerShell does a similar thing. For a single command, you define which parameters are "grouped" together. This allows you to say that two parameters can't be used together, or that you can provide parameterA or parameterB depending on if parameterC is provided. The hard part was how to display this sort of meta information to the user. When you're just typing out parameters on the command line and using the tab complete capabilities described in the previous section, then Mythic will automatically know which parameter group you're using. If you satisfy the requires for multiple parameter groups while using tab-complete, Mythic will just keep giving you options from all matching groups. Once you've supplied enough parameters to match only a single group, then Mythic will only give you parameter recommendations from that group. However, if you're using the popup modal, then Mythic will have a new dropdown menu at the top for selecting which parameter group you want to use. If you've started typing out parameters on the command line and hit `shift+enter` to cause the modal to appear, if Mythic can determine which parameter group you're using, then that one will automatically populate the modal. ## Old / New Web Interface With the changes to how tasking is working, the old user interface will be decommissioned and you will have to use the new interface. # Agents 2.2 -> 2.3 Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.2-greater-than-2.3-updates/agents-2.2-greater-than-2.3 ## PyPi Version To update for Mythic version 2.3.7, you need to have the `mythic_payloadtype_container==0.1.7` PyPi version installed. This will report to Mythic as container version 12. After that, you'll need to perform the following updates so that your agent can successfully sync with Mythic and appear in the user interface. If you're using one of the `itsafeaturemythic` Docker images, check [this page](/version-4.0/customizing/payload-type-development/payload-type-info/container-syncing#current-payloadtype-versions) for the appropriate Docker image to use. ## Build Parameters Build parameters are moving from a dictionary format to an array. There's now also the build parameter type of `BuildParameterType.Boolean`. ``` build_parameters = [ BuildParameter( name="mode", parameter_type=BuildParameterType.ChooseOne, description="Choose the build mode option. Select default for executables, " "c-shared for a .dylib or .so file, " "or c-archive for a .Zip containing C source code with an archive and header file", choices=["default", "c-archive", "c-shared"], default_value="default", ), BuildParameter( name="proxy_bypass", parameter_type=BuildParameterType.Boolean, default_value=False, description="Ignore HTTP proxy environment settings configured on the target host?", ), ] ``` There shouldn't have to be any other changes to this. ## Supported Operating Systems In your payload definition file, you can specify the supported operating systems from a specific set of operating systems via `supported_os = [SupportedOS.Linux, SupportedOS.MacOS]`. To provide even more options going forward, if you want to specify an OS that doesn't have a pre-defined value (like `SupportedOS.Linux`), then you can supply your own via `SupportedOS("MyOS")`. At that point, `MyOS` would appear in the web interface as a build option. ## Browser Scripts With the decommissioning of the old interface, we will also be decommissioning the old style of browser scripts. With that, there are no more `support_browser_scripts` in your payload definition file. That is strictly a relic of the old way of doing things. In your individual commands though, there are still browser scripts. There needs to be an additional attribute `for_new_ui` added in to specify that a script is meant for the new UI vs the old UI. ``` browser_script = [ BrowserScript(script_name="ls", author="@its_a_feature_"), BrowserScript(script_name="ls_new", author="@its_a_feature_", for_new_ui=True) ] ``` ## Command Parameters / Arguments There are a few things that changed as part of the `TaskArguments` processing. The first change is just with the `__init__` part - we're adding in a `**kwargs` parameter: ``` class LsArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) ``` So, you need to make sure you add in the `**kwargs` to *all* of your command files. It's a little tedious, but it allows us to provide more and more features without you having to change stuff down the line. Today, the feature this provides is `tasking_location` - you'll be able to know *where* tasking came from (more on this in a bit). The next big piece is that the `self.args` dictionary is now an array, and the `CommandParameter` class has a few adjustments. ``` self.args = [ CommandParameter( name="path", type=ParameterType.String, default_value=".", description="Path of file or folder on the current system to list", parameter_group_info=[ParameterGroupInfo( required=False )] ) ] ``` So, as you can see from the above code block, you can just remove the dictionary pice and keep your `CommandParameter` as an array. ### CommandParameters The `CommandParameter` class has a few slight adjustments. In addition to the `name` parameter, there's a `cli_name` and `display_name` parameter. This gives you the flexibility to refer to a parameter by `name` for your agent, by `cli_name` when a user is typing out parameters on the command line, and by `display_name` when the user opens up a tasking modal. If you don't supply a `cli_name` then the `name` parameter will be used in its place. Similarly, if you don't supply a `display_name`, then `name` will be used. If `name` or `cli_name` have spaces, then the resulting `cli_name` will replace those spaces with `-` to make it more cli friendly. The `required` and `ui_position` attributes have been removed! They are no longer part of the `CommandParameter` class. They are now part of the `ParameterGroupInfo` class. #### parameter\_group\_info To help with conditional parameters, Mythic 2.3 is introducing parameter groups. Every parameter must belong to at least one parameter group (if one isn't specified by you, then Mythic will add it to the `Default` group and make the parameter `required`. You can specify this information via the `parameter_group_info` attribute on `CommandParameter` class. This attribute takes an array of `ParameterGroupInfo` objects. Each one of these objects has three attributes: `group_name` (string), `required`(boolean) `ui_position` (integer). These things together allow you to provide conditional parameter groups to a command. Let's look at an example - the new `apfell` agent's `upload` command now leverages conditional parameters. This command allows you to either: * specify a `remote_path` and a `filename` - Mythic then looks up the filename to see if it's already been uploaded to Mythic before. If it has, Mythic can simply use the same file identifier and pass that along to the agent. * specify a `remote_path` and a `file` - This is uploading a new file, registering it within Mythic, and then passing along that new file identifier Notice how both options require the `remote_path` parameter, but the `file` and `filename` parameters are mutually exclusive. ``` class UploadArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ CommandParameter( name="file", cli_name="new-file", display_name="File to upload", type=ParameterType.File, description="Select new file to upload", parameter_group_info=[ ParameterGroupInfo( required=True, group_name="Default" ) ] ), CommandParameter( name="filename", cli_name="registered-filename", display_name="Filename within Mythic", description="Supply existing filename in Mythic to upload", type=ParameterType.ChooseOne, dynamic_query_function=self.get_files, parameter_group_info=[ ParameterGroupInfo( required=True, group_name="specify already uploaded file by name" ) ] ), CommandParameter( name="remote_path", cli_name="remote_path", display_name="Upload path (with filename)", type=ParameterType.String, description="Provide the path where the file will go (include new filename as well)", parameter_group_info=[ ParameterGroupInfo( required=True, group_name="Default", ui_position=1 ), ParameterGroupInfo( required=True, group_name="specify already uploaded file by name", ui_position=1 ) ] ), ] ``` So, the `file` parameter has one `ParameterGroupInfo` that calls out the parameter as required. The `filename` parameter also has one `ParameterGroupInfo` that calls out the parameter as required. It also has a `dynamic_query_function` that allows the task modal to run a function to populate the selection box. Lastly, the `remote_path` parameter has TWO `ParameterGroupInfo` objects in its array - one for each group. This is because the `remote_path` parameter applies to both groups. You can also see that we have a `ui_position` specified for these which means that regardless of which option you're viewing in the tasking modal, the parameter `remote_path` will be the first parameter shown. This helps make things a bit more consistent for the user. If you're curious, the function used to get the list of files for the user to select is here: ``` async def get_files(self, callback: dict) -> [str]: file_resp = await MythicRPC().execute("get_file", callback_id=callback["id"], limit_by_callback=False, get_contents=False, filename="", max_results=-1) if file_resp.status == MythicRPCStatus.Success: file_names = [] for f in file_resp.response: if f["filename"] not in file_names: file_names.append(f["filename"]) return file_names else: return [] ``` In the above code block, we're searching for files, not getting their contents, not limiting ourselves to just what's been uploaded to the callback we're tasking, and looking for all files (really it's all files that have "" in the name, which would be all of them). We then go through to de-dupe the filenames and return that list to the user. #### Tasking Location and parse\_dictionary Historically, we treated everything as just a `String` and passed everything the user typed, everything from a tasking modal, everything from scripting, etc to a single `parse_arguments` function. It was up to that function to then determine if it was looking at some form of JSON string or raw arguments or something else and parse it out into the actual CommandParameter objects. That's still the case, but only in some situations. Mythic now tracks *where* tasking came from and can automatically handle certain instances for you. Mythic now tracks a `tasking_location` field which has the following values: * `command_line` - this means that the input you're getting is just a raw string, like before. It could be something like `x86 13983 200` with a series of positional parameters for a command, it could be `{"command": "whoami"}` as a JSON string version of a dictionary of arguments, or anything else. In this case, Mythic really doesn't know enough about the source of the tasking or the contents of the tasking to provide more context. * `parsed_cli` - this means that the input you're getting is a dictionary that was parsed by the new web interface's CLI parser. This is what happens when you type something on the command line for a command that has arguments (ex: `shell whoami` or `shell -command whoami`). Mythic can successfully parse out the parameters you've given into a single parameter\_group and gives you a `dictionary` of data. * `modal` - this means that the input you're getting is a dictionary that came from the tasking modal. Nothing crazy here, but it does at least mean that there shouldn't be any silly shenanigans with potential parsing issues. * `browserscript` - if you click a tasking button from a browserscript table and that tasking button provides a dictionary to Mythic, then Mythic can forward that down as a dictionary. If the tasking button from a browserscript table submits a `String` instead, then that gets treated as `command_line` in terms of parsing. With this ability to track where tasking is coming from and what form it's in, an agent's command file can choose to parse this data differently. By default, all commands must supply a `parse_arguments` function in their associated `TaskArguments` subclass. If you do nothing else, then *all* of these various forms will get passed to that function as strings (if it's a dictionary it'll get converted into a JSON string). However, you can provide another function, `parse_dictionary` that can handle specifically the cases of parsing a given dictionary into the right CommandParameter objects as shown below: ``` async def parse_arguments(self): if len(self.command_line) == 0: raise ValueError("Must supply arguments") raise ValueError("Must supply named arguments or use the modal") async def parse_dictionary(self, dictionary_arguments): self.load_args_from_dictionary(dictionary_arguments ``` In this case, we are forcing the user to supply dictionary-based arguments from one of the methods above. The `self.load_args_from_dictionary(dictionary_arguments)` function takes in the dictionary supplied and looks through the keys to see if they match any `name` or `cli_name` parameters. The same sort of functionality is available in the `parse_arguments` function if you construct your own dictionary or you can call the `self.load_args_from_json_string(string_name_here)` to do the same thing but given a JSON string rather than a dictionary. Inside of these functions you also have access to the `self.get_parameter_group_name()` function to get back the name of the matching parameter group based on which parameters have values. This function will either return a string value of the name or raise a `ValueError` exception with information about *why* you don't currently match any group or why you match too many groups. You also have access to a `self.get_parameter_group_arguments` function which returns an array of just the `CommandParameters` that are associated with the group you're in. # Overview Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.3-greater-than-3.0-updates If you want to look into all the new features available to you as a payload developer from an agent perspective, check out [Agents 2.3 -> 3.0](/version-4.0/updating/mythic-2.3-greater-than-3.0-updates/agents-2.3-greater-than-3.0). The rest of this page is for higher-level updates and UI changes. ## Mythic Server The main goal of this update was to re-do the entire back-end for Mythic from Python3 to GoLang. Mythic's Python back-end was written during a time when asyncio was starting to gain popularity and as such had a mix of naturally asyncio functionality and some faked asyncio functionality. This combination resulted in a database connectivity bug that would only sometimes show up in unreproducible ways and would make it appear as though all of the containers were offline, despite them all being online. Since the work to change all of the database connectivity code would have touched almost every single function in conjunction with the core of how Mythic operated, it was the perfect time to just redo the back-end and solve a lot of technical debt as well. ## Automatic Deployments What if you're automatically deploying Mythic 2.3 right now and want to know what changes need to happen on your end to automatically deploy Mythic 3.0? No worries, here are the deployment changes: * `mythic-cli` isn't included as part of the repo anymore (2.5MB changing binary is gross). Instead, you need to run `sudo make` in the Mythic folder first (that'll create a docker container to build it and then copy it out to the Mythic folder for you automatically). * Mythic no longer does logging/webhooks directly. Instead, if you want logging and webhooks, you need to install those from the [C2Profiles GitHub](https://github.com/MythicC2Profiles). * A basic webhook container is: [basic\_webhook](https://github.com/MythicC2Profiles/basic_webhook) * A basic logger container is: [basic\_logger](https://github.com/MythicC2Profiles/basic_logger) * If you're scripting anything with Mythic, make sure to update to the latest `mythic` pypi package and double check your scripts still work. ## Agent Development / Containers Mythic v2.3 used PyPi packages (`mythic_payloadtype_container`, `mythic_c2_container`, and `mythic_translation_container`) and very specific folder layouts to configure and run the various agents, c2 profiles, and translation containers. This became tedious to maintain and resulted in a lot of duplicated effort as the capabilities offered to containers expanded. To address this, now Mythic containers all use `mythic_container` as their PyPi package if they're written in Python or `github.com/MythicMeta/MythicContainer` package if they're written in GoLang. This new format also allows a single "container" to have multiple payload types, multiple c2 profiles, translation containers, and even two new kinds of containers. ### Logging In an effort to embrace the microarchitecture more, Mythic split out the logging from the main server. Instead, when there is a logging-based event, Mythic will emit that message to a logging container (if one is installed). This allows operators/developers to configure logging to happen however they want. You can write to stdout, to certain files, adjust the format of the messages, you can even use the MythicRPC functionality to enrich the data and log even more. You can even ship the data directly to your SIEM. An example of this is available at `https://github.com/MythicC2Profiles/basic_logger`. ### Webhooks Continuing the effort of the microarchitecture, Mythic split out webhooks. Right now there are three webhook-able events - new callbacks, mythic starting up, and "feedback". You can configure global settings for these in the Mythic UI with a single webhook URL and channel, but you can also configure the container with per-webhook type custom webhook URLs, channels, and the message itself. Similar to the logging, the webhook simply listens on a RabbitMQ queue for messages and then does something with them. What happens with them is entirely under your control, so you can adjust everything and post to entirely different services if you want. An example of the GoLang version of this logger is available at `https://github.com/MythicC2Profiles/basic_webhook`. The number and kinds of webhook events will expand over time. ## Agent Features The rewrite also included a few new agent features. ### Crypto There's a decision that can be made about where "crypto" lives for an agent. Is the crypto part of the agent core, or is it specific to the c2 profile? Historically, Mythic only supported configuring crypto as part of a c2 profile. Now though, you can mark `crypto_type=true` with a Payload Type's build parameters and supercede a c2 profile's cryptography. This gives the agent developer a higher degree of customizability than existed before. ### Build Parameters A few new types of parameters were added for building payloads - Dictionaries, Arrays, Numbers, and Dates. Now, any type of parameter that exists for a C2 profile parameter also exists for a build parameter. ### Build Steps One of the tough things about agent development and usage is that the build process could potentially take a while. However, from the UI, the operator simply sees a spinning circle and has no indication of what's going on - did the container crash? is it still building? how far along in the build process is it? To help with this, Payload Types can now declare "build steps" as part of their Payload Type definition and during the build process they can report back to Mythic that a step is done either successfully or with error and provide both stdout and stderr to go along with it. This makes it a lot easier to see where in the build process you're hitting an error. Each one of those steps are clickable and provide detailed information about what's going on along with how long each step took. A Payload's detailed information also shows more detail about the step itself, like the description of what's going on. ## Services Mythic now provides an additional services - Jupyter Notebooks. From the hamburger icon in the top left, if you click on "Services", you'll see the services available to you. ### Jupyter Jupyter Notebooks, `/jupyter`, provide a handy, persistent way to allow scripting without requiring operators to get set up on their own environment. Right now it's a single, shared instance for all of Mythic (not per operation), but if it gains traction an people really like it, there's a beefier version that allows multi-user sign-ins that can be used instead. This will slowly get a library of common scripting examples that you can draw from when creating your own scripts for your operations. This also provides a much nicer interface for testing and scripting than pulling code from a wiki page and hoping it's updated. The `password` is `mythic`. ### GraphQL Console This will open a new tab to `/console` where you can interact with Hasura - the provider for the GraphQL component of Mythic. All of the web UI and scripting goes through Hasura, so this provides a great way to test out custom GraphQL queries and see what all you can do. When you go here you'll be prompted for a credential to log in - use `sudo ./mythic-cli config get hasura_secret` to get the password to use. If you generate an API token for yourself via your settings page in the Mythic UI, you can supply that token as shown above so you can see *exactly* what your account is able to do as if you logged in via scripting. ### Consuming Services This provides a single page to look at all of the possible logging and webhook message types that can be emitted and allows you to send test messages. This is particularly handy if you're writing/modifying a logging/webhook container and want to make sure that your messages are going through properly. ## Feedback As much as possible, operators should be operating. It's very annoying and potentially time consuming if you have to stop what you're doing and record somewhere that you ran into an issue, that you got caught, or even just that something is weird with a tool you're using. To try to help with this, Mythic now has a "feedback" button at the top of the screen (the thumbs down icon). You can click this at any time and submit to a webhook information for a bug you encountered, record a deconfliction event, submit feedback about something confusing, or even recording a feature request. You must have a webhook container running, like the one from `https://github.com/MythicC2Profiles/basic_webhook` and configure a webhook for your operation, but then it's super easy to record this sort of stuff and continue on with your operation. Then, when your op is done, you can go back and file github issues, feature requests, ask for UI tweaks about things that were confusing, or even having an easy timeline of when you got caught. Because this data is sent to the webhook containers, you can take the data and do whatever you want with it. You don't technically have to do a webhook - you could turn it directly into a github issue or just save it off as a note somewhere for you to visit later. ## Tagging Tagging tasks with additional information has been around for a while in Mythic, but it got a bigger facelift this time around. By clicking the tag icon at the top of the screen, you can view all of your current tag types for the operation. Tag types consist of a description, a short-hand display, and a color. The color you set will show both the light mode and dark mode so that you can be sure to pick a color that'll show properly regardless of what users prefer. Once you have tag types created, you can use them in your operation to tag various things with more information. This is most useful in combination with scripting to "auto tag" things, but you can manually tag everything as well. Right now it just helps you see things as your're scrolling through the UI, but soon there will be a dashboard that gives overviews and more detailed information about all of the things that were tagged. For Tasks, Files, Credentials, Keylogs, Processes, and the FileBrowser, you can click on the tag icon to edit/add tags. This allows you to provide more context to the generic tag type. For example - you can have a tag type of `cred` and if you find credentials in a file, you can then tag that file with the `cred` tag so it's easier for other people to know (and you to remember) that you pulled creds from that file. When you create a new tag you select the type of tag, supply your own source of where the data is coming from (some operator, some automated script, etc). You can supply an external-linkable URL for more information or maybe even the target of where the data applies. The JSON data doesn't technically have to be valid JSON, but if it is, then when you click to view the tag it'll be automatically parsed into a nice table. you can see the short-hand tag displayed next to the file to let you know that it's been tagged. When you click on the tag `cred` now instead of the tag icon itself, you see the data that was supplied. ## Adjusting loaded commands Sometimes, especially during development, you want to test out a new feature or expose a new function within Mythic for a payload type. Historically, that meant you either needed to already have a `load` command created, or you'd have to create an entirely new payload, execute it, then test out your new function. That can be a headache and impractical, especially if it's a `script_only` command that you want to make available to your callbacks *now*. To facilitate this, Mythic now allows you to manually adjust which commands are available in your payloads and callbacks through the UI. This does NOT actually adjust anything within the payload itself, nor does it adjust anything within a running callback. This simply adjusts Mythic's perception of which commands are available. As such, if you use this to add commandX, but it's not actually part of your payload or callback, then your agent won't know what to do with the information. In that case, you'd still need a proper load command to send the new command down to your agent. For payloads, you can select the blue "info" icon next to your payload and scroll down to the "commands" area. You'll see a new button called "Add/Remove Commands": Clicking on that will open up a new dialog box where you can select which commands to add and remove: The same flow is available for Callbacks - click the blue down arrow next to an active callback and select the "View Metadata" entry, then scroll down to the loaded commands. # Agents 2.3 -> 3.0 Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-2.3-greater-than-3.0-updates/agents-2.3-greater-than-3.0 With Mythic 3.0, you have two options for your containers - GoLang or Python. This section will only go over the Python side since there are no 2.3 agents with GoLang containers. When you git clone the new Mythic v3.0.0 you'll notice that there's no `mythic-cli` binary. To reduce the size of the GitHub clones, this binary is now included as part of the main base docker image, so run `sudo make` and the binary will be downloaded and copied into the normal spot. The Apfell v3.0.0 ([https://github.com/MythicAgents/apfell/tree/v3.0.0](https://github.com/MythicAgents/apfell/tree/v3.0.0)) branch and the Apollo v3.0.0 ([https://github.com/MythicAgents/apollo/tree/v3.0.0](https://github.com/MythicAgents/apollo/tree/v3.0.0)) branch are good examples of two slightly different ways you can format things. Make sure you update your Docker version to at least `20.10.22` or above (current is `23.0.1`. This is required for the latest docker containers to work properly. A simple `sudo apt upgrade` and install should suffice. Also install docker-compose via `sudo apt install docker-compose-plugin` vs the `docker-compose` script as the script will soon be deprecated according to Docker. The `ExternalAgent` format is still the same. These next pieces are how you can test updates of your agent locally before copying your agent's folder back into your normal ExternalAgent format. The `Mythic/InstalledServices` folder is on the Mythic server. When you installed a PayloadType via `./mythic-cli install github `, the folder within your github's `Payload_Type` folder is copied to the `Mythic/InstalledServices` folder. If you are doing local agent development instead of on the same server as the Mythic instance, then in the following steps you can treat the `Mythic/InstalledServices` folder the same as your GitHub's `Payload_Type` folder (or any other folder really - it's simply serving as a staging ground while you create the new folder structure). If you're doing local development, you need at least Python version 3.10 because of the new typing features it offers. 1. Make a directory, `agent name` , in `Mythic/InstalledServices` 2. Copy your entire Payload type's `agent name` directory into `InstalledServices` (yes, the path will look like `Mythic/InstalledServices/agentName/agentName`) 3. In `Mythic/InstalledServices/agentName` create a `main.py` and a `Dockerfile` 4. In your new `Dockerfile`, copy the contents of your old `Dockerfile` and change the `FROM` line to `FROM itsafeaturemythic/mythic_python_base:latest` 5. In your new `main.py` add: ``` import mythic_container from [agent name].mythic import * mythic_container.mythic_service.start_and_run_forever() ``` 6. To make your new `agent name` directory a PyPi package that can be imported, create a `__init__.py` file in `Mythic/InstalledServices/agentName/agentName`. In the `Mythic/InstalledServices/agentName/agentName/Mythic` folder make a `__init__.py` file with the following contents (this will loop through all of your command files and import them automatically): ``` import glob import os.path from pathlib import Path from importlib import import_module, invalidate_caches import sys # Get file paths of all modules. currentPath = Path(__file__) searchPath = currentPath.parent / "agent_functions" / "*.py" modules = glob.glob(f"{searchPath}") invalidate_caches() for x in modules: if not x.endswith("__init__.py") and x[-3:] == ".py": module = import_module(f"{__name__}.agent_functions." + Path(x).stem) for el in dir(module): if "__" not in el: globals()[el] = getattr(module, el) sys.path.append(os.path.abspath(currentPath.name)) ``` 1. In your `Mythic/InstalledServices/agentName/agentName/mythic/agent_functions` files, we need to replace all `mythic_payloadtype_container` with `mythic_container` . If you have an import like `from agent_functions.execute_pe import PRINTSPOOFER_FILE_ID` which references another command file, update it to `from .execute_pe import PRINTSPOOFER_FILE_ID`. If you include a local library at the same level as `agent_functions`, you can import it like `from [agent name].mythic.[package] import [thing]` If you're doing local development, you'll need a `rabbitmq_config.json` file at the same level as your `main.py` to tell your service where Mythic is located and the rabbitmq password. The configuration options you can supply can be found in the [Local Development](/version-4.0/customizing/payload-type-development#turning-a-vm-into-a-mythic-container) section. There are some changes to the `rabbitmq_config.json` file keys: * `container_files_path` is no longer used and can be deleted. * `username` is no longer used and can be deleted. * `password` is now `rabbitmq_password.` * `host` is now `rabbitmq_host.` * `name` is no longer used and can be deleted. * `virtual_host` is no longer used and can be deleted. Required keys in `rabbitmq_config.json` are: * `rabbitmq_host` - points to the IP where Mythic lives * `rabbitmq_password` - the password used to authenticate to rabbitmq * `mythic_server_host` - points to the IP where Mythic lives * `mythic_server_port` - if you're using something other than the default (this is NOT the 7443 that you use for the UI) * mythic\_server\_grpc\_port - if you're using something other than the default Now to actually update the content of your builder/command files. There's not much you need to do. #### builder.py agent definition Because the new structure treats your entire agent directory as a Python package, the container no longer knows the paths for things. This gives you a lot more freedom in how you want to organize your code, but does require you to specify where things are located. In your `builder.py` file where you define your Payload Type, you need to add the following: ``` agent_path = pathlib.Path(".") / "apollo" / "mythic" agent_code_path = pathlib.Path(".") / "apollo" / "agent_code" agent_icon_path = agent_path / "agent_functions" / "apollo.svg" ``` The `agent_path` is the path to your general agent structure (typically with the `agent_functions` as a sub-folder. The `agent_code_path` points to your agent's actual code. Something that's a little different is the agent icons - the agents will sync that over automatically with the rest of their definition (no more having to copy it over manually or get it from an install). What that means though is you either need to supply `agent_icon_path` and provide the path to your agent's svg icon or specify `agent_icon_bytes` and provide the raw bytes for your icon. #### build In your payload type's build function you can report back on build steps via the `SendMythicRPCPayloadUpdateBuildStep` RPC call (based on your defined build steps). This will update the UI step-by-step for the operator so they know what's going on. You can also set `UpdatedFilename` (or `updated_filename` for Python) in your build response and adjust the final filename of the payload. This can be helpful if your payload type allows you to build to various outputs (exe, dll, dylib, binary, etc). This allows you to adjust the filename based on that so that when the user clicks "download" in the UI, they get the right file and don't have to change the filename. #### Browser scripts Browserscripts work just the same, but browserscripts will look for their code at `agent_path / browser_scripts / filename.js` OR at the path specified by the `name` parameter for the script. So, that means your can either specify the name as `test.js` and have it located in your `agent_path / browser_scripts / test.js` file or specify a full path as your name. The browser\_script attribute is a single BrowserScript value, not an array. This is because the entire Python back-end is gone, so there's no more need to supply a script for the old UI and the new UI. #### c2 profile parameters when building When looping through c2 profile parameters - arrays are actually arrays, crypto types and dictionary types are dictionaries, so do better checking here for name of parameters. A bunch of agents simply check if the supplied value is a dictionary and then automatically try to pull out certain values, but that might not be the case anymore. For example, when looping through the `http` profile, both the `AESPSK` and the `headers` parameters will be passed in as dictionaries. ``` for key, val in c2.get_parameters_dict().items(): if key == "AESPSK": c2_code = c2_code.replace(key, val["enc_key"] if val["enc_key"] is not None else "") elif not isinstance(val, str): c2_code = c2_code.replace(key, json.dumps(val)) else: c2_code = c2_code.replace(key, val) ``` #### Updated Create Tasking - create\_go\_tasking The current `create_tasking` functions still work just like normal; however, the newer `create_go_tasking` function gives you more contextual data and mirrors the data structures from the new Golang container version. ``` async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) return response ``` This `taskData` variable is defined here: [https://github.com/MythicMeta/MythicContainerPyPi/blob/main/mythic\_container/MythicCommandBase.py#L1068](https://github.com/MythicMeta/MythicContainerPyPi/blob/main/mythic_container/MythicCommandBase.py#L1068) and provides a lot more context in a well-defined class. #### Completion Function for Tasking Tasks can specify for a certain function to execute when the task finishes executing. That hasn't changed. However, the format of how you define it has changed slightly. Before, you'd simply pass the name of a function and the container would loop through all known function definitions looking for one that matched. That's not super great, so now you define a dictionary of function name to function as part of your command definition. ``` completion_functions: dict[str, Callable[[PTTaskCompletionFunctionMessage], Awaitable[PTTaskCompletionFunctionMessageResponse]]] = {} ``` The PTTaskCompletionFunctionMessage and response classes can be found in the PyPi code and auto-completed via IDEs. This syntax is just the Python way of saying that the format is: ``` async def functionName(myArg: PTTaskCompletionFunctionMessage) -> PTTaskCompletionFunctionMessageResponse: do something here ``` To leverage this new `functionName` function as part of your tasking, in your `create_tasking` function you need to set the name: ``` async def create_tasking(self, task: MythicTask) -> MythicTask: task.completed_callback_function = "functionName" return task ``` If you're using the new `create_go_tasking` function, then you need to do somthing very similar: ``` async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, CompletionFunctionName="functionName" ) return response ``` #### Process response function Sending back data via the `process_response` key within your `responses` allows you to hook into the associated command's `process_response` function within your Payload Type's container. The format of this function has changed *slightly*: old: ``` async def process_response(self, response: AgentResponse): resp = await MythicRPC().execute("update_callback", task_id=response.task.id, sleep_info=response.response) ``` new: ``` async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) await MythicRPC().execute("update_callback", task_id=task.Task.ID, sleep_info=response) return resp ``` #### Dynamic Query Function Similar to the completion functions, dynamic query functions look a *little* different, but are generally still the same: ``` dynamic_query_function: Callable[[PTRPCDynamicQueryFunctionMessage], Awaitable[PTRPCDynamicQueryFunctionMessageResponse]] = None, ``` which is to say that the function is pre-defined (one per command parameter) and looks like: ``` async def dynamic_query_function(myArg: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse: do something ``` #### Command OPSEC The opsec functionality has been removed from a special CommandOPSEC class and moved to the main command class itself. So, your command can have two additional functions: ``` async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse: response = PTTTaskOPSECPreTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPreBlocked=False, OpsecPreMessage="Not implemented, passing by default", ) return response async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse: response = PTTTaskOPSECPostTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPostBlocked=False, OpsecPostMessage="Not implemented, passing by default", ) return response ``` ### SOCKS The RPC call to start SOCKS is no longer `control_socks`. Instead, you'll use the `SendMythicRPCProxyStart` and `SendMythicRPCProxyStop` commands as detailed [here](/version-4.0/customizing/payload-type-development/socks). ## C2 Profiles C2 profiles also need to be updated for Mythic 3.0.0, in an extremely similar way to Payload Types. 1. Make a directory, `c2 name` , in `Mythic/InstalledServices` 2. Copy your entire C2 Profile's `c2 name` directory into `InstalledServices` (yes, the path will look like `Mythic/InstalledServices/c2Name/c2Name`) 3. Remove `c2_service.sh`, `mythic_service.py`, and `rabbitmq_config.json` from your `mythic` folder 4. Remove `C2_RPC_Functions.py` 5. In `Mythic/InstalledServices/c2Name` create a `main.py` and a `Dockerfile` 6. In your new `Dockerfile`, copy the contents of your old `Dockerfile` and change the `FROM` line to `FROM itsafeaturemythic/mythic_python_base:latest` 7. In your `mythic/c2_functions/` folder, your definition file should import `mythic_container` instead of `mythic_c2_container` (similar to what we did for agent updates). 8. In your new `main.py` add: ``` import mythic_container from [c2 name].mythic import * mythic_container.mythic_service.start_and_run_forever() ``` 9. In your c2 profile definition, add in two more attributes - `server_folder_path` (path to the folder where your server binary and config.json files exist), and `server_binary_path` (path to the binary to execute if you're doing an egress c2 profile and not a p2p profile). To see what this looks like all together, look at the `websocket` example here: [https://github.com/MythicMeta/ExampleContainers/tree/main/Payload\_Type/python\_services](https://github.com/MythicMeta/ExampleContainers/tree/main/Payload_Type/python_services). You'll notice that the `websocket` is just one of *multiple* services that the single docker contianer is offering. If you want your container to *only* offer that one, then you can remove the other folders and adjust your `main.py` accordingly. Keys in the C2 Profile Parameter Type `Dictionary` will be sorted alphabetically - they will NOT maintain the order they were specified in the UI. This is currently a limitation of the Golang Google JSON library. ### Env Settings Mythic provides a `MYTHIC_ADDRESS` environment variable that points to `http://mythic_server:17443/agent_message` for C2 Profiles to use for forwarding their messages. With Mythic 3.0+, there are going to be more options for connections outside of a static HTTP endpoint. Therefore, the `MYTHIC_ADDRESS` field exists, but there's additional values for `MYTHIC_SERVER_HOST` and `MYTHIC_SERVER_PORT` so that we can dynamically use these later on. ## Translation Containers Translation containers are no different than C2 Profiles and Payload Types for the new format of things. Look to `translator` in the ExampleContainers ([https://github.com/MythicMeta/ExampleContainers/tree/main/Payload\_Type/python\_services](https://github.com/MythicMeta/ExampleContainers/tree/main/Payload_Type/python_services)) repository for an example of how to format your new structure. Translation containers boil down to one class definition with a few functions. One big change from Mythic 2.3 -> 3.0 for Translation Containers is that they now operate over gRPC instead of RabbitMQ. This means that they need to access the gRPC port on the Mythic Server if you intend on running a translation container on a separate host from Mythic itself. This port is configurable in the `Mythic/.env` file, but by default it's 17443. This change to gRPC instead of RabbitMQ for the translation container messages speeds things up and reduces the burden on RabbitMQ for transmitting potentially large messages. Some additional notes about Translation container message updates: * Although Mythic 3 will base64 decode a message before providing it to translate\_from\_c2\_format, Mythic 3 will not base64 encode the result of translate\_to\_c2\_format which you will still need to do like you would have for Mythic 2.3 * Mythic 2.3 allowed UUID prefixes to custom agent messages to a little endian encoded 16 byte value. In Mythic 3 any 16 byte UUID prefix needs to be big endian encoded * Mythic 2.3 required the translation container to base64 encode/decode inputs and outputs for generate\_keys. Mythic 2.3 would directly use that base64 data to populate enc\_key or dec\_key values for building and would provide that base64 data directly to any translate\_to\_c2\_format and translate\_from\_c2\_format functions. * Mythic 3 expects generate\_keys to provide the keys as byte arrays. Mythic 3 will base64 encode/decode the byte arrays when populating any enc\_key or dec\_key value for an agent configuration, but will use the byte array when calling any translate\_to\_c2\_format and translate\_from\_c2\_format function * Mythic 2.3 would provide the entire message as input to translate\_from\_c2\_format. Mythic 3 provides the message, minus any UUID prefix # Mythic 3.2->3.3 Updates Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-3.2-greater-than-3.3-updates Updates and needed agent changes between versions ## Breaking Changes * All Webhook / Logging containers must be updated to the new PyPi/Go packages and provide two new fields: name and description. * All C2 Profiles must update to the new PyPi/Go packages so that they can properly handle messages for getting/updating files (including editing the local config). No other change required outside of just updating the packages. * All Payload Types just need to update to the new PyPi and MythicContainer packages so that they can accept the new file\* RPC calls from the web UI. * MythicContainer @ v1.4.1 for Golang * mythic-container == 0.5.9 for Python * Wrapper payloads get their `wrapped_payload` as raw bytes now, not as a base64 blob! **This is a breaking change** ## Agent Messages to Mythic **Artifacts** Two new fields you can (optionally) report back with your artifacts are the following: * `needs_cleanup` - This identifies in the Mythic UI if this artifact needs some additional action to be cleaned up. Some artifacts naturally clean themselves up or are temporary, others need to be manually removed/killed. * `resolved` - This indicates that the artifact that needed to be cleaned up has been successfully cleaned up **File Browser** * `success` is now an optional boolean field instead of a required one. Setting this to `true` will result in a green checkmark in the UI and setting this to `false` will result in a red warning sign. Not setting a value will leave it with no additional icon. ## Quality of Life ### Invite Links Admins can generate one-time-use invite links to invite a new operator to their Mythic server without pre-creating the account. This is disabled by default but can be enabled via .env or the global settings by an admin. More info [here](/version-4.0/updating/mythic-3.2-greater-than-3.3-updates#invite-links). # Mythic 3.3->3.4 Updates Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-3.3-greater-than-3.4-updates Updates and needed agent changes between versions ## Breaking Changes None! ## To Update Depending on your language, update the associated Mythic Container library: * github.com/MythicMeta/MythicContainer\@v1.5.0 for Golang * mythic-container==0.6.0 for Python ## Quality of Life * Tabs in the active callbacks page that you aren't currently looking at will get a green notification bubble if they have changes * All services (payload type, c2 profile, translation container, eventing, auth, webhooks, logging, etc) can how report back their own semver for easier version tracking * The payload building process in the UI had a complete overhaul! ### Payload Types * Build Parameters are now dynamic and can be hidden depending on the selected OS or other build parameter values. * Build Parameters can now specify a "group name" to be grouped together logically in the UI. * A new field, C2 Parameter Deviations, allows Payload Types to "modify" supported C2 Profile parameters to more closely align with their implementations * Payload Type definitions can now implement their own "help" function so you don't have to use Mythic's built-in help * you don't need to create a "command" for it, it's a new function that's available as part of your payload definition * Payload types can specify if they support multiple c2 selections in a payload or not (ex: http and tcp) * Payload types can specify if they support multiple instances of c2s in a payload or not (ex: 2 instances of httpx) ### Eventing * New GUI Wizard button available on the main eventing page in the top left * Eventing step details auto hide stdout/stderr if they're empty * Some eventing triggers that allow you to limit by payload\_type now also allow you to further limit by selected OS * you can now trigger eventing workflows from right clicking a callback in the active callbacks page ### C2 Profiles * C2 profiles can now report their own agent icons (and dark mode agent icons) to be shown in the UI and in graphs! ### Invite Links Invite Links are now associated with a database table, so they survive reboots! # Mythic 3.4 -> 4.0 Updates Source: https://docs.mythic-c2.net/version-4.0/updating/mythic-3.4-greater-than-4.0-updates Breaking changes, migration steps, and developer updates for Mythic v4.0 Mythic 4.0 is a major release. It adds operation chat, scoped API tokens, operator aliases, task references, richer payload and C2 parameters, conditional wrapper compatibility, resumable file transfers, and interactive file editing. It also changes authentication and several public API contracts. Read the breaking changes before upgrading a production server. Existing API tokens stop working, older container libraries cannot preserve v4 authentication context, and integrations using renamed Hasura actions or internal database IDs must be updated. ## Upgrade checklist 1. Schedule downtime and back up both the database and Mythic-managed files. ```bash theme={"system"} sudo ./mythic-cli backup database /path/to/mythic-backup sudo ./mythic-cli backup files /path/to/mythic-files-backup ``` 2. Record scripts, bots, eventing workflows, and external integrations that use API tokens, `/api/v1.4` routes, Hasura actions, or task/callback IDs. 3. Update every payload type, C2 profile, translation container, and consuming container to a v4-compatible container library. The v4 development branches use: * Go: `github.com/MythicMeta/MythicContainer@v1.7.0-rc4` * Python: `mythic-container==0.7.0-rc9` * Python scripting: `mythic==0.3.0-rc8` Use the newest v4-compatible release when a later stable tag is available. For consistency sake, if you have a public service that you're updating, make the updates to a branch called `Mythic-v4.0.0`. This will make it so users doing testing can always check out/install the same branch for this batch of updates. 4. Update the Mythic checkout, rebuild `mythic-cli`, and start Mythic. Database migrations run automatically. ```bash theme={"system"} git checkout origin Mythic-v4.0.0 sudo make sudo ./mythic-cli start sudo ./mythic-cli install github https://github.com/MythicAgents/apollo -f -b Mythic-v4.0.0 sudo ./mythic-cli install github https://github.com/MythicC2Profiles/http -f -b Mythic-v4.0.0 sudo ./mythic-cli install github https://github.com/MythicC2Profiles/basic_webhook -f -b Mythic-v4.0.0 etc ``` 5. Confirm the server and UI versions, then check **Installed Services** for incompatible or offline containers. 6. Recreate API tokens with the minimum required scopes and update each caller to use `Authorization: Bearer `. 7. Run a smoke test for login, GraphQL queries, payload builds, callback tasking, file upload/download, eventing, and every external integration. ## Breaking changes ### Authentication is Bearer-only Protected HTTP and GraphQL requests no longer authenticate from the `mythic` cookie. Access tokens and API tokens use the same header: ```http theme={"system"} Authorization: Bearer mtk_example_token_value ``` The old API-token header is no longer valid: ```diff theme={"system"} - apitoken: + Authorization: Bearer ``` Browser sessions continue to work through Mythic's UI because the UI reads its access token and adds the header to authenticated requests. Custom browser extensions, reverse proxies, and scripts that depended on cookie-only authentication must be updated. ### API tokens are opaque, scoped, and shown once API tokens are no longer long-lived JWTs. New tokens begin with `mtk_`, are hashed at rest, and are shown only when created. Save a new token immediately; Mythic cannot recover its value from the UI or database. All v3.4 API tokens must be regenerated. A token can receive only scopes already available to the caller. Write scopes include the corresponding read scope. Resource wildcards such as `task.*` and the global `*` scope are supported. ```graphql theme={"system"} mutation CreateAutomationToken { createAPIToken( name: "task monitor" scopes: ["callback.read", "task.read", "response.read"] ) { status error token_value } } ``` Use `apiTokenScopeDefinitions`, `scopeCheck`, and `whoami` to discover available scopes and inspect the current identity. ### `/api/v1.4` was removed Mythic action and webhook routes are now mounted without the `/api/v1.4` prefix. For example: ```diff theme={"system"} - POST /api/v1.4/task_upload_file_webhook + POST /task_upload_file_webhook ``` Do not mechanically add a different version prefix. Read the current Hasura metadata or use the supported scripting/container libraries for route construction. ### Hasura actions use camelCase The remaining snake\_case action names were normalized: | Mythic 3.4 | Mythic 4.0 | | --------------------------- | ------------------------- | | `callbackgraphedge_add` | `callbackgraphedgeAdd` | | `callbackgraphedge_remove` | `callbackgraphedgeRemove` | | `config_check` | `configCheck` | | `download_bulk` | `downloadBulk` | | `dynamic_query_function` | `dynamicQueryFunction` | | `rebuild_payload` | `rebuildPayload` | | `redirect_rules` | `redirectRules` | | `reissue_task` | `reissueTask` | | `reissue_task_handler` | `reissueTaskHandler` | | `typedarray_parse_function` | `typedarrayParseFunction` | | `meHook` | `whoami` | Regenerate GraphQL clients or update stored queries after changing these names. ### Actions use operation-scoped display IDs Public task and callback actions now use the IDs operators see in the UI. Migrate ambiguous arguments as follows: | Old argument | New argument | | ---------------------------- | ------------------------ | | `task_id` | `task_display_id` | | `task_ids` / `tasks` | `task_display_ids` | | `callback_id` | `callback_display_id` | | `callback_ids` / `callbacks` | `callback_display_ids` | | `parent_task_id` | `parent_task_display_id` | For example, v4 task creation uses display IDs and can resolve task references explicitly: ```graphql theme={"system"} mutation CreateTask($callback: Int!, $params: String!) { createTask( callback_display_id: $callback command: "shell" params: $params resolve_task_references: true ) { status error } } ``` Database primary keys still exist in direct table queries and internal container messages. Do not substitute a display ID where a schema explicitly asks for a database `id`. ### Container RPC carries authenticated context Mythic now propagates an authenticated context through RabbitMQ and enforces scopes on container RPC requests and responses. All services must use compatible container libraries; hand-written RabbitMQ clients must preserve the auth-context header when forwarding work. ```mermaid theme={"system"} sequenceDiagram participant U as Operator or API token participant M as Mythic participant Q as RabbitMQ participant C as Container U->>M: Request with Bearer token M->>M: Validate identity, operation, and scopes M->>Q: RPC plus signed auth context Q->>C: Deliver request and context C->>Q: Response preserving context Q->>M: Correlated response M->>M: Enforce response route scope M-->>U: Result ``` If a container works on v3.4 but v4 reports missing context or scope errors, update its library before debugging the RPC itself. ### Eventing API-token inputs are objects The v4 `mythic.apitoken` schema is an object with a token `type` and explicit `scopes` list: ```yaml theme={"system"} inputs: API_TOKEN: type: mythic.apitoken scopes: - callback.read - task.write - response.read ``` The legacy scalar shorthand remains as a compatibility path and requests broad `*` access. Migrate workflows to the object form so their required permissions are visible and constrained. The token exists only for the event step and is invalidated when the step finishes. Grant only the scopes the custom function needs. ### Wrapper allowlists were replaced The payload type fields `wrapped_payloads` and `supported_wrapper_payload_types` were replaced by `wrapper_payload_requirements`. Builders now return `build_metadata` (`architecture` and `format`; Mythic records the selected OS), and wrappers declare exact accepted combinations with optional build-parameter conditions. This way wrappers and payloads can link up in a supported way without hardcoding payload type names. The following wrapper example says that it: * supports a payload's format of `shellcode`, architecture of `x64`, and os of `windows` WHEN this wrapper's own build parameter of `arch` is `x64` * no other payload requirements, so it supports no other options ```python3 Python3 Example in wrapper payload definition theme={"system"} wrapper_payload_requirements = [ WrapperPayloadRequirement( requires=WrapperPayloadRequirementRequires( format=PayloadBuildMetadataFormat.Shellcode, architecture=PayloadBuildMetadataArchitecture.X64, os=SupportedOS.Windows, ), when=[WrapperPayloadRequirementWhen( build_parameter_name="arch", build_parameter_value="x64", )] ) ] ``` A payload that supports this might do the following: ```python Payload build metadata theme={"system"} resp = BuildResponse(status=BuildStatus.Error) resp.build_metadata = PayloadBuildMetadata( architecture=PayloadBuildMetadataArchitecture.X64, format=PayloadBuildMetadataFormat.Exe if output_type == "WinExe" or output_type == "Service" else PayloadBuildMetadataFormat.Shellcode if output_type == "Shellcode" else "Source" ) ``` Here we see that this payload is settings its architecture to `x64` and setting its `format` to `exe`, `shellcode`, or `source`. The builder doesn't need to specify the `os` because Mythic already knows it based on the parameters the user selected when building the payload. Existing wrapper payload types must migrate their definitions. Normal payload builders should return metadata so compatible payload discovery is accurate. ### Legacy browser-script renderers were removed The legacy `screenshot`, `download`, and `search` browser-script result renderers were removed. Return supported plaintext, table, or media structures and use current authenticated file/media URLs. The obsolete operation-specific browser-script mapping table and legacy payload/C2/translation resync send paths were also removed. ### Process Browser The format for messages from the agent for the process browser changed. The idea is for a very small change to lift out three frequently repeated fields into more generic metadata. ```json Mythic v3.4 theme={"system"} { "action": "post_response", "responses": [ { "task_id": "uuid", "processes": [ { "process_id": 5, "parent_process_id": 2, "update_deleted": true, "host": "workstation1", "os": "windows", } ] } ] } ``` ```json Mythic v4.0 theme={"system"} { "action": "post_response", "responses": [ { "task_id": "uuid", "processes": { "host": "workstation1", "os": "windows", "update_deleted": true, "processes": [ { "process_id": 5, "parent_process_id": 2, } ] } } ] } ``` ### SendMythicRPCAgentStorageCreate A typo in the Python PyPi name for the `SendMythicRPCAgentstorageCreateMessage` is updated to `SendMythicRPCAgentStorageCreateMessage`. Notice the small `s` in `storage` initially is updated to a capital `S` now. ### C2 Profile File Host Changes Mythic 3.4 would send a single file at a time to be hosted by a C2 profile with just add/remove, the path, and a file\_id. With Mythic 4.0, there are no more blanket allows for downloading files if you know the UUID. Mythic now sends a single message to the host file function for a C2 profile with *all* of the files. Each file has its own add/remove state, path, agent\_file\_id *and* a download token. That download token must be passed as an `Authorization: Bearer ` value with the download request. These tokens are scoped specifically to the associated `agent_file_id` value, so you can only ever download one file with it. These tokens are also not permanently saved by Mythic, so a reboot will generate new tokens that the C2 profile must update internally. ### Testing old containers with new changes If you want to use a base container for an older version of mythic, like `itsafeaturemythic/mythic_python_go:latest`, but want to test stuff out for v4, you can make the following changes to your `Dockerfile`: ```bash Dockerfile theme={"system"} FROM itsafeaturemythic/mythic_python_go:latest RUN pip3 install mythic_container==0.7.0rc9 RUN pip3 install --upgrade --force-reinstall grpcio>=1.78.0 ``` ## New v4 capabilities * **Operation chat** supports standard channels and AI-backed chat containers, streaming responses, cancellation, search, message edits, tool output, approvals, input requests, and delegated sub-agent views. * **Operator aliases** provide command and generic aliases, optional payload type or chat-container scope, nesting, and import/export. * **Task references** expand `@cred` and `@link` values before tasking and record the resolution on the task. * **Parameters** add display names, display labels for choices, richer hide conditions, dynamic C2 choices, complex dynamic choices, `other_parameters`, and `JSONString` parameters with inline schemas. * **Eventing** steps can pause for approval or typed runtime input and resume with the operator's response. * **File transfers** can use byte offsets, report byte-level progress, and resume from the next missing chunk or offset. * Instead of reporting `total_chunks`, `chunk_size`, and `chunk_num`, agents can now optionally report `total_size` and `chunk_offset` for variable chunks when download (agent -> mythic) files. * Agents can report back in the `download` key a `file_id` and `resume: true` - Mythic will tell the agent the transfer type that the file started with (chunk or offset) and the next chunk\_num/chunk\_offset that it's expecting. * **File editing** adds an interactive, versioned UTF-8 text editor with conflict detection, staged previews, refresh, force-overwrite, and close actions. * **Credentials** add subtype, structured identity and metadata, custom display values, updates, validity tracking, and dedicated JWT/Kerberos renderers. * **C2 file hosting** is tracked and can be updated, retried, stopped, or removed. * **Agent RPC** lets an agent make asynchronous command specific requests through its container. * **Custom RPC timeouts** can be extended with `custom_rpc_timeout` without retrying duplicate work. See the linked v4 operator and development pages for the new JSON and language-specific examples. ## Mythic-v4.0.0 To make things easier for users, if you're testing some public updates for Mythic v4.0, I request that you make your changes in a branch called `Mythic-v4.0.0`. This makes it so that users are always able to install Mythic v4 compatible versions with the same `-b Mythic-v4.0.0` command line argument. ## Rollback Do not point v3.4 services at a database after v4 migrations have run. To roll back, stop Mythic, restore the v3.4 code, restore the pre-upgrade database and file backups, rebuild `mythic-cli`, and start the old version. Recreate any external configuration changes made after the backup separately. # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers Consuming containers are a separate class of containers that wait for things to happen that aren't tasking ## Location Consuming containers are located by clicking the hamburger icon in the top left, selecting "services", and then clicking "consuming services". ## Types There are 4 kinds of consuming containers: * `webhook` - these containers get messages pertaining to alerts, callbacks, feedback, startup, and custom messages. Their goal is to take the data presented to them in these messages and send webhook messages to additional services (like slack/discord) * `logging` - these containers get messages pertaining to artifacts, callbacks, credentials, files, keylogs, payloads, and tasks. The goal of these containers is to take these messages and log them to files, stdout, or to SIEMs so that these important events can be tracked more easily for your environment * `eventing` - these containers get messages about custom functions and conditional checks in eventing workflows. They are used to do more complex decisions and actions within workflows than the basic functionality provided by Mythic's core. * `auth` - these containers extend the login functionality within Mythic. You can either add SSO support or custom auth (such as LDAP), but at the end of the process you have to return the email of the user to authenticate. This email is then checked against the operator's email addresses in Mythic to determine which account to create a JWT for. ## Commonalities Every consuming container has the following in common: * `name` - because each of these containers are tracked for their online/offline status (and potentially used for eventing/auth), each one needs to have a unique name * `description` - it's helpful to describe what each container is responsible for, especially if you have a bunch of services installed to know what's happening where * `subscriptions` - you'll probably see a "Subscriptions" field, but you don't need to fill this out. Mythic uses this to track what all the container is subscribing to. This is auto populated by the golang/python library code for syncing to Mythic # Auth Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/auth ## Auth Structure Auth containers allow you to extend Mythic's login capabilities by offloading the check from Mythic's salted + hashed database password to something else. This can either be towards an Identity Provider (IDP) like Microsoft ADFS (SSO), or towards a non-identity provider/SSO check (like attempted auth to an LDAP service). ```go Go theme={"system"} type AuthDefinition struct { Name string \`json:"name"\` Description string \`json:"description"\` IDPServices []string \`json:"idp_services"\` NonIDPServices []string \`json:"non_idp_services"\` GetIDPMetadata func(GetIDPMetadataMessage) GetIDPMetadataMessageResponse GetIDPRedirect func(GetIDPRedirectMessage) GetIDPRedirectMessageResponse ProcessIDPResponse func(ProcessIDPResponseMessage) ProcessIDPResponseMessageResponse GetNonIDPMetadata func(GetNonIDPMetadataMessage) GetNonIDPMetadataMessageResponse GetNonIDPRedirect func(GetNonIDPRedirectMessage) GetNonIDPRedirectMessageResponse ProcessNonIDPResponse func(ProcessNonIDPResponseMessage) ProcessNonIDPResponseMessageResponse // Subscriptions - don't bother here, this will be auto filled out on syncing Subscriptions []string \`json:"subscriptions"\` OnContainerStartFunction func(sharedStructs.ContainerOnStartMessage) sharedStructs.ContainerOnStartMessageResponse \`json:"-"\` } ``` for example: ```go Go Example theme={"system"} func Initialize() { authName := "MyAuthProvider" myAuth := authstructs.AuthDefinition{ Name: authName, Description: "A custom SSO auth provider for ADFS", IDPServices: []string{"ADFS"}, NonIDPServices: []string{"LDAP"}, OnContainerStartFunction: func(message sharedStructs.ContainerOnStartMessage) sharedStructs.ContainerOnStartMessageResponse { logging.LogInfo("started", "inputMsg", message) return sharedStructs.ContainerOnStartMessageResponse{} }, GetIDPMetadata: func(message authstructs.GetIDPMetadataMessage) authstructs.GetIDPMetadataMessageResponse { response := authstructs.GetIDPMetadataMessageResponse{ Success: false, } err := initializeSAMLSP(authName, message.ServerName) if err != nil { response.Error = err.Error() return response } buf, err := xml.MarshalIndent(samlSP.ServiceProvider.Metadata(), "", " ") if err != nil { response.Error = err.Error() return response } response.Success = true response.Metadata = string(buf) return response }, ... } } ``` ## Auth Functionality There are two forms of auth containers - Identity Provider (IDP) SSO Services and Non-IDP checks. The IDP and NonIDP Services are available as options in the Logon UI page as long as the containers are online. If the auth container goes offline, then the option will disappear from the Logon page and you will have to fallback to the normal Mythic logon procedures. ### IDP SSO Services In these cases we are going to use the auth container to forward Mythic to a remote, SSO Identity Provider. This tends to have a few components: * The SSO Identity Provider needs to get some sort of metadata from the auth container to register it as a trusted partner (i.e. `GetIDPMetadata`) * The user needs to be redirected to this IDP to kick off the auth flow (username/password, MFA, duo prompts, etc) (i.e. `GetIDPRedirect`) * After the IDP has determined if you're authorized or not, the result needs to be POSTed back to Mythic (and thus to the auth container) (i.e. `ProcessIDPResponse`). The end result here has to return a message back to Mythic to inform Mythic of two things: - was the authentication successful or not - what is the email associated with the user that just authenticated The `IDPServices` array identifies which options are displayed to the user when they try to log in: ```go theme={"system"} IDPServices: []string{"ADFS"}, ``` Selecting the `ADFS - MyAuthProvider` kicks off a request to the `GetIDPRedirect` function. ### Non-IDP SSO Services The flow here is almost exactly the same as the IDP SSO Services, but instead of IDP, we have NonIDP everywhere. However, since we're not redirecting the user to an SSO service, we need to specify what information we need the user to provide. The `GetNonIDPRedirect` returns an array of fields that we want the user to specify: ```go theme={"system"} GetNonIDPRedirect: func(message authstructs.GetNonIDPRedirectMessage) authstructs.GetNonIDPRedirectMessageResponse { return authstructs.GetNonIDPRedirectMessageResponse{ Success: true, RequestFields: []string{"username", "password", "OTP"}, } }, ``` This generates three fields in Mythic's logon page for `username`, `password`, and `OTP`. Once the user provides their data and click "login", then the containers `ProcessNonIDPResponse` function is called. Just like in the IDP SSO Services function, at the end of this we need to return if auth was successful or not and the email of the user that was authenticated. # Chat Containers Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/chat Build provider-neutral AI chat services for Mythic operation channels A chat container receives an operator prompt, calls a model or tool system, and streams structured responses back to a Mythic AI chat channel. The Python `mythic-container` library provides the v4 chat base class and response helpers. ## Minimal container ```python theme={"system"} from mythic_container.ChatBase import ( Chat, ChatConfigView, ChatModelDefinition, ChatModelMetadata, ChatRequest, ChatSecretView, ) class MyChat(Chat): name = "my_chat" description = "Example provider-backed chat container" semver = "0.1.0" models = [ ChatModelDefinition( Name="My Provider", Description="Streams responses from My Provider", Metadata=ChatModelMetadata(Provider="my-provider"), ) ] async def chat(self, msg: ChatRequest) -> None: config = ChatConfigView.from_request(msg) secrets = ChatSecretView.from_request(msg) api_key = secrets.required_text("MY_PROVIDER_API_KEY") model = config.text("MY_PROVIDER_MODEL", "default-model") response_key = f"assistant:my-provider:{msg.RequestID}" await self.send_streaming(msg, response_key, metadata={"model": model}) async for delta in stream_provider(api_key, model, msg.Prompt): await self.send_delta(msg, response_key, delta, metadata={"model": model}) await self.send_complete(msg, response_key, complete_request=True) ``` One `chat(msg)` call handles one Mythic request. An approval or input continuation arrives as a new `ChatRequest`, not as a callback into the old coroutine. ## Request shape ```json theme={"system"} { "container_name": "my_chat", "operation_id": 7, "channel_id": 44, "apitokens_id": 91, "request_id": 1234, "model": "My Provider", "prompt": "Summarize active callbacks", "config": {"MY_PROVIDER_MODEL": "model-a"}, "secrets": {"MY_PROVIDER_API_KEY": {"value": "..."}}, "context": [], "slash_command": null, "input_response": null, "delegation_id": "", "delegation_name": "" } ``` Never copy secrets or Bearer tokens into response content or metadata. ## Streaming response shape ```json theme={"system"} { "operation_id": 7, "request_id": 1234, "response_key": "assistant:my-provider:1234", "content": "partial text", "is_delta": true, "complete": false, "complete_request": false, "status": "streaming", "error": "", "metadata": {"provider": "my-provider", "model": "model-a"} } ``` Reuse one `response_key` for updates to the same visible block. Use another key for tool cards, approval cards, or a separate final answer. ## Human-in-the-loop and tools Well-known `metadata.special_type` values let Mythic render native UI: * `tool_use` for tool progress and lazily fetched full output; * `mcp_tool_confirmation` for an approval before a write-capable MCP call; * `input_requested` for free-form or choice-based operator input; * `subagent` for delegated work grouped by `delegation_id`. When a tool requires approval, finish the current request after sending the confirmation card. If approved, Mythic sends a new request containing the confirmed tool call. Rebuild typed configuration and run only that approved call. ## Cancellation The library tracks active request tasks and cancels them when Mythic sends a cancellation. Provider clients should propagate cancellation to their HTTP/streaming call and avoid sending a second terminal completion after cancellation. For a complete reference implementation, use the `basic_chat` example and the `CHAT_CONTAINERS.md` guide in the `MythicContainerPyPi` repository matching your installed library version. # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/eventing ## What is it? Eventing is a way to automatically perform actions based on some "event" within Mythic. The format and flow for this was heavily modeled after GitHub Actions, so if you're familiar with that then this should come pretty easily. Mythic 4.0 adds a visual workflow wizard, per-step approval and typed-input gates, scoped step API tokens, richer instance details, and an operator workflow for responding to paused steps. A step can wait for approval, input, or both before its action begins. ```mermaid theme={"system"} flowchart LR T["Trigger"] --> D["Dependencies satisfied"] D --> A{"Approval required?"} A -->|"yes"| WA["Wait for approval"] A -->|"no"| I{"Input required?"} WA --> I I -->|"yes"| WI["Collect typed input"] I -->|"no"| R["Run action"] WI --> R R --> O["Publish outputs"] ``` ### Components There are a few components that make up an eventing workflow. Let's see what happens when you click the "New Event Groups" but on on the left hand side and select a file/multiple files to upload: 1. The original eventing workflow file. This is a YAML, JSON, or TOML file that contains information about the entire workflow. In Mythic's GraphQL API, each `eventgroup` has a file\_id that points to this file so that the contents can always be fetched later. Clicking the image folder icon under "actions" will display this file in the UI. 2. After loading the workflow file and creating an `eventgroup`, each step is analyzed and parsed into an `eventstep` associated with that event group. 3. When an event happens and triggers a workflow, then an `eventgroupinstance` is created to track that specific instance along with `eventstepinstance` for each step. This is how Mythic is able to tell all the different pieces from each other. These instances are tracked in the table below. Once you select a workflow on the left, you'll see a bunch more information appear on the right. Let's look at that top table first: * `Author` - this is the user that uploaded the workflow file * `Created At` - this is when the user uploaded the workflow file * `Trigger` - this is what "event" within Mythic will cause this workflow to execute * `Keywords` - these are optional additional words you can use to kick off this workflow. For example, maybe you want something to execute each time there's a new callback. However, as part of something else you're doing, you want to execute this workflow anyway - you can associate a keyword with the workflow and then use that to execute the workflow at any time (more on that later). * `Context` - this is additional context about the workflow to help decide if/when it should execute. For example, with the new callback trigger, you can use this context to limit which types of payloads you want to execute. A good example would be that you want to run some situational awareness commands when there's a new callback, but you only want to do it for Windows payloads. This is `trigger_data` in [Workflow Triggers](/version-4.0/customizing/3.-consuming-containers/eventing/workflow-triggers). * `Env` - this is extra, global data you can access and pass into all of the steps in a workflow. You can set this via the `environment` keyword. * `Actions` - this is a set of additional actions you can take on this workflow * If the trigger is `manual` or `keyword`, then the `green play` button will appear here and you can manually trigger this workflow. * The popout icon allows you to see the step flow graph in a bigger view * The file image icon allows you to see the backing file for this workflow * The paperclip icon allows you to upload additional files for this workflow. These files can then be referenced from within your steps (this is how, for example, you can handle issuing tasks that might need to upload files) * The layered square icon gives context about additional services that might need to be running for this workflow to execute successfully. If this is red (with a number), then one or more additional services are needed (such as custom functions and conditional checks), but they're offline. * Graph: The big graph in the middle shows all the steps associated with this workflow and their dependencies. You can right-click any step and click "View Details" to get more contextual information about each specific step within the workflow overall. A disabled event group workflow can't run new instances, but still shows up by default in the UI. A deleted event group workflow can't create new instance and doesn't show up by default in the UI. Use the workflow editor to configure common triggers and actions, then inspect the generated YAML, JSON, or TOML before production use. The source file remains the portable definition and the UI preserves it with the event group. # Operator Context (run_as) Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/eventing/operator-context-run_as ## Event workflow actions as ...? If you're thinking about creating an eventing workflow, but curious who these actions would run as, then you're in the right spot! It shouldn't be the case where something takes actions as you without you knowing it. Mythic tracks everything that's happening, so if an action happens on your behalf that you didn't authorize, then it starts to break down that model. Instead, there's the `run_as` field in the workflow file and required consent. Let's break it down. ## bot Every time an operation is created, a "bot" account is also created and added to the operation as a standard "operator". More detail on the accounts can be found [here](/version-4.0/operators#bots). The default operational context for a workflow is the "bot" account for the operation. However, we don't want just anybody to upload workflows to do arbitrary things within Mythic. That could get dangerous. Instead, anybody can upload the workflow files, but if the execution context is for the bot, then the admin of the operation must approve it to run. At any point, the admin of the operation can go back and change prior approval to a deny or a deny to an approval. The last time it changed will always be tracked. Also, if the admin of the operation changes, then all of their prior approvals are removed and the new admin must re-approve them. Since the "bot" account is a normal operator, you can apply block lists as well. That makes it easy to block what scripts are able to execute vs what you want to require a human intervention to task. Bots are always easy to identify when assigning operators because they have a robot symbol next to their name: If you leave `run_as` blank or omit it entirely, then `bot` is the default value used. ## self A `run_as` value of `self` means that the workflow will execute under the context of the operator that uploaded it. ## trigger A `run_as` value of `trigger` means that the workflow will execute under the context of the operator that triggered it (or bot if there wasn't an explicit trigger). For this case, each operator must provide their consent or it'll fail to run for operators that don't provide consent. ## lead A `run_as` value of `lead` means that the workflow will execute under the context of the operation admin. Naturally, the operation admin must approve this before this can execute. ## anything else If you supply a value to `run_as` that's none of the above header values (bot, self, trigger, lead), then it's assumed that you're trying to run within the context of a specific operator. If the name matches an existing operator, then that operator must be part of the operation and have granted consent. If you specify the name of a bot, then the lead of the operation must grant consent first. # Steps Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/eventing/steps ## What are they? An eventgroup workflow has a trigger that causes the entire workflow to start. Once it starts, it's the individual steps that perform actions. Once a steps is complete, the next step(s) start based on which other steps they depend on. Each step has the following: * `name` - string - a unique name within the eventgroup workflow * `description` - string - a description of what the step is for * `depends_on` - array of strings - a list of other step names that this step needs to complete first before this step can start * `action` - string - the specific action to take for this step * `action_data` - dictionary - per-action specific data that is necessary for that step (ex: think payload config information for creating a payload or command name and parameters for issuing a task). * `environment` - dictionary - per-step unique environment information you want available during step execution * `inputs` - dictionary - the inputs that are going into the step that come from things outside of your knowledge when you first upload the workflow. * `outputs` - dictionary - the data you want to export from this step so that it's available to be used as part of the `inputs` of another step * `continue_on_error` - a boolean - indicates if you want to continue onto the next step if this step fails. Normally, if a step hits an error case then the entire workflow is cancelled from then on. ## inputs Each step can define inputs that are necessary for the step that may or may not be known at the time that the eventgroup workflow is uploaded to Mythic. The point of this information is that you want to use it to replace sections of your `action_data` based on other factors. A simple example is that you have a workflow that triggers on new callbacks, and you want to issue a new task to that callback. Well, in order for you to issue a task, you need to know *which* callback was just created. Inputs are a dictionary where the key is the value that's swapped out in the `action_data` or made available in actions like `custom_function` and the value indicates where the data comes from. Let's take an example to make that clearer: ### inputs example ``` name: "whoami on new callbacks" description: "automatically issue whoami on new callbacks" trigger: callback_new trigger_data: payload_types: - poseidon keywords: - poseidon_callback steps: - name: "issue whoami" description: inputs: CALLBACK_ID: env.display_id API: type: mythic.apitoken scopes: - task.write - callback.read COMMAND: shell RUBEUS_FILE_ID: upload.Rubeus.exe action: task_create action_data: callback_display_id: CALLBACK_ID params: whoami command_name: COMMAND ``` In the above example we have an eventgroup workflow called `whoami on new callbacks`, which, as you might expect, wants to `automatically issue whoami on new callbacks`. This is triggered based on `callback_new` , but limited to only the `poseidon` payload types. This can also be triggered via `keywords` using the `poseidon_callback` keyword (more on that later). This workflow has one step called `issue whoami` that takes 4 inputs, has an action of `task_create`, and has some `action_data`. #### input value keywords The inputs dictionary has keys on the left (here they're all caps, but it doesn't matter) and on the right are some specially formatted strings. You'll notice that most are in the form `X.Y`. Mythic checks these and determines if the `X` is a special keyword. The possible keywords are: * `env` - fetch the named value from the environment (including information that triggered the workflow). In the above example, we fetch `display_id` from the callback information that triggered the workflow and store it in the `CALLBACK_ID` variable for use in `action_data.` * If you're ever curious what data is automatically available to you in this field, you can trigger the event, then right click one of the steps and click "View Details". The first dropdown, "Original and Instance Metadata", will show inputs, outputs, and environment data. * `upload` - this allows you to specify the name of a file that has been uploaded to Mythic. The resulting value is the `agent_file_id` UUID value for that file or `""` if it doesn't exist. * `download` - this allows you to specify the name of a file that was downloaded to Mythic. The resulting value is the `agent_file_id` UUID value for that file, or `""` if it doesn't exist. * `workflow` - this allows you to specify the name of a file that was uploaded as part of the workflow. You can see these files and upload/remove them by clicking the paperclip icon in the `actions` column when viewing the eventgroup workflow. * `mythic` - this allows you to get various pieces of information from Mythic that you can't get elsewhere. This will expand over time, but currently the only options for after the `.` are the following: * `apitoken` - this generates an API token that exists only for this step. In v4, use an object with `type: mythic.apitoken` and a non-empty `scopes` array. Any actions taken with the token are tracked in the step details, and Mythic invalidates it when the step completes. ```yaml theme={"system"} inputs: API_TOKEN: type: mythic.apitoken scopes: - callback.read - task.write - response.read ``` The legacy scalar shorthand requests broad `*` access and exists only for compatibility; new workflows should always declare the smallest useful scope set. * **anything else** - if your input is of the format `X.Y`, but doesn't match one of the above keywords, then Mythic checks if `X` matches the name of a step. If so, then it looks for an `output` key in that step that matches `Y`. If that exists, then Mythic will swap it out. If either of those conditions aren't met though, then just the static valued is used. After going through to find the values for the various `inputs` fields, the corresponding data is replaced within the `action_data` - for example, `CALLBACK_ID` from our `inputs` key matches the `CALLBACK_ID` in the `action_data`, so it gets swapped out. Same with `COMMAND`. ## Actions * `payload_create` - This action allows you to start building a payload. This action is over once the payload finishes (success or error). * `action_data` - a dictionary of the data used to create a new payload. This is the same sort of data when you click to "export" a payload's configuration on the payloads page. * `description` - the description for the new payload * `payload_type` - the name of the payload type for the payload * `selected_os` - the name of the selected operating system * `filename` - the name of the file you want at the end * `wrapped_payload` - if you're wrapping another payload, specify the wrapped payload's UUID here * `c2_profiles` - an array of c2 profile data which is as follows: * `c2_profile` - the name of the c2 profile * `c2_profile_parameters` - a dictionary of the parameters for the c2 profile in key-value format * `build_parameters` - an array of build parameter dictionary values as follows: * `name` - the name of the build parameter * `value` - the value of the build parameter ex: ``` steps: - name: "apollo bin" description: "generate shellcode" action: "payload_create" action_data: payload_type: "apollo" description: "apollo test payload shellcode" selected_os: "Windows" build_parameters: - name: "output_type" value: "Shellcode" filename: "apollo.bin" c2_profiles: - c2_profile: "websocket" c2_profile_parameters: AESPSK: "aes256_hmac" callback_host: "ws://192.168.0.118" tasking_type: "Push" commands: - shell - exit - load outputs: PayloadUUID: "uuid" environment: - name: "apollo service" description: "service exe with apollo shellcode" action: "payload_create" inputs: WRAPPER_UUID: "apollo bin.PayloadUUID" depends_on: - "bin opsec checker" action_data: payload_type: "service_wrapper" description: "apollo service exe" selected_os: "Windows" build_parameters: - name: "version" value: "4.0" - name: "arch" value: "x64" filename: "apollo_service.exe" wrapped_payload: WRAPPER_UUID outputs: PayloadUUID: "uuid" ``` * `callback_create` - This action allows you to create a new callback. * `action_data` - a dictionary of data used to create a new callback: * `payload_uuid` - the payload UUID that was used to create this callback * `c2_profile` - the name of the c2 profile that was used to "create" this callback * `encryption_key` - base64 bytes of an optional encryption key to use * `decryption_key` - base64 bytes of an optional decryption key to use * `crypto_type` - string type of crypto to use (just like you'd select from the dropdown menu when generating a payload) if you want to use something other than what was selected for your payload/c2 profile. * `user` - the username for the callback context * `host` - the hostname of the computer where the callback is executing * `pid` - the pid of the callback process * `extra_info` - a string of any extra information you want to save/store about this callback * `sleep_info` - a string of extra information you want to save/store about the sleep context for this callback (interval, jitter, skew, etc) * `ip` - the ip of the callback (if you only collect one) * `ips` - an array of ip strings * `external_ip` - if you know your external ip and want to supply it * `os` - specific os information about the host * `domain` - the domain name associated with the host * `architecture` - the architecture of the process * `description` - a custom description for the callback * `process_name` - the name of the binary that's backing your process execution * `task_create` - This action allows you to create a new task. This action is over once the task finishes (success or error). * `action_data` - a dictionary of the data used to create a new task: * `callback_display_id` - the display id of the callback to task * `command_name` - the name of the command you want to issue * `payload_type` - if you're leveraging a command\_augment container and the command name isn't unique, then specify which payload\_type you are referring to here. * `params` - if you just have a string of parameters to supply, you can provide that here * `params_dictionary` - if your command optionally supports providing structured data, provide a dictionary here of your parameter names -> values * `parameter_group_name` - if you are using parameter groups and want to explicitly say which one to use, put that here * `token` - if you're using a token associated with the callback, specify that here. If you don't put the token id then you won't use a token at all * `parent_task_display_id` - if you're wanting to issue a task as a subtask of another task, specify the operation-scoped display ID of the parent task here * `is_interactive_task` - if the task you're issuing is part of an ongoing interactive task, specify that here (and be sure to specify the `parent_task_display_id` too, which would be the one that started the interactive tasking session) * `interactive_task_type` - if this is an interactive task, specify what kind of task input it is as an int value * `resolve_task_references` - set to `true` when the task's parameter values should expand v4 `@cred` and `@link` references before tasking ``` action_data: callback_display_id: CALLBACK_ID params: whoami command_name: COMMAND ``` * `custom_function` - This action allows you to execute a custom function within a custom `event` container that you create or install. The benefit here is that you can get access to the entirety of Mythic's Scripting and GraphQL API by using an input of `mythic.apitoken` . This allows you to do almost anything. * `action_data` - dictionary with the following two things. As part of the function execution, you get access to the environment, inputs, and action data, so there's plenty of ways to get additional context and information for your custom function. * `container_name` - the name of the container that has the custom function you want to execute * `function_name` - the name of the function you want to execute within that container * `conditional_check` - This action allows you to run a custom function within a custom `event` container that you create or install with the purpose of identifying if certain steps should be skipped or not. Normally, if a step hits an error then the entire workflow is cancelled, but you can use this `conditional_check` to run custom code to determine if potentially problematic steps should be skipped or not. * `action_data` - dictionary with the following three things. As part of the function execution, you get access tot he environment, inputs, and action data, so there's plenty of ways to get additional context and information for your custom function. * `container_name` - the name of the container that has the custom conditional check you want to execute * `function_name` - the name of the conditional check you want to execute within that container * `steps` - an array of step names that you want to potentially skip based on the result of this function call * `task_intercept` - This allows you to intercept a task after the task's `opsec_post` function finishes to have one final opportunity to block the task from executing. This can only be used in conjunction with the `task_intercept` trigger. You can have additional steps as part of the workflow, but one step must be `task_intercept` if you have a trigger of `task_intercept`. * `action_data` - dictionary with the following. You get access to the environment, inputs, action data, and taskid so that you can fetch more information about the task and use GraphQL to perform any additional actions you might need. * `container_name` - the name of the container that has the task intercept function you want to execute. There can only be one per container, so you don't need to specify a function name. * `response_intercept` - This allows you to intercept the `user_output` response of an agent before it goes to the Mythic UI for an operator. Just like with `task_intercept`, this must exist if you have a trigger of `response_intercept` and can't be used if that's not the trigger. This allows you to get access to the output that the agent returned, then modify it before forwarding it along to Mythic. This means you can modify the response (add context, change values, etc) before the user ever sees it. Tasks with output that's been intercepted will have a special symbol next to them in the UI. * `action_data` - a dictionary with the following. You get access to the environment, inputs, action data, and response id so that you can fetch more information about the response (and thus task) to use with GraphQL to perform any additional actions you might need. * `container_name` - the name of the container that has the response intercept function you want to execute. There can only be one per container, so you don't need to specify a function name ## outputs Outputs from a step allow you to expose something from one step to another step. Say for example that you have a step that creates a new payload, but you want to use that new payload's UUID as input for the next step that creates a wrapper around that payload. You need some way to expose that specific UUID (there might be multiple if you're creating multiple payloads). This is where outputs come into play. We've already seen from inputs in that last example, **anything else**, where prior steps' outputs can be examined to look for values to use as inputs to new steps. When Mythic processes outputs for a step, it loops through certain data depending on the action: * `payload_create` - Currently, the following are available as keyword outputs: * `uuid` - this returns the UUID of the payload that was created * `build_phase` - this returns the string build phase of the payload that was created * `id` - this returns the ID of the payload that was created * `task_create` - Currently, the following are available as keyword outputs: * `status` - the string status of the task * `params` - the string parameters that were passed down to the agent * `original_params` - the original parameters that were supplied as a string * `command_name` - the name of the command that was sent down to the agent (if any) * `display_id` - the display id integer of the task that was created * `token` - the token id associated with the task if any * `parent_task_id` - the id of the parent task associated with this task (if any) * `agent_task_id` - the UUID of the task that the agent would see * `callback_create` - Currently, the following are available as keyword outputs: * `display_id` - the display ID of the callback that was created * `agent_callback_id` - the UUID of the callback that was created that agents see * `id` - the int ID of the callback that was created Everything else that's returned by a step in "outputs" is unmodified. Let's take an example: ``` steps: - name: "apollo bin" description: "generate shellcode" action: "payload_create" action_data: payload_type: "apollo" description: "apollo test payload shellcode" selected_os: "Windows" build_parameters: - name: "output_type" value: "Shellcode" filename: "apollo.bin" c2_profiles: - c2_profile: "websocket" c2_profile_parameters: AESPSK: "aes256_hmac" callback_host: "ws://192.168.0.118" tasking_type: "Push" commands: - shell - exit - load outputs: PayloadUUID: "uuid" Whatever: "my thing" ``` In the above example, we have an action of `payload_create`, which means we have a few output keywords that can be replaced. In our `outputs` dictionary, we have `PayloadUUID` that should have the value of `uuid` (this is one of our keywords specifically for `payload_create`), so this value is swapped out with the UUID of the payload we created. We have another output, `Whatever`, that has a value of `my thing` (this isn't one of our keywords, so it's left as is). This means another step that comes after this one can do something like this: ``` inputs: WrapperPayloadUUID: "apollo bin.PayloadUUID" APIToken: type: mythic.apitoken scopes: - payload.read ``` Notice we have an input into a step called `WrapperPayloadUUID` that needs the value of the `PayloadUUID` output from the step `apollo bin`. We also request a scoped API token that exists only for this step. ## User interaction gates A step can pause before its action and wait for approval, typed input, or both. The response is recorded with the step instance, then submitted values are merged into that step's runtime `inputs` before the action executes. ```yaml theme={"system"} steps: - name: "deploy selected payload" action: task_create inputs: CALLBACK_ID: env.display_id user_interaction: approval_required: true approval_prompt: "Approve deployment to this callback?" approval_policy: bot_context: approver: lead input_required: true input_prompt: "Confirm the deployment options." inputs: - name: mode type: ChooseOne required: true description: "Choose the execution mode" default_value_source: custom default_value: standard choices: - standard - guarded - name: reason type: string required: true description: "Record the operator's reason" default_value_source: custom default_value: "" action_data: callback_display_id: CALLBACK_ID command_name: deploy params_dictionary: mode: mode reason: reason ``` Supported input types are `string`, `number`, `boolean`, `json`, and `ChooseOne`. `default_value_source: custom` uses the literal `default_value`. A source such as `lookup.default_choice` resolves from a prior step's output; for `ChooseOne`, a prior output can also provide the `choices` list. When a workflow runs as a bot, `approval_policy.bot_context.approver` can be `operator` (any operation operator or lead) or `lead` (operation lead only). Human-started runs remain associated with their initiating operator. Operators can approve, reject, or submit requested values from the eventing instance and the corresponding special operation-chat message. # Workflow Triggers Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/eventing/workflow-triggers ## What are they? Triggers are the "events" that kick off your workflow. They typically involve something "happening" within Mythic's sphere of influence and sometimes allow you to add some additional context via `trigger_data`. `trigger_data` isn't set in stone and can be expanded upon over time. If you have additional ideas for trigger data, let me know! ## Trigger Options * `manual` - This workflow is triggered manually in the UI via the green run icon. * `trigger_data` - N/A * `keyword` - This workflow is triggered by a keyword and optional dictionary of contextual data. * `trigger_data` - dictionary of any extra data you want to send along. Normally, this is an *extra* way of triggering a workflow that's normally triggered in another way. In that case, you should probably pass along in the `trigger_data` whatever your workflow normally expects. * `mythic_start` - This workflow is triggered when Mythic starts. * `trigger_data` - N/A * `cron` - This workflow is triggered on a cron schedule. * `trigger_data` - Dictionary with the following keys: * `cron` - a normal `cron` string indicating when you want to execute this workflow. This is a handy place to check out for cron execution strings ([https://crontab.guru/](https://crontab.guru/)). * `payload_build_start` - This workflow is triggered when a Payload first starts being built. * `trigger_data` - Dictionary with the following keys: * `payload_types` - a list of all the payload types where you *want* this to trigger. If you don't specify any, then it will trigger for *all* payload types. * `payload_build_finish` - This workflow is triggered when a Payload finishes being built (either successfully or with an error). * `trigger_data` - Dictionary with the following keys: * `payload_types` - a list of all the payload types where you *want* this to trigger. If you don't specify any, then it will trigger for *all* payload types. * `task_create` - This workflow is triggered when a Task is first created and sent for preprocessing. * `trigger_data` - N/A * `task_start` - This workflow is triggered when a Task is picked up by an agent to start executing. * `trigger_data` - N/A * `task_finish` - This workflow is triggered when a Task finishes (at any point in the task lifecycle) either successfully or with an error. * `trigger_data` - N/A * `user_output` - This workflow is triggered when a Task returns new output in the `user_output` field for the user to see in the UI. * `trigger_data` - N/A * `file_download` - This workflow is triggered when a file finishes downloading from a callback. * `trigger_data` - N/A * `file_upload` - This workflow is triggered when a file finishes uploading to Mythic. * `trigger_data` - N/A * `screenshot` - This workflow is triggered when a screenshot finishes downloading from a callback. * `trigger_data` - N/A * `alert` - This workflow is triggered when an agent sends an alert back to Mythic. * `trigger_data` - N/A * `callback_new` - This workflow is triggered when a new callback is created. * `trigger_data` - Dictionary with the following keys: * `payload_types` - a list of all the payload types where you *want* this to trigger. If you don't specify any, then it will trigger for *all* payload types. * `task_intercept` - This workflow is triggered after a Task finishes its `opsec_post` check to allow one more chance for a task to be blocked. * `trigger_data` - N/A * `response_intercept` - This workflow is triggered when a Task returns new output in the `user_output` field for the user to see in the UI, but first passes that output to this workflow for modification before saving it in the database. * `trigger_data` - N/A # Logging Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/logging ## Logging Structure Many organizations that use Mythic have a need for the logs generated from it, either for tracking user actions, deconflictions, or as part of operations (such as purple teaming). Different teams also have different levels of detail needed from logs, different formats, and even different output styles (stdout, files, direct to a SIEM, etc). Because of this, Mythic outsources *most* of its logs to logging containers that can subscribe to various events and then do whatever they want with the data. The nice thing about having this as part of a container that's hooked up to Mythic is that you can get the initial logging message and then turn around and use MythicRPC and Mythic's Scripting to augment that data with more context that you might need before shipping it off. ```go Go theme={"system"} type LoggingDefinition struct { Name string Description string LogToFilePath string LogLevel string LogMaxSizeInMB int LogMaxBackups int NewCallbackFunction func(input NewCallbackLog) NewCredentialFunction func(input NewCredentialLog) NewKeylogFunction func(input NewKeylogLog) NewFileFunction func(input NewFileLog) NewPayloadFunction func(input NewPayloadLog) NewArtifactFunction func(input NewArtifactLog) NewTaskFunction func(input NewTaskLog) NewResponseFunction func(input NewResponseLog) Subscriptions []string OnContainerStartFunction func(sharedStructs.ContainerOnStartMessage) sharedStructs.ContainerOnStartMessageResponse } ``` and creating it: ```go theme={"system"} myLoggerName := "my_logger" myLogger := loggingstructs.LoggingDefinition{ NewCallbackFunction: func(input loggingstructs.NewCallbackLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input) }, NewTaskFunction: func(input loggingstructs.NewTaskLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input.Data) }, NewPayloadFunction: func(input loggingstructs.NewPayloadLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input.Data) }, NewKeylogFunction: func(input loggingstructs.NewKeylogLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input.Data) }, NewCredentialFunction: func(input loggingstructs.NewCredentialLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input.Data) }, NewArtifactFunction: func(input loggingstructs.NewArtifactLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input.Data) }, NewFileFunction: func(input loggingstructs.NewFileLog) { loggingstructs.AllLoggingData.Get(myLoggerName).LogInfo(input.Action, "data", input.Data) }, } loggingstructs.AllLoggingData.Get(myLoggerName).AddLoggingDefinition(myLogger) ``` In this example we're just using the built-in logger and writing to stdout. Most of these fields in the definition are pretty self explanatory. You don't need to fill out `subscriptions` though - that is auto populated based on which functions you provide and is used to update the MythicUI to indicate what logs you're collecting. In the Go example above and screenshot below, we didn't register a function for new responses, so in the UI you can see that the "test" button for new responses is disabled. # Webhooks Source: https://docs.mythic-c2.net/version-4.0/customizing/3.-consuming-containers/webhooks ## Webhook Structure Webhooks are notified of certain events in an asynchronous manner and submit that data to pre-configured webhook URLs. Webhooks can take advantage of everything you can do via Scripting by using the MythicRPCAPITokenCreate functionality. This function provides a temporary, trackable API token that can be used to interact with the GraphQL API. The benefit here is that if you want or need more information than what's directly provided by the webhook message, you can fetch it from GraphQL. ```go Go theme={"system"} type WebhookDefinition struct { Name string Description string WebhookURL string WebhookChannel string NewFeedbackFunction func(input NewFeedbackWebookMessage) NewCallbackFunction func(input NewCallbackWebookMessage) NewStartupFunction func(input NewStartupWebhookMessage) NewAlertFunction func(input NewAlertWebhookMessage) NewCustomFunction func(input NewCustomWebhookMessage) Subscriptions []string OnContainerStartFunction func(sharedStructs.ContainerOnStartMessage) sharedStructs.ContainerOnStartMessageResponse } ``` for example: ```go theme={"system"} func Initialize() { myWebhooks := webhookstructs.WebhookDefinition{ Name: "my_webhooks", Description: "default webhook for slack example", NewFeedbackFunction: newfeedbackWebhook, NewCallbackFunction: newCallbackWebhook, NewStartupFunction: newStartupMessage, } webhookstructs.AllWebhookData.Get("my_webhooks").AddWebhookDefinition(myWebhooks) } ``` There's also a built-in function you can leverage to get the webhook url and channel: ```go theme={"system"} func newCallbackWebhook(input webhookstructs.NewCallbackWebookMessage) { newMessage := webhookstructs.GetNewDefaultWebhookMessage() newMessage.Channel = webhookstructs.AllWebhookData.Get("my_webhooks").GetWebhookChannel(input, webhookstructs.WEBHOOK_TYPE_NEW_CALLBACK) var webhookURL = webhookstructs.AllWebhookData.Get("my_webhooks").GetWebhookURL(input, webhookstructs.WEBHOOK_TYPE_NEW_CALLBACK) if webhookURL == "" { logging.LogError(nil, "No webhook url specified for operation or locally") go mythicrpc.SendMythicRPCOperationEventLogCreate(mythicrpc.MythicRPCOperationEventLogCreateMessage{ OperationId: &input.OperationID, Message: "No webhook url specified, can't send webhook message", MessageLevel: mythicrpc.MESSAGE_LEVEL_WARNING, }) return } ... } ``` * `Name` - this is the name of your webhook container * `Description` - this is the description for your container (probably provides insight if you're going to submit to Slack, Discord, or some other service) * `WebhookURL` - this is an optional URL you can configure for the actual webhook to use. Configuring it here makes it take the highest precedence when it comes time to actually send the webhook, but has the downside of being hardcoded. You can also optionally configure this on a per-operation basis in your Operation in the UI. The last place you can configure this is in the .env file for Mythic as the ``` WEBHOOK_DEFAULT_URL ``` variable. * `WebhookChannel` - similar to the `WebhookURL`, this is the channel you're going to send your webhook. This can also be configured via the `.env` as a series of `WEBHOOK_DEFAULT_*_CHANNEL` to allow you to configure a different channel per type of notification. * The `*Function`s are what get executed when an event of that type happens. If, for example, you don't want to handle processing NewFeedback messages from Mythic, then you can simply not provide a function here (or set it to `nil` / `None` explicitly) and Mythic won't even bother sending the notification down to your container. * `OnContainerStartFunction` - this allows you to perform additional processing/setup when your container comes online and syncs up with Mythic. You get a temporary (5min) Spectator token **for each active operation**. This means that if there are two active operations in Mythic, then this function gets called **twice**, once for each operation. This is so that if you need to do some sort of configuration that's specific to an operation, you can fetch data for that operation. # 4. Extending Agent Commands Source: https://docs.mythic-c2.net/version-4.0/customizing/4.-extending-agent-commands ## What is this? Sometimes you want to use public agents, but you *also* have some internal-specific commands you'd like to use with the agent as well. You might also have a set of commands you want to use across a variety of agents, but don't want to have to create/maintain it for each agent. This is where the new "command\_augment" container comes into play. ## What does this mean? When creating a Payload type there's an option to specify an "AgentType". Normally this defaults to "agent" and you have a normal Payload Type. You could also change this to "wrapper" and have a wrapper payload type. There's also a "service" type you can set so that Mythic's UI doesn't expect you to have any C2 associated with the profile, but instead you to interact with 3rd party services. This is one more kind of "AgentType" called "command\_augment". This works exactly the same as normal "agent" Payload Types *except* you don't actually build anything. Instead, the commands you register are "injected" into callbacks of other Payload Types. Naturally, these commands either need to be self contained (i.e. they set their completed status to true and never get sent down to the agent) or they need to eventually result in some other command getting executed (i.e. spawning subtasks or changing their CommandName in their create tasking function so that the agent executes a different function). ```python Python theme={"system"} class JXAAugment(PayloadType): name = "jxa_augment" file_extension = "js" author = "@its_a_feature_" supported_os = [SupportedOS.MacOS] agent_type = AgentType.CommandAugment note = """This payload uses JavaScript for Automation (JXA) for execution on macOS boxes.""" supports_dynamic_loading = True ``` ```go Go theme={"system"} var payloadDefinition = agentstructs.PayloadType{ Name: "MyCustomCommands", Author: "@its_a_feature_", SupportedOS: []string{agentstructs.SUPPORTED_OS_LINUX, agentstructs.SUPPORTED_OS_MACOS}, Description: "Extra commands I want added to all agents", AgentType: agentstructs.AgentTypeCommandAugment, MessageFormat: agentstructs.MessageFormatJSON, } func Initialize() { agentstructs.AllPayloadData.Get("MyCustomCommands").AddPayloadDefinition(payloadDefinition) } ``` Notice all we do in this example is say which kinds of callback OS we want these commands to get injected into and we specify the AgentType to be CommandAugment. We don't actually specify any build function. You can choose to have your commands injected by one or both of the following options: * `SupportedOs` - the supported OS can be used as part of your command augment payload type definition to limit yourself to only certain operating systems * `CommandAugmentSupportedAgents` - you can provide a list of agent names here and only have your commands injected into callbacks based on these agents If you provide both, then both must be true (i.e. OS must match *and* payload type name must match). For every Command associated with this CommandAugment container, only the ones that have CommandParameters with `builtin` or `suggested_command` set to `True` are loaded automatically into the callback (assuming the above criteria are met). If you want to allow others to be loaded or not, you need to include your own `load/unload` commands within the CommandAugment container. If you're not actually adding new commands that need to be sent down to the agent for loading, but instead just need to make Mythic aware of new commands that can be issued, then you can use the MythicRPCCallbackAddCommand functionality to just let Mythic know. ## What does this look like? In the Callback metadata table, you can see these commands automatically injected: You can also see them in the add/remove commands for callbacks: You can see which specific backing container the command is associated with. There is currently no de-duplication of command names between what's part of the Payload Type itself and what you inject with CommandAugment containers. You can see above that there's two `ls` commands. When tab completing on the command line, you'll cycle through both of them, but there's currently no easy way to tell which is which. So, it's recommended that if you want to inject your own commands into a callback, that you do it in a way that makes it clear what your injected commands are for the operator. ## Command Wrapping One common use case for a command augmentation container is to provide a wrapper, or meta command around an already existing command in an agent. For example, if a payload type has the ability to execute .net assemblies or BOF/COF files already, but you want to extend this capability with your own specific files and named parameters, then this is a perfect way to do it. You create your command augmentation container, and get your commands registered within the callback based on the above criteria. So now let's say you have a command, `Rubeus`, that you want to expose from your `command_augment` container, but you want to ultimately have `apollo`'s `execute_assembly` function execute it. How do you do this? In your `Rubeus`'s create\_tasking function, you'd set the `response.CommandName = "execute_assembly"` to make sure we're going to pass context to the right command (this also makes it so that when the callback picks up the task, it sees `execute_assembly` instead of `Rubeus`). The next piece is to make sure that `apollo`'s `execute_assembly` 's create\_tasking function gets a chance to process this data. To do that, we specify `response.ReprocessAtNewCommandPayloadType = "apollo"`. This tells Mythic that instead of going to process `Rubeus`'s opsec\_post function next, it should first pass execution to `apollo`'s container, specifically for the `execute_assembly` command. Your next question is probably something along the lines of: "how do I know to pass execution to apollo or to its execute\_assembly vs some other payload type"? As part of your `create_go_tasking` function, you have access to `taskData`. There's a distinction in the data for this variable passed in: ``` taskData.PayloadType <-- the payload type associated with the callback that's running taskData.CommandPayloadType <-- the payload type associated with the command that's executing ``` It's a small nuance, but important. The `taskData.PayloadType` always refers to the payload type that created the payload that resulted in the callback that's being tasked. `taskData.CommandPayloadType` refers to the payload type associated with the command that's executing. In most situations, these two would be the same. You're in a callback from an `apollo` payload type, you run a command from `apollo`, and so they match. When we start introducing `command_augmentation` containers though, you could be executing a command from a command augmentation container *from* a callback spawned by `apollo`, `merlin`, `apfell`, etc. So that answers the first question - how do you know which payload type you should be passing execution off to (if needed). Identifying the specific command within that other payload type to execute is a *little* more complicated, but not by much. You have a few options. 1. You could have something hard-coded so that if you see `apollo`, you run `execute_assembly`, if you see `blah`, you run `custom_blah_blah`. That works, but doesn't make your command augmentation container really usable by any other base agents unless the operator modifies your source code. Not great. 2. You could have some sort of config file you use (that an operator is able to view/edit in the UI) where you keep a mapping of agent -> command. That makes it an easy lookup and is something that an operator can modify to make your command augmentation container support arbitrary other agents as well. Good, but still requires some operator config. 3. You can leverage `supported_ui_features`. These are used all throughout Mythic and browser scripts, but they're not limited to just that. The great thing is that these are also fully customizable. The down side is that the agent has to explicitly set these. So it doesn't work great for ad-hoc supporting new agents, but for known agents, it makes things pretty quick. 1. All you'd do is say, for example, that your `command_augment` container X, `CA_X`, offers the ability to provide wrappers for BOFs. You identify how you want to find commands that support your command's output, for example, you could say that the command needs a supported\_ui\_feature of `CA_X:bof` (completely arbitrary value, just make it unique). Then, if `apollo` wanted to expose its `execute_coff` command, it would set in its `supported_ui_features = ["CA_X:bof"]`. Then, in the command augment container, you can do a `SendMythicRPCCallbackCommandSearch` and specifically search for any loaded commands in the callback with your ui feature (`CA_X:bof`). Once you find a matching command, you can set your `response.CommandName` to that command name. # 5. Custom Browsers Source: https://docs.mythic-c2.net/version-4.0/customizing/5.-custom-browsers ## What is this? Custom Browsers allow you to collect and represent data in a "file browser"-like view, but for anything that has a hierarchical/tree structure to it. ## What does this mean? Custom Browsers are a new kind of container you can create that define how the "browser" view should work. Any agent is then able to "hook" into these browsers by reporting data back in a specific way. ```python Python theme={"system"} class LdapBrowser(CustomBrowser): name = "ldap_browser" description = "A browser for LDAP information." author = "@its_a_feature_" semver = "0.0.1" indicate_partial_listing = False show_current_path = True path_separator = "," row_actions = [ CustomBrowserRowAction( Name="Set Attribute", UIFeature="ldap_browser:set_attribute", SupportsFile=True, SupportsFolder=True, OpenDialog=True, GetConfirmation=False, Icon="fa-pen-to-square", Color="warning" ) ] columns = [ CustomBrowserTableColumn( Key="samaccountname", Name="Account", FillWidth=True, Width=100, DisableSort=False, DisableFilterMenu=False, DisableDoubleClick=False, ColumnType=CustomBrowserTableColumnType.String ), CustomBrowserTableColumn( Key="description", Name="Description", FillWidth=True, Width=100, DisableSort=False, DisableFilterMenu=False, DisableDoubleClick=False, ColumnType=CustomBrowserTableColumnType.String ) ] default_visible_columns = [ "Account", "Description" ] extra_table_inputs = [ CustomBrowserExtraTableTaskingInput( Name="query", DisplayName="Query", Description="LDAP Query", Required=True, ), CustomBrowserExtraTableTaskingInput( Name="attributes", DisplayName="Attributes", Description="Comma separated list of attributes to fetch", ) ] export_function = export_ldap_browser ``` ```go Go theme={"system"} func Initialize() { registry := custombrowserstructs.CustomBrowserDefinition{ Name: "registry_browser", Description: fmt.Sprintf("Basic Windows registry browser"), Author: "@its_a_feature_", SemVer: version, Type: custombrowserstructs.CUSTOMBROWSER_TYPE_FILE, PathSeparator: "\\", Columns: []custombrowserstructs.CustomBrowserTableColumn{ { Key: "type", Name: "Type", Type: "string", Width: 100, FillWidth: false, }, { Key: "value", Name: "Value", Type: "string", FillWidth: true, }, }, RowActions: []custombrowserstructs.CustomBrowserRowAction{ { Name: "Set Value", SupportsFile: true, SupportsFolder: true, OpenDialog: true, UIFeature: "registry_browser:set_value", Icon: "fa-pen-to-square", Color: "warning", }, { Name: "Create Key", SupportsFile: false, SupportsFolder: true, OpenDialog: true, UIFeature: "registry_browser:create_key", Icon: "fa-folder-plus", Color: "success", }, }, DefaultVisibleColumns: []string{"Type", "Value"}, IndicatePartialListingInUI: true, ShowCurrentPathAboveTable: true, } custombrowserstructs.AllCustomBrowserData.Get(registry.Name).AddCustomBrowserDefinition(registry) } ``` There's a few things to call out for what we're defining here: * `PathSeparator` - This is how Mythic is able to separate out the various pieces of your hierarchy path to create needed elements. Some examples include `\` and `/` for normal paths, but also things like `,` for an LDAP path (ex: `CN=test,DC=domain,DC=com`) * `Columns` - Since this browser view has a "tree" on one side and a table of data on the other, you can define how that table is presented. The `Columns` here define what to display. `key` is the name of the field in your `metadata` to actually display whereas `name` is the name of the column shown to the user. You can opt to give a specific default width to the column with `width` or specify `fillWidth` to True and tell Mythic to use the remaining space. There's a few other attributes to allow you to optionally disable certain filters/sorts on the column as well. The various "types" specify what to do with the data and how to present/sort it. * `RowActions` - When right clicking a row in the table view, you can optionally provide actions that agents can hook into. The specific command is looked up via `UIFeature` like most things, but you can specify if this command is only for file or folders as well if we should `getConfirmation` (yes/no dialog) or `openDialog` to open the modal for more information. The name is whatever you want displayed when the user right-clicks the column and the icon/color are the same as that goes with browser scripting. You can identify any icon from font awesome's solid icons by name and any color you want to use as well. * `DefaultVisibleColumns` - You might have 8 different columns, but showing all of those by default at once might make things really hard to read unless you have a really wide monitor. To help with this, you can specify which columns are visible by default by their `Name`. * `IndicatePartialListingInUI` - As you've potentially noticed with the file browser, if you have some data for a "folder" but haven't explicitly listed its contents before, Mythic will show a little overlay telling you this information and prompting you to actually list the contents. This setting allows you to toggle that off. * `ShowCurrentPathAboveTable` - By default, if you click on `Desktop` in `/Users/itsafeature`, then the top bar of the table will show `/Users/itsafeature/Desktop`. That makes sense in many scenarios, but not necessarily all of them. If you'd like the user to be able to click around *without* modifying that top bar automatically, you can toggle that here. * `ExtraTableInputs` - Since this is a "file" browser, when you issue a "list" command, you by default get sent the host, full path, filename, and path that was listed. However, your browser might need or offer *more* pieces of information. For example, the `ldap` browser allows you to specify a `query` filter and an `attributes` list. To expose these options, we use the `ExtraTableInputs` array and list out the fields that we offer, along with if they're required or not. ### How does an agent hook into this? Like most things, the agent sends back data in the `responses` array, but with a new key: ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "uuid here", "custom_browser": { "browser_name": "name of the custom browser you're reporting data for", "host": "name of the host that this data is for", "update_deleted": false, "set_as_user_output": false, "entries": [ { "name": "name of this entry", "parent_path": "path of parent entry or \"\" if this is a root node", "display_path": "different path you'd want displayed in the resulting table if you want it to be different than parent_path + separator + name", "success": true, "can_have_children": true, "metadata": {}, "children": [ { "name": "name of child entry", "display_path": "display path of child entry if different than parent_path + separator + name", "can_have_children": false, "metadata": {} } ] } ] } } ] } ``` I know that seems complicated, but it's very similar to file browser data with a few tweaks: * `browser_name` - this is just the name of the browser you want to submit data for * `host` - this is the name of the host you're submitting data for, this could be anything. For example, with `ldap_browser`, the `host` field is the domain, so `dc=domain,dc=com` * `update_deleted` - set this to true if you want mythic to mark anything that used to be in these entries but isn't present here as "deleted" * `set_as_user_output` - set this to true if you want mythic to echo this entire `custom_browser` data as `user_output` to show to the user. This is helpful if you have a browser script you want to use for the data, but don't want to explicitly submit the same data as `custom_browser` and again as a string in `user_output` * `entries` - this is an array of entries, each one distinct and processed separately * `entries.name` - this is the name of a specific entry * `entries.parent_path` - this is the parent path of the specific entry without any trailing path separator. If the entry is the root node, then you can specify the `parent_path` as just `""` * `entries.display_path` - this is one of the parts that differs from the normal file browser data. * sometimes the data you want to display is organized in a hierarchical fashion, but it might not be structured in the best way. Let's look at an example for ldap: * LDAP data is structured in a hierarchical way: `CN=Administrator,CN=Users,DC=sevenkingdoms,DC=local` however, notice that the order is *backwards* from something like a file browser. In order to process this as a normal hierarchy, we need `DC=local,DC=sevenkingdoms,CN=Users,CN=Administrator`. * To account for this, we set `name` to `CN=Administrator`, `parent_path` to `DC=local,DC=sevenkingdoms,CN=Users`, but then `display_path` to `CN=Administrator,CN=Users,DC=sevenkingdoms,DC=local`. * The way this manifests is that you get a proper tree hierarchy, but when you click on any actual entry, the path displayed at the top of the table is the one that "makes sense" for the data (i.e. backwards). * `entries.success` - setting this to `true` means you'll get a green checkmark and confirmation that you successfully listed the contents of this "folder". Setting this to `false` will give a red exclamation mark. Not setting it at all (i.e. null) will result in no marking. * `entries.can_have_children` - setting this to `true` marks this entry as a "folder" in the browser sense * `entries.metadata` - this is an arbitrary dictionary of data you can specify. Your `Columns` from the Custom Browser definition have `key` values specified - those keys are expected to be in this `metadata` so that the right things can be rendered in the table for the user. You can of course have extra keys here and you can view them at any time in the UI as well. * `entries.children` - this is where you can specify any direct children for this entry * notice that the `children` array doesn't have *another* `children` field - if you want to recursively provide this data, add new `entries` ### How does an agent hook into the UI buttons for this? You have an agent can that report back the right JSON data, but you're wondering how to get your command called when the user clicks the list button in the UI for this new custom browser? This is the same as the other "browsers" - say the name of the custom browser is `ldap_browser`, so for the list button you'll have `ldap_browser:list` in your `supported_ui_features` for your command. Every other tasking capability through the `row_actions` will specify its needed UI feature (ex: `UIFeature="ldap_browser:set_attribute",`) # 1. Getting Started Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/mythic-definition/2.1.1-c2-class-definition ## What are C2 Profiles Command and Control (C2) profiles live in their own docker containers (like everything) and act as a forwarding mechanism between whatever your special sauce C2 protocol is and what the back-end Mythic server understands (HTTP POST). Their entire role in life is to get data off the wire from whatever special communications format you're using and forward that to Mythic. By defining a C2 protocol specification, other payload types can register that they speak that C2 protocol as well and easily hook in without having to do back-end changes. By having C2 protocols divorced from the main Mythic server, you can create entirely new C2 protocols more easily and you can do them in whatever language you want. If you want to do all your work in GoLang, C#, or some other language for the C2 protocol, go for it. It's all encapsulated in the C2's Docker container with whatever environment you desire. ## How does a C2 Profile work in Mythic? When we look at how C2 Profiles work within Mythic, there are two different stages to consider: 1. How is the C2 Profile defined so that Mythic can track all of the parameters and present them to the user when generating payloads. 2. How does the C2 Profile's code run so that it can listen for agent traffic and communicate with Mythic. #### Step 1 - Defining your Profile Just like with Payload Types, C2 Profiles can either run within Docker, on a separate host, or within a VM somewhere. This isn't a hard requirement, but makes it easier to share them. We're going to do all of the initial development ***without*** Docker, so put that out of your mind for now. Since we're going to do this without Docker initially, we need to change a few things on your Mythic instance: 1. `sudo ./mythic-cli config set rabbitmq_bind_localhost_only false` 2. `sudo ./mythic-cli config set mythic_server_bind_localhost_only false` 3. `sudo ./mythic-cli start` 4. `sudo ./mythic-cli config get rabbitmq_password` 5. Take note of your Mythic server IP address ```python MyC2.py theme={"system"} from mythic_container.C2ProfileBase import * import pathlib class MyC2(C2Profile): # your class name can be anything, but must subclass C2Profile name = "myc2" description = f"My Custom C2." author = "@you" is_p2p = False semver = "0.0.1" # icons aren't required, but if you do provide them, they must be svg format agent_icon_path = pathlib.Path(".") / "myc2.svg" dark_mode_agent_icon_path = pathlib.Path(".") / "myc2_darkmode.svg" server_binary_path = pathlib.Path(".") / "myc2_executable" server_folder_path = pathlib.Path(".") parameters = [ ... ] ``` ```go MyC2.go theme={"system"} var dnsc2definition = c2structs.C2Profile{ Name: "myc2", Author: "@you", Description: fmt.Sprintf("My Custom C2."), IsP2p: false, SemVer: "0.0.1", ServerBinaryPath: filepath.Join(".", "myc2_executable"), ServerFolderPath: filepath.Join("."), } func Initialize() { // note: the "myc2" here in the Get call needs to match the name of your c2 profile exactly c2structs.AllC2Data.Get("myc2").AddC2Definition(dnsc2definition) } ``` Some context about these fields: * `name` - as you might expect, this is the `name` of the profile that shows up in the Mythic UI and how payload types specify they support this format * `is_p2p` - this indicates if this is a P2P profile or an Egress profile * the difference here is that a P2P profile doesn't have any "logic" code to run since all that logic is in the agents, not this server side * `server_binary_path` - this is the path to the code (any language, just must be executable) that actually does your C2 logic * this is the code that will open ports, connect to 3rd party services, etc. * if this is a scripted language, like `python`, then you need to put a `#! python` at the top of your file specified here * `server_folder_path` - this is the path to the folder where `server_binary_path` is generally going to run * the point here is that in the UI when you click the little paperclip icon to browser the files of a container - **this** folder is what you're seeing * this is where you'll include your `config.json` file that allows an operator to configure the server side of your profile # 2. Parameters Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/mythic-definition/2.1.2-c2-parameters ## Parameters The array of parameters shown here are the ones that are presented to the operator when building their payload. These should be things that the Payload will need in order to successfully talk through your C2 Profile. What if your Server and Payload need some shared piece of data? There's a ConfigCheck function that's called that you can define here that will provide the full payload configuration. You can take this config and update something on your server-side configuration to keep parity between the two. ```python parameters example theme={"system"} parameters = [ C2ProfileParameter( name="callback_host", description="Callback Host", default_value="ws://127.0.0.1", verifier_regex="^(ws|wss)://[a-zA-Z0-9]+", required=True ), C2ProfileParameter( name="AESPSK", description="Crypto type", default_value="aes256_hmac", parameter_type=ParameterType.ChooseOne, choices=["aes256_hmac", "none"], required=False, crypto_type=True ), C2ProfileParameter( name="callback_interval", description="Callback Interval in seconds", default_value="10", verifier_regex="^[0-9]+$", required=False, ), C2ProfileParameter( name="encrypted_exchange_check", description="Perform Key Exchange", parameter_type=ParameterType.Boolean, default_value=True, required=False, ), C2ProfileParameter( name="killdate", description="Killdate for when the C2 Profile should stop working and exit the agent", default_value=365, parameter_type=ParameterType.Date, ), ] ``` ```go parameters example theme={"system"} var dnsc2parameters = []c2structs.C2Parameter{ { Name: "domains", Description: "Series of domains to use", DefaultValue: []string{"domain.com"}, ParameterType: c2structs.C2_PARAMETER_TYPE_ARRAY, Required: true, }, { Name: "killdate", Description: "Kill Date", DefaultValue: 365, ParameterType: c2structs.C2_PARAMETER_TYPE_DATE, Required: false, }, { Name: "encrypted_exchange_check", Description: "Perform Key Exchange", DefaultValue: true, ParameterType: c2structs.C2_PARAMETER_TYPE_BOOLEAN, Required: false, }, { Name: "callback_jitter", Description: "Callback Jitter in percent", DefaultValue: 23, ParameterType: c2structs.C2_PARAMETER_TYPE_NUMBER, Required: false, VerifierRegex: "^[0-9]+$", }, { Name: "AESPSK", Description: "Encryption Type", DefaultValue: "aes256_hmac", ParameterType: c2structs.C2_PARAMETER_TYPE_CHOOSE_ONE, Required: false, IsCryptoType: true, Choices: []string{ "aes256_hmac", "none", }, }, { Name: "callback_interval", Description: "Callback Interval in seconds", DefaultValue: 10, ParameterType: c2structs.C2_PARAMETER_TYPE_NUMBER, Required: false, VerifierRegex: "^[0-9]+$", }, { Name: "domain_rotation", Description: "Domain rotation pattern. Fail-over uses each one in order until it can't communicate with it successfully and moves on. Round-robin makes each request to the next host in the list.", ParameterType: c2structs.C2_PARAMETER_TYPE_CHOOSE_ONE, Choices: []string{ "round-robin", "random", "fail-over", }, }, } func Initialize() { c2structs.AllC2Data.Get("myc2").AddParameters(dnsc2parameters) } ``` ### Parameter Components So what are all these components in the Parameter definitions? * `name` - What is the name of the parameter * this is how you'll get the value for this parameter during build time * `description` - The long form description of what this value means * `default_value` - What is the default value for this parameter * `verifier_regex` - Optional feature you can use to provide a regex that indicates if a value is valid or not for the operator * `required` - Is this field required for the operator to fill out * `parameter_type` - What type of parameter is this: * `String` - you get a `string` value * `ChooseOne`` - you get a `string\` value * `ChooseOneCustom` - you get a `string` value * `ChooseMultiple` - you get an `array` of `string` values * `Array` - you get an `array` of `string` values * `Date` - you get a string representation of the Date as YYYY-MM-DD * `Dictionary` - you get a `dictionary` representation, or `map[string]string` in Go * `Boolean` - you get a `bool` value * `TypedArray` - you get an `array` of `tuples` * this will either be something like `[ ["type", "value"], ["type", "value"] ]` if the UI modal was used * this will be something like `[ ["", "user typed"], ["", "user typed"] ]` and sent to your typed\_array\_parse function in the case that the user just typed something and needs parsing into the above format * `File` - you will get a `string` UUIDv4 value that you can use to upload/download via chunking * `FileMultiple` - you will get an `array` of `string` UUIDv4 values that you can use to upload/download via chunking * `Number` - you will get an `int` in Python and a `float64` in Go by default ## Mythic 4.0 parameter fields C2 parameters now support the same display and conditional form capabilities as payload build parameters: ```python theme={"system"} from mythic_container.C2ProfileBase import ( C2ProfileParameter, C2RPCDynamicQueryC2ParameterFunctionMessage, C2RPCDynamicQueryC2ParameterFunctionMessageResponse, ComplexChoice, ParameterType, ) async def query_domains( msg: C2RPCDynamicQueryC2ParameterFunctionMessage, ) -> C2RPCDynamicQueryC2ParameterFunctionMessageResponse: region = msg.OtherParameters.get("region", "us-west") return C2RPCDynamicQueryC2ParameterFunctionMessageResponse( Success=True, ComplexChoices=[ ComplexChoice(DisplayValue=f"Primary ({region})", Value="primary.example"), ComplexChoice(DisplayValue=f"Backup ({region})", Value="backup.example"), ], ) parameters = [ C2ProfileParameter( name="region", display_name="Hosting Region", description="Region used to choose managed redirectors", parameter_type=ParameterType.ChooseOne, choices=["us-west", "us-east"], choices_display_names={"us-west": "US West", "us-east": "US East"}, default_value="us-west", ui_position=1, ), C2ProfileParameter( name="callback_host", display_name="Callback Host", description="Select a host returned by the C2 service", parameter_type=ParameterType.ChooseOne, dynamic_query_function=query_domains, ui_position=2, ), ] ``` `OtherParameters` contains the values currently selected for the other C2 parameters. Return `Choices` when the label equals the value, or `ComplexChoices` when operators should see a friendly label while the payload receives a different value. * `display_name` preserves a stable internal `name` while improving the form label. * `choices_display_names` supplies labels for static choice values. * `group_name` and `ui_position` organize the builder form. * `hide_conditions` accepts `eq`, `neq`, `in`, `nin`, `lt`, `gt`, `lte`, `gte`, `sw`, `ew`, `co`, and `nco`. * `dynamic_query_function` supplies service-backed choices. * `ParameterType.JSONString` plus `json_string_schema` provides a structured visual/source JSON editor. See [JSONString Parameters](/version-4.0/customizing/payload-type-development/payload-type-info/json-string-parameters) for the schema vocabulary and Python/Go constants. # 3. Running Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/mythic-definition/2.1.3-c2-running ## Step 1.5 Running You've defined some stuff, now let's do something with it **(without Docker)**. On your dev system where you're making this code, let's run it. We'll need a few things first though. First off, we just need to run (or compile) our definition. In Python, this just means importing the new class and calling `mythic_container.mythic_service.start_and_run_forever()`. For Go, it means calling that `Initialize` function and `MythicContainer.StartAndRunForever` with the `MythicContainer.MythicServiceC2` service specified. ```python main.py theme={"system"} import mythic_container from myc2 import * mythic_container.mythic_service.start_and_run_forever() # Run this with: python3 main.py # this assumes that `myc2.py` is in the same folder with a __init__.py folder so you can import from that file ``` ```go main.go theme={"system"} package main import ( httpfunctions "MyContainer/dns/c2functions" // import your functions, wherever they happen to live "github.com/MythicMeta/MythicContainer" ) func main() { // load up the agent functions directory so all the init() functions execute httpfunctions.Initialize() // sync over definitions and listen MythicContainer.StartAndRunForever([]MythicContainer.MythicServices{ MythicContainer.MythicServiceC2, }) } ``` There's one last thing we need to do before we can run this - indicate how this code will actually connect to Mythic. This code needs to connect up to Mythic via RabbitMQ to sync this data over and get tasking. ```makefile Python theme={"system"} BINARY_NAME?=main DEBUG_LEVEL?="debug" RABBITMQ_HOST?="127.0.0.1" RABBITMQ_PASSWORD?="PqR9XJ957sfHqcxj6FsBMj4p" MYTHIC_SERVER_HOST?="127.0.0.1" MYTHIC_SERVER_GRPC_PORT?="17444" WEBHOOK_DEFAULT_URL?= WEBHOOK_DEFAULT_CHANNEL?= WEBHOOK_DEFAULT_FEEDBACK_CHANNEL?= WEBHOOK_DEFAULT_CALLBACK_CHANNEL?= WEBHOOK_DEFAULT_STARTUP_CHANNEL?= MYTHIC_ADDRESS=http://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/agent_message MYTHIC_WEBSOCKET=ws://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/ws/agent_message run_custom: DEBUG_LEVEL=${DEBUG_LEVEL} \ RABBITMQ_HOST=${RABBITMQ_HOST} \ RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} \ MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST} \ MYTHIC_SERVER_GRPC_PORT=${MYTHIC_SERVER_GRPC_PORT} \ WEBHOOK_DEFAULT_URL=${WEBHOOK_DEFAULT_URL} \ WEBHOOK_DEFAULT_CHANNEL=${WEBHOOK_DEFAULT_CHANNEL} \ WEBHOOK_DEFAULT_FEEDBACK_CHANNEL=${WEBHOOK_DEFAULT_FEEDBACK_CHANNEL} \ WEBHOOK_DEFAULT_CALLBACK_CHANNEL=${WEBHOOK_DEFAULT_CALLBACK_CHANNEL} \ WEBHOOK_DEFAULT_STARTUP_CHANNEL=${WEBHOOK_DEFAULT_STARTUP_CHANNEL} \ MYTHIC_ADDRESS=${MYTHIC_ADDRESS} \ MYTHIC_WEBSOCKET=${MYTHIC_WEBSOCKET} \ python3 main.py ``` ```makefile Go theme={"system"} BINARY_NAME?=main DEBUG_LEVEL?="debug" RABBITMQ_HOST?="127.0.0.1" RABBITMQ_PASSWORD?="PqR9XJ957sfHqcxj6FsBMj4p" MYTHIC_SERVER_HOST?="127.0.0.1" MYTHIC_SERVER_GRPC_PORT?="17444" WEBHOOK_DEFAULT_URL?= WEBHOOK_DEFAULT_CHANNEL?= WEBHOOK_DEFAULT_FEEDBACK_CHANNEL?= WEBHOOK_DEFAULT_CALLBACK_CHANNEL?= WEBHOOK_DEFAULT_STARTUP_CHANNEL?= MYTHIC_ADDRESS=http://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/agent_message MYTHIC_WEBSOCKET=ws://${MYTHIC_SERVER_HOST}:${MYTHIC_SERVER_PORT}/ws/agent_message local: CGO_ENABLED=0 go build -o ${BINARY_NAME} . run_custom: local DEBUG_LEVEL=${DEBUG_LEVEL} \ RABBITMQ_HOST=${RABBITMQ_HOST} \ RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD} \ MYTHIC_SERVER_HOST=${MYTHIC_SERVER_HOST} \ MYTHIC_SERVER_GRPC_PORT=${MYTHIC_SERVER_GRPC_PORT} \ WEBHOOK_DEFAULT_URL=${WEBHOOK_DEFAULT_URL} \ WEBHOOK_DEFAULT_CHANNEL=${WEBHOOK_DEFAULT_CHANNEL} \ WEBHOOK_DEFAULT_FEEDBACK_CHANNEL=${WEBHOOK_DEFAULT_FEEDBACK_CHANNEL} \ WEBHOOK_DEFAULT_CALLBACK_CHANNEL=${WEBHOOK_DEFAULT_CALLBACK_CHANNEL} \ WEBHOOK_DEFAULT_STARTUP_CHANNEL=${WEBHOOK_DEFAULT_STARTUP_CHANNEL} \ MYTHIC_ADDRESS=${MYTHIC_ADDRESS} \ MYTHIC_WEBSOCKET=${MYTHIC_WEBSOCKET} \ ./${BINARY_NAME} ``` For Python, instead of doing a Makefile and providing all of the parameters via environment variables, you can create a `rabbitmq_config.json` file in the same directory as your `main.py`. This file will be automatically ingested when the `main.py` file starts. You can provide the same options here, like a `rabbitmq_password` field and a `rabbitmq_host` field with the right data filled in. At this point, you should see your `MyC2` appear in the Mythic UI under the `InstalledServices` -> `C2` tab. You'll notice that there might be a warning about being unable to start the internal server for the profile and that it's not listening for connections. Don't worry, that's expected! After all, you don't have any actual server code yet, just the Mythic definition. # 4. Server Code Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/mythic-definition/2.1.4-c2-server-code ## Step 2 - The Server Code The C2 Profile doesn't need to know anything about the actual content of the messages that are coming from agents and in most cases wouldn't be able to read them anyway since they'll be encrypted. Depending on the kind of communications you're planning on doing, your C2 Profile might wrap or break up an agent's message (eg: splitting a message to go across DNS and getting it reassembled), but then once your C2 Profile re-assembles the agent message, it can just forward it along. In most cases, simply sending the agent message as an HTTP POST message to the location specified by `MYTHIC_ADDRESS`. You'll get an immediate result back from that which your C2 profile should hand back to the agent. Your server code **MUST** send an HTTP header of `Mythic: ProfileNameHere` when forwarding your messages to `MYTHIC_ADDRESS`. This allows Mythic to know *which* profile is connecting. Mythic will try to automatically start your server file when the Mythic definition file syncs over. This same file is what gets executed when you click to "start" the profile in the UI. The C2 Profile has nothing to do with the **content** of the messages that are being sent. It has no influence on the encryption or what format the agent messages are in (JSON, binary, stego, etc). If you want to control that level of granularity, you need to check out the [10. Translation Containers](/version-4.0/customizing/payload-type-development/translation-containers). When forwarding messages to Mythic, they must be in a specific format: Base64(UUID + message). This just allows Mythic to have a standard way to process messages that are coming in and pull out the needed pieces of information. The UUID allows mythic to look up the associated Payload Type and see what needs to happen (is it a payload that's staging, is it a callback, does processing need to go to a translation container first, etc). The `message` is typically an encrypted blob, but could be anything. Your server code can be whatever you want it to be. You can write it in any language you want. # 1. Configuration Files Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.1-c2-configuration-files ## Config.json Every C2 Profile should have a `config.json` file located in their defined `server_folder_path` which contains all the fields an operator would need to configure. This is the main way for an operator to do configurations of the C2 profile without having to connect to the server where Mythic is running. This can be any format in reality, the name just has to stay the same. ## Duplicated data What if you need some piece of data as part of both this config.json for the server *and* for the payload? Making an operator supply it for the payload *and* supplying it here manually isn't great. Instead, your [config check](/version-4.0/customizing/c2-related-development/server-side-coding/2.2.3-configuration-checks) function is called with a payload's configuration in two scenarios: 1. an operator is creating a payload 2. an operator is manually trying to check their configuration In both cases, you're getting execution in that config check function with the payload's configuration. So, the easiest thing to do is to take the config here and update your config.json with that data as needed. In the `httpx` profile, there's actually a *separate* file where we're aggregating these sort of agent-side config values that are needed. You can also indicate as part of your response that you want Mythic to stop your C2 server code and restart it, that way your changes to the config.json (or other files) can be properly loaded. # 2. OPSEC Checks Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.2-opsec-checks-for-c2-profiles ## OPSEC scripting When creating payloads, Mythic will send a C2 Profile's parameters to the associated C2 Profile container for an "opsec check". This is a function that you can choose to write (or not) to look over the C2-specific parameter values that an operator selected to see if they pass your risk tolerance. This function is part of your C2 Profile's class definition: ```python theme={"system"} async def opsec(self, request: C2ProfileBase.C2OPSECMessage): response = C2ProfileBase.C2OPSECMessageResponse(Success=True) return response ``` ```go theme={"system"} OPSECCheckFunction func(message C2OPSECMessage) C2OPSECMessageResponse ``` ```go theme={"system"} package c2structs // C2_OPSEC_CHECKS STRUCTS type C2OPSECMessage struct { C2Parameters } type C2OPSECMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` Message string `json:"message"` } ``` In the end, the function is returning success or error for if the OPSEC check passed or not. ## When is this executed? OPSEC checks for C2 profiles are executed every time a Payload is created. This means when an operator does it through the UI, when somebody scripts it out, and when a payload is automatically generated as part of tasking (such as for lateral movement or spawning new callbacks). # 3. Configuration Checks Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.3-configuration-checks Check Agent's Configuration Before Generation ## What is this? Configuration checks are optional checks implemented by a C2 profile to alert the operator if they're generating an agent with a C2 configuration that doesn't match the current server configuration. ## When does this happen? This check occurs every time an agent is generated, and this output is added to the payload's `build_message`. Thus, an operator sees it when generating a payload, but it's always viewable again from the created payloads page. ## What does it look like? The function is part of your C2 Profile's class definition, so it has access to your local `config.json` file as well as the instance configuration from the agent. ```python theme={"system"} async def config_check(request: C2ProfileBase.C2ConfigCheckMessage) -> C2ProfileBase.C2ConfigCheckMessageResponse: return C2ProfileBase.C2ConfigCheckMessageResponse( Success=True, Message="Some configuration check message", ) ``` ```go theme={"system"} ConfigCheckFunction func(message C2ConfigCheckMessage) C2ConfigCheckMessageResponse ``` ```go theme={"system"} package c2structs // C2_CONFIG_CHECK STRUCTS type C2ConfigCheckMessage struct { C2Parameters } type C2ConfigCheckMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` Message string `json:"message"` } ``` # 4. Sample Message Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.4-sample-message ## What is it? It's often useful to test your C2 redirector setup before your final deployment. It's also tough to know if there's an issue, if it could be with the agent, with a redirector, or with the C2 profile itself. Because of this, it can be very helpful for a C2 profile to generate a "sample message" that should fit all of the criteria based on an agent's configuration that you can either test configurations or even include in a report about how the C2 configuration works. ## Where is it? On the created payloads page, there's an actions dropdown button next to each payload. That dropdown will contain an option to generate a sample message. This request takes that agent's configuration and forwards it along to the C2 profile. ## What does it look like? ```python theme={"system"} async def sample_message(self, inputMsg: C2SampleMessageMessage) -> C2SampleMessageMessageResponse: """Generate a sample message for this c2 profile based on the configuration specified :param inputMsg: Payload's C2 Profile configuration :return: C2SampleMessageMessageResponse detailing a sample message """ response = C2SampleMessageMessageResponse(Success=True) response.Message = "Not Implemented" response.Message += f"\nInput: {json.dumps(inputMsg.to_json(), indent=4)}" return response ``` ```go theme={"system"} SampleMessageFunction func(message C2SampleMessageMessage) C2SampleMessageResponse ``` ```go theme={"system"} package c2structs // C2_SAMPLE_MESSAGE STRUCTS // C2SampleMessageMessage - Generate sample C2 Traffic based on this configuration so that the // operator and developer can more easily troubleshoot type C2SampleMessageMessage struct { C2Parameters } // C2SampleMessageResponse - Provide a string representation of the C2 Traffic that the corresponding // C2SampleMessageMessage configuration would generate type C2SampleMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` Message string `json:"message"` } ``` # 5. File Hosting Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.5-file-hosting Add and remove operator-selected files at C2 profile URLs C2 file hosting lets an operator expose a Mythic file or payload at a path handled by a C2 profile. Mythic 4.0 manages these hosted-file records from the UI and sends add/remove batches to the profile's `host_file` function. ## Implement the handler ```python theme={"system"} from mythic_container.C2ProfileBase import ( C2HostFilesMessage, C2HostFilesMessageResponse, C2HostFilesMessageResponseFile, ) async def host_file( self, input_msg: C2HostFilesMessage ) -> C2HostFilesMessageResponse: results = [] for entry in input_msg.Files: try: if entry.Remove: await self.remove_route(entry.HostURL) else: file_bytes = await self.fetch_from_mythic( agent_file_id=entry.AgentFileID, bearer_token=entry.DownloadToken, ) await self.install_route( route=entry.HostURL, filename=entry.Filename, contents=file_bytes, ) results.append( C2HostFilesMessageResponseFile( Success=True, AgentFileID=entry.AgentFileID, HostURL=entry.HostURL, ) ) except Exception as exc: results.append( C2HostFilesMessageResponseFile( Success=False, Error=str(exc), AgentFileID=entry.AgentFileID, HostURL=entry.HostURL, ) ) return C2HostFilesMessageResponse( Success=all(result.Success for result in results), Results=results, ) ``` ```go theme={"system"} HostFileFunction func( context.Context, C2HostFilesMessage, ) C2HostFilesMessageResponse type C2HostFileMessage struct { AgentFileID string `json:"agent_file_id"` HostURL string `json:"host_url"` Remove bool `json:"remove"` DownloadToken string `json:"download_token,omitempty"` Filename string `json:"filename,omitempty"` } type C2HostFilesMessage struct { Name string `json:"c2_profile_name"` Files []C2HostFileMessage `json:"files"` } type C2HostFileMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` AgentFileID string `json:"agent_file_id"` HostURL string `json:"host_url"` } type C2HostFilesMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` Results []C2HostFileMessageResponse `json:"results,omitempty"` RestartInternalServer bool `json:"restart_internal_server,omitempty"` } ``` ## Request contract Each batch identifies the C2 profile and contains one or more files: ```json theme={"system"} { "c2_profile_name": "http", "files": [ { "agent_file_id": "4b60bd75-bcf4-4c3e-8abe-9566c23b8cb8", "host_url": "/updates/agent.bin", "remove": false, "download_token": "mctx_example_file_token", "filename": "agent.bin" } ] } ``` * `agent_file_id` is the Mythic file UUID. * `host_url` is the profile-specific route selected by the operator. * `remove` distinguishes removal from creation or refresh. * `download_token` is a file-scoped `mctx_` bearer token the C2 profile can use to fetch the file from Mythic. Mythic rotates or invalidates it as the hosted-file state changes; do not log or persist it. * `filename` is the operator-facing filename. Return one result for each requested entry so Mythic can update individual rows even when part of a batch fails. Set `RestartInternalServer` only when the profile must restart its internal server to activate the route changes. ```mermaid theme={"system"} sequenceDiagram participant O as Operator participant M as Mythic participant C as C2 profile participant S as C2 server O->>M: Add or remove hosted file M->>C: host_file(files[] + download token) alt Add or refresh C->>M: Fetch file with Bearer token C->>S: Install route and contents else Remove C->>S: Remove route end C-->>M: Per-file results M-->>O: Update hosted-file state ``` ## Operator workflow Blue globe actions on payload and file views open the C2 hosting dialog. The operator chooses a compatible C2 profile and a URL. Existing entries can be refreshed, retried, stopped, or removed from the hosted-files management view. Treat `host_url` as untrusted operator input. Normalize it and prevent path traversal or collisions according to the C2 server's routing model. # 6. Redirect Rules Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.6-redirect-rules ## What is it? This is a function operators can manually invoke for a payload to ask the payload's C2 profiles to generate a set of redirection rules for that payload. Nothing in Mythic knows more about a specific C2 profile than the C2 profile itself, so it makes sense that a C2 profile should be able to generate its own redirection rules for a given payload. These redirection rules are up to the C2 Profile creators, but can include things like Apache mod\_rewrite rules, Nginx configurations, and more. ## Where is it? Operationally, users can invoke this function from the created payloads page with a dropdown menu for the payload they're interested in. Functionally, this code lives in the class definition of your C2 Profile. ## What does it look like? This function gets passed the same sort of information that the opsec check and configuration check functions get; namely, information about all of the payload's supplied c2 profile parameter values. This function can also access the C2 Profile's current configuration. The format of the function is as follows: ```python theme={"system"} async def redirect_rules(request: C2ProfileBase.C2GetRedirectorRulesMessage) -> C2ProfileBase.C2GetRedirectorRulesMessageResponse: return C2ProfileBase.C2GetRedirectorRulesMessageResponse( Success=True, Message="some mod rewrite rules here" ) ``` ```go theme={"system"} GetRedirectorRulesFunction func(message C2GetRedirectorRuleMessage) C2GetRedirectorRuleMessageResponse ``` ```go theme={"system"} package c2structs // C2_REDIRECTOR_RULES STRUCTS type C2_GET_REDIRECTOR_RULE_STATUS = string type C2GetRedirectorRuleMessage struct { C2Parameters } type C2GetRedirectorRuleMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` Message string `json:"message"` } ``` # 7. Get IOC Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.7-get-ioc ## What is it? Since C2 profiles can vary pretty wildly, it's not always easy to know what potential indicators of compromise exist for any given c2, especially when you consider how it's modified for a very specific agent. The thing that would know *best* what kinds of IOCs exist for a given agent configuration for a C2 profile would be the C2 profile itse.f ## Where is it? The dropdown actions button for any payload will have an option to generate IOCs from the corresponding built-in C2 profiles. ## What does it look like? ```python theme={"system"} async def get_ioc(self, inputMsg: C2GetIOCMessage) -> C2GetIOCMessageResponse: """Generate IOCs for the network traffic associated with the specified c2 configuration :param inputMsg: Payload's C2 Profile configuration :return: C2GetIOCMessageResponse detailing some IOCs """ response = C2GetIOCMessageResponse(Success=True) response.IOCs = [] return response ``` ```go theme={"system"} GetIOCFunction func(message C2GetIOCMessage) C2GetIOCMessageResponse ``` ```go theme={"system"} package c2structs // C2_GET_IOC STRUCTS // C2GetIOCMessage given the following C2 configuration, determine the IOCs that a defender should look for type C2GetIOCMessage struct { C2Parameters } // IOC identify the type of ioc with Type and the actual IOC value // An example could be a Type of URL with the actual IOC value being the configured callback URL with URI parameters type IOC struct { Type string `json:"type" mapstructure:"type"` IOC string `json:"ioc" mapstructure:"ioc"` } // C2GetIOCMessageResponse the resulting set of IOCs that a defender should look out for based on the // C2GetIOCMessage configuration type C2GetIOCMessageResponse struct { Success bool `json:"success"` Error string `json:"error"` IOCs []IOC `json:"iocs"` } ``` # 8. Push C2 Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.8-push-c2 ## What is it? Push C2 is a way to do egress C2 channels without requiring the agent to beacon periodically for tasking. Instead, a connection is held open and Mythic "Pushes" tasks and data down to the agent. This is very similar to how many Peer-to-peer (P2P) agents handle connections, just extended to the egress side of things. ## How does it work? For this to work, there needs to be a held open connection between the C2 Profile docker container and the Mythic server itself. This is done via gRPC. As part of this held open connection, the C2 profile identifies itself and forwards along messages. ## What does it look like? In the mythic UI, the last checkin time will change to 1970-01-01 and appear as `Streaming Now`. The moment the held open gRPC connection disconnects, that time will update to the current UTC time. This makes it very easy to know that a connection is currently held open even if no traffic is going through it. Below are some simplified examples of working with this gRPC. An example of this Push style C2 as part of websockets is available with the `websocket` C2 profile. ```python theme={"system"} from mythic_container.grpc.pushC2GRPC_pb2_grpc import PushC2Stub from mythic_container.grpc import pushC2GRPC_pb2 as grpcFuncs import grpc.aio async def handleStreamConnection(client): global UUIDToWebsocketConn global grpcStream try: while True: grpcStream = client.StartPushC2Streaming() # first send a connect message telling Mythic who this is logger.info(f"Connected to gRPC for pushC2 Streaming") async for request in grpcStream: # this is streaming responses from Mythic to go to agents try: # do something with request except Exception as d: logger.exception(f"Failed to process handleStreamConnection message:\n{d}") logger.error(f"disconnected from gRPC for handleStreamConnection") except Exception as e: logger.exception(f"[-] exception in handleStreamConnection: {e}") async def handleGrpcStreamingServices(): maxInt = 2 ** 31 - 1 while True: try: logger.info(f"Attempting connection to gRPC for pushC2...") channel = grpc.aio.insecure_channel( f'127.0.0.1:17444', options=[ ('grpc.max_send_message_length', maxInt), ('grpc.max_receive_message_length', maxInt), ]) await channel.channel_ready() client = PushC2Stub(channel=channel) streamConnections = handleStreamConnection(client) logger.info(f"[+] Successfully connected to gRPC for pushC2") await asyncio.gather(streamConnections) except Exception as e: logger.exception(f"Translation gRPC services closed for pushC2: {e}") asyncio.run(handleGrpcStreamingServices()) ``` ```go theme={"system"} PushConn := mythicGRPC.GetNewPushC2ClientConnection() grpcClient := services.NewPushC2Client(PushConn) streamContext, cancel := context.WithCancel(context.Background()) defer func() { cancel() }() grpcStream, err := grpcClient.StartPushC2Streaming(streamContext) if err != nil { log.Printf("Failed to get new client: %v\n", err) return } else { log.Printf("Got new push client") } // sending a message from an agent to Mythic readErr = grpcStream.Send(&services.PushC2MessageFromAgent{ C2ProfileName: "websocket", RemoteIP: websocketClient.RemoteAddr().String(), TaskingSize: 0, Message: nil, Base64Message: []byte(fromAgent.Data), }) if readErr != nil { log.Printf("failed to send message to grpc stream: %v\n", readErr) grpcStream.CloseSend() return } // getting a message from Mythic fromMythic, readErr := grpcStream.Recv() if readErr != nil { log.Printf("Failed to read from grpc stream, closing connections: %v\n", readErr) grpcStream.CloseSend() return } dataContent := fromMythic.GetMessage() ``` ### Agent Expectations How is an agent supposed to work with a Push-style C2 profile? It's the same as working with a Peer-to-peer (P2P) profile: * If a payload is executed (it's not a callback yet), then reach out to the C2 profile to make a connection. Once a connection is established, start your normal encrypted key exchange or checkin process * If an existing callback loses connection for some reason, then reach out to the C2 profile to make a connection. Once a connection is established, send your checkin message again to inform Mythic of your existence * At this point, just wait for messages to come to you (no need to do a get\_tasking poll) and as you get any data (socks, edges, alerts, responses, etc) just send them out through your c2 connection. ## Types of Push C2 There are two types of Push C2. They both function largely the same, but within the C2 server they function differently. ### One-to-One The first one is probably the most common, one-to-one. This means that for every agent that connects to the C2 server (typically with a held-open connection like a WebSocket), the C2 server also opens one gRPC connection to Mythic. The following is a diagram what this means: ```mermaid theme={"system"} sequenceDiagram autonumber participant M as Mythic participant C as C2 Server participant A1 as Agent1 participant A2 as Agent2 A1 ->>+ C: Establish WebSocket1 C ->>+ M: Establish gRPC1 A2 ->>+ C: Establish Websocket2 C ->>+ M: Establish gRPC2 M ->> C: Issue task to Agent1 (gRPC1) C ->>+ A1: Issue Task to Agent1 (Websocket1) A1 -->>- C: Task Response (Websocket1) C -->> M: Task Response (gRPC1) A2 -->> C: Agent2 Exits Websocket2 C -->>- M: Agent2 Exits gRPC2 A1 -->> C: Agent1 Exits Websocket1 C -->>- M: Agent1 Exits gRPC1 ``` In this case, the C2 server connects via: ```go theme={"system"} grpcClient.StartPushC2Streaming(streamContext) ``` The first C2 server is just acting like a proxy in this case and forwards the agent's messages along. ```go theme={"system"} grpcStream.Send(&services.PushC2MessageFromAgent{ C2ProfileName: "websocket", RemoteIP: websocketClient.RemoteAddr().String(), Message: nil, Base64Message: []byte(fromAgent.Data), }) ``` The above snippet shows an example of sending a message from an agent - we specify the c2 profile name, the remote IP, and in this case, we're passing along the direct base64 blob from the agent. If you wanted to, depending on how your C2 functions, you could pass along a base64 decoded version in the `Message` field instead. Once Mythic is done processing a message, we can send the response back to the agent: ```go theme={"system"} fromMythic, readErr := grpcStream.Recv() if readErr != nil { log.Printf("Failed to read from grpc stream, closing connections: %v\n", readErr) return } fromMythic.GetMessage() // do something with the message from Mythic ``` ### One-to-Many The other type of Push C2 is one-to-many. This functions largely the same as one-to-one, except that the C2 server only ever opens up one gRPC connection to Mythic with many agents that go through it. Below is a diagram showing this flow: ```mermaid theme={"system"} sequenceDiagram autonumber participant M as Mythic participant C as C2 Server participant A1 as Agent1 participant A2 as Agent2 C ->>+ M: Establish gRPC A1 ->>+ C: Establish WebSocket1 A2 ->>+ C: Establish Websocket2 M ->> C: Issue task to Agent1 (gRPC) C ->>+ A1: Issue Task to Agent1 (Websocket1) A1 -->>- C: Task Response (Websocket1) C -->> M: Task Response (gRPC) A2 -->> C: Agent2 Exits Websocket2 A1 -->> C: Agent1 Exits Websocket1 C -->> M: End flow ``` Because of this, we need to make sure that the user understands that all agents using this C2 profile are sharing a connection with Mythic. We start this connection by telling Mythic that our C2 profile wants to open a oneToMany stream: ```go theme={"system"} grpcStream = client.StartPushC2StreamingOneToMany() await grpcStream.write(grpcFuncs.PushC2MessageFromAgent( C2ProfileName="websocket" )) ``` Notice how the function call is `StartPushC2StreamingOneToMany()` and not `StartPushC2Streaming` like in the one-to-one example. Before we even get any agent connections, we send a message to the gRPC stream with just the c2 profile name. This let's Mythic know that we have a new one-to-many c2 profile running and the name of that connection. At this point, everything works the same as before with the one-to-one profile. As agents send the profile messages, the c2 profile should forward them off to Mythic via the stream and read from the stream to send messages back. You're probably wondering though - how is the multiplexing happening between the one connection to Mythic and the many agents on the other side? This stream utilizes a `TrackingID` that's supplied by the C2 profile to track these individual streams: ```go theme={"system"} await grpcStream.write(grpcFuncs.PushC2MessageFromAgent( C2ProfileName="websocket", RemoteIP=str(websocketConn.remote_address), Base64Message=jsonMsg["data"].encode("utf-8"), TrackingID=connUUID )) ``` This `TrackingID` is something generated by the C2 profile when sending messages and is echoed back as part of the responses that Mythic sends. This allows a C2 Profile to do the proper correlation with messages it gets back and which agent to send it to. This data is saved and tracked by Mythic so that it can be used even when Mythic is the one sending the initial piece of data (like a new task). Let's look at a slightly more complete example to see how that works: ```python theme={"system"} import asyncio import uuid import websockets from websockets.server import serve from mythic_container.logging import logger from mythic_container.grpc.pushC2GRPC_pb2_grpc import PushC2Stub from mythic_container.grpc import pushC2GRPC_pb2 as grpcFuncs import grpc.aio import json UUIDToWebsocketConn = {} grpcStream = None async def handleStreamConnection(client): global UUIDToWebsocketConn global grpcStream try: while True: grpcStream = client.StartPushC2StreamingOneToMany() await grpcStream.write(grpcFuncs.PushC2MessageFromAgent( C2ProfileName="websocket" )) logger.info(f"Connected to gRPC for pushC2 StreamingOneToMany") async for request in grpcStream: # this is streaming responses from Mythic to go to agents try: if request.TrackingID in UUIDToWebsocketConn: logger.info(f"sending message back to websocket for id: {request.TrackingID}") await UUIDToWebsocketConn[request.TrackingID].send(json.dumps({"data": request.Message.decode()})) else: logger.error(f"tracking ID not tracked: {request.TrackingID} ") except Exception as d: logger.exception(f"Failed to process handleStreamConnection message:\n{d}") logger.error(f"disconnected from gRPC for handleStreamConnection") except Exception as e: logger.exception(f"[-] exception in handleStreamConnection: {e}") async def handleGrpcStreamingServices(): maxInt = 2 ** 31 - 1 while True: try: logger.info(f"Attempting connection to gRPC for pushC2OneToMany...") channel = grpc.aio.insecure_channel( f'127.0.0.1:17444', options=[ ('grpc.max_send_message_length', maxInt), ('grpc.max_receive_message_length', maxInt), ]) await channel.channel_ready() client = PushC2Stub(channel=channel) streamConnections = handleStreamConnection(client) logger.info(f"[+] Successfully connected to gRPC for pushC2OneToMany") await asyncio.gather(streamConnections) except Exception as e: logger.exception(f"Translation gRPC services closed for pushC2OneToMany: {e}") async def handle_connection(websocketConn: websockets.WebSocketServerProtocol): global UUIDToWebsocketConn global grpcStream connUUID = str(uuid.uuid4()) logger.info(f"New tracking ID created: {connUUID}") UUIDToWebsocketConn[connUUID] = websocketConn try: async for message in websocketConn: # get message from agent and send it to grpc stream logger.info(f"new websocket msg for id: {connUUID}") while True: if grpcStream is None: await asyncio.sleep(1) continue break try: jsonMsg = json.loads(message) await grpcStream.write(grpcFuncs.PushC2MessageFromAgent( C2ProfileName="websocket", RemoteIP=str(websocketConn.remote_address), Base64Message=jsonMsg["data"].encode("utf-8"), TrackingID=connUUID )) except Exception as e: logger.info(f"Hit exception trying to send websocket message to grpc: {e}") await asyncio.sleep(1) except Exception as c: if grpcStream is not None: logger.info(f"websocket connection dead, removing it: {connUUID}") try: del UUIDToWebsocketConn[connUUID] await grpcStream.write(grpcFuncs.PushC2MessageFromAgent( C2ProfileName="websocket", RemoteIP=str(websocketConn.remote_address), TrackingID=connUUID, AgentDisconnected=True )) except Exception as e: logger.error(f"Failed to send message to Mythic that connection dropped: {e}") async def main(): logger.info("starting grpc connection server") asyncio.create_task(handleGrpcStreamingServices()) logger.info("starting websocket server") async with serve(handle_connection, "127.0.0.1", 8081): await asyncio.Future() asyncio.run(main()) ``` Here we have a version of the websocket profile - this version uses the one-to-many format. On initial WebSocket connection, we generate a UUID and save the UUID + connection information off into a global variable. We then send that data off into the gRPC stream. When we get data back from the gRPC stream, we use the same `TrackingID` that's echoed back to look up the corresponding WebSocket stream and send data that way. There's one additional piece here that hasn't been covered yet though - `AgentDisconnected`. Since all agents on the other side of this gRPC connection are sharing the one connection to Mythic, if that gRPC connection exits, then Mythic detects that and marks **ALL** agents that use that C2 profile has having lost that connection. However, what if just *one* agent on the other end disconnects? The main gRPC connection is still there, so we need a way to inform Mythic that one remote connection is gone. This is where the `AgentDisconnected` piece comes into play: ```python theme={"system"} await grpcStream.write(grpcFuncs.PushC2MessageFromAgent( C2ProfileName="websocket", RemoteIP=str(websocketConn.remote_address), TrackingID=connUUID, AgentDisconnected=True )) ``` By sending this message with the same TrackingID that the agent was using, Mythic can look up the corresponding Callback and mark it as no longer "Now Streaming...". If at any point the agent re-connects, then you can use a new TrackingID or even the same TrackingID and everything will connect right back up (assuming the agent sends a message like the checkin message) and the callback's last checkin will update back to "Now Streaming...". Detecting an agent as gone is easy when we have a held open connection like a WebSocket. Sometimes though, it's not nearly as easy. This is up to you, the C2 developer. You could always leave the agents as "Now Streaming...", but that might be a little confusing for operators. Instead, you could have a timeout where if you haven't heard from a particular agent in a certain amount of time, mark them as gone. If they send a message again later, great, if not though, then at least the user has an idea that the agent might not be there anymore. [Previous: Get IOC](/version-4.0/customizing/c2-related-development/server-side-coding/2.2.7-get-ioc) · [Next: Consuming Containers](/version-4.0/customizing/3.-consuming-containers) # 9. Custom RPC Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.2.9-custom-rpc ## What is it? C2 Profiles have access to MythicRPC just like Payload Types, but naturally they're not going to have a `TaskID`. So, the functions you can use will be limited, but there's still a bunch you can leverage. ### Payload Type Docker -> C2 Docker This one is a little less intuitive than the C2 Docker container directly reaching out to the Mythic server for functionality. This functionality allows tasking as an operator to directly manipulate a C2 component. This functionality has no "default" functions, it's all based on the C2 profile itself. Technically, this goes both ways - C2 Profiles can reach back and execute functionality from Payload Types as well. Payload Types and C2 Profiles can specify an attribute, `custom_rpc_functions`, which are dictionaries of `key`-`value` pairs (much like the completion functions) where the `key` is the name of the function that a remote services can call, and the `value` is the actual function itself. These functions have the following format: Mythic 4.0 propagates authenticated context through these calls. Use current container libraries and preserve that context if you build a custom RabbitMQ forwarding layer. ```python theme={"system"} async def func_name(incomingMsg: PayloadBuilder.PTOtherServiceRPCMessage) -> PayloadBuilder.PTOtherServiceRPCMessageResponse: response = PayloadBuilder.PTOtherServiceRPCMessageResponse( Success=True, Result={"some dictionary": "with some values", **incomingMsg.ServiceRPCFunctionArguments} ) ``` The incoming data is a dictionary in the `incomingMsg.ServiceRPCFunctionArguments` and the resulting data goes back through the `Result` key. ### Long-running RPCs Set `custom_rpc_timeout` to a positive number of seconds in Mythic's configuration when supported server-to-container RPC routes are expected to take longer than the default timeout. V4's custom-timeout retry policy waits that long and does not retry a timed-out request, preventing duplicate builds, dynamic queries, file-hosting actions, or other work that may still be running. ```bash theme={"system"} sudo ./mythic-cli config set custom_rpc_timeout 120 ``` Leave the value at `0` to use the default timeout. A larger timeout does not make a function asynchronous; callers still wait for one correlated response. # 1. Docker Containers Source: https://docs.mythic-c2.net/version-4.0/customizing/c2-related-development/server-side-coding/2.3.1-c2-docker-containers ## What's the goal of the container? What do the C2 docker containers do? Why are things broken out this way? In order to make things more modular within Mythic, most services are broken out into their own containers. When it comes to C2 profiles, they simply serve as an intermediary layer that translates between your special sauce C2 mechanism and the normal REST-ful interface that Mythic uses. This allows you to create any number of completely disjoint C2 profiles without having to modify anything in the main Mythic codebase and without worrying about any conflicting dependencies. ### Container Information C2 Profile Containers, like Payload Type and Translation Containers, need to have the `mythic_container` library available (either PyPi or Go). From there you can leverage the `github.com/MythicMeta/MythicContainer` GoLang package or the `mythic_container` PyPi package depending on if you want to write the meta information about your C2 profile in Go or Python. The actual code that binds to ports and accepts messages can be written in any language. Mythic provides some basic containers that include these libraries by default, but they will also be pretty chonky. You're welcome to create your own and limit the size. ## Container Components There are a few things needed to make a C2 container. For reference on general code structure options, check out [1. Payload Type Development](/version-4.0/customizing/payload-type-development). The general structure is the same for Payload Types, C2 Profiles, and Translation containers. When starting the container with Mythic, the local `Mythic/InstalledServices/[your c2 name]` path will be mounted as `/Mythic` in the container. Within the `server_folder_path` should be a file called `config.json`, this is what the operator is able to edit through the UI and should contain all of the configuration components. The one piece that doesn't need to be here are if the operator needs to add additional files (like SSL certs). # Mythic UI Development Source: https://docs.mythic-c2.net/version-4.0/customizing/mythic-ui-development ## Mythic UI The current Mythic UI is split into two parts: 1. Normally, when using mythic, `Mythic/mythic-react-docker` and the `mythic_react` container serves a compiled version of the React UI. 2. For development, the `Mythic/MythicReactUI` folder holds the source code that gets built. ### Modifying the Mythic UI To load up the source code for active development, set the `Mythic/.env` variable `mythic_react_debug` to `true` and then run `sudo ./mythic-cli build mythic_react`. This will tear down the current `mythic_react` container and re-build it with nodejs. At this point, any changes you make to the source code in `Mythic/MythicReactUI` will trigger automatic re-builds on the UI for easy development. ### Building the Mythic UI When you're done with all of your changes, run `sudo ./mythic-cli build_ui` and Mythic will automatically build the UI and place it in the `mythic-react-docker` folder. At this point, if you set the `mythic_react_debug` back to `false` and run` ./mythic-cli build mythic_react`, you'll see the UI exactly as somebody else would with a fresh install of Mythic. ### Modifying permissions with GraphQL/Hasura If you modify any of the permissions, create new actions, or make any modifications at all in Hasura's GraphQL, then you need to make sure those get saved off. There's not a *great* way to do this yet, but for now the following steps will save them when done from the Mythic directory: ``` docker exec -it mythic_graphql /bin/bash hasura-cli init [enter some random name here, let's say Bob] cd Bob cp /metadata/config.yml . hasura-cli metadata export cd metadata cp * -R /metadata ``` The above steps export the metadata and puts it back into the mounted `/metadata` directory for Hasura. # 11 TypedArray Parse Function Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/12-typedarray-parse-function ## What is it? This is an optional function to supply when you have a command parameter of type `TypedArray`. This doesn't apply to BuildParameters or C2Profile Parameters because you can only supply those values through the GUI or through scripting. When issuing tasks though, you generally have the option of using a modal popup window *or* freeform text. This `typedarray_parse_function` is to help with parsing freeform text when issuing tasks. When creating a `TypedArray` parameter, the `choices` attribute is the set of options presented to the user in the dropdowns for the modal and the `default_value` attribute is the option that's selected by default when the user adds a new array entry. ## Where is it? This is an optional function you can supply as part of your command parameter definitions. ## What does it do? A `TypedArray` provides two things from the operator to the agent - an array of values and a type for each value. This makes its way to the payload type containers during tasking and building as an array of arrays (ex: `[ ["int", "5"], ["string", "hello"] ]` ). However, nobody types like that on the command line when issuing tasks. That's where this function comes into play. Let's say you have a command, `my_bof`, with a TypedArray parameter called `bof_args`. `bof_args` has type options (i.e. the `choices` of the parameter) of `int`, `wstring`, and `char*`. When issuing this command on the command line, you'd want the operator to be able to issue something a bit easier than multiple nested arrays. Something like: ``` my_bof -bof_args int:5 char*:testing wstring:"this is my string" my_bof -bof_args int/5 char*/testing wstring/"this is my string" my_bof -bof_args int(5) char*(testing) wstring(this is my string) ... ``` The list of options can go on and on and on. There's no ideal way to do it, and everybody's preferences for doing something like this is a bit different. This is where the TypedArray parsing function comes into play. Your payload type can define or explain in the command description how arguments should be formatted when using freeform text, and then your parsing function can parse that data back into the proper array of arrays. This function takes in a list of strings formatted how the user presented it on the command line, such as: ```json theme={"system"} [ "int:5", "char*:testing", "wstring:this is my string" ] ``` The parse function should take that and split it back out into our array of arrays: ```json theme={"system"} [ ["int": "5"], ["char*", "testing"], ["wstring", "this is my string"] ] ``` ## When does it get called? This function gets called in a few different scenarios: * The user types out `my_bof -bof_args int:5 char*:testing` on the command line and hits enter * If the modal is used then this function is *not* called because we already can create the proper array of arrays from the UI * The user uses Mythic scripting or browser scripts to submit the task * The user types out `my_bof -bof_args int:5 char*:testing` on the command line and hits SHIFT+enter to open up a modal dialog box. This will call your parsing function to turn that array into an array of arrays so that the modal dialog can display what the user has typed out so far. ### What do I do for parse\_arguments or parse\_dictionary functions then? These functions are essentially the first line of processing that can happen on the parameters the user provides (the first optional parsing being done in the browser). For your typed array parameter, `bof_args` in this case, you just need to make sure that one of the following is true after you're done parsing with either of these functions: * the typed array parameter's value is set to an array of strings, ex: `["int:5", "char*:testing"]` * the typed array parameter's value is set to an array of arrays where the first entry is empty and the second entry is the value, ex: `[ ["", "int:5"], ["", "char*:testing"] ]` After your `parse_arguments` or `parse_dictionary` function is called, the mythic\_container code will check for any typed\_array parameters and check their value. If the value is one of the above two instances, it'll make it match what's expected for the typedarray parse function, call your function, then automatically update the value. ### How can I check that it parsed correctly in the UI? If you're using the Mythic UI and want to make sure your parsed array happened correctly, it's pretty easy to see. Expand your task, click the blue plus sign, then go to the keyboard icon. It'll open up a dialog box showing you all the various stages of parameter parsing that's happened. If you just typed out the information on the command line (no modal), then you'll likely see an array of arrays where the first element is always `""` displayed to the user, but the `agents parameters` section will show you the final value after all the parsing happens. That's where you'll see the result of your parsing. # 14. Interactive Tasking Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/15.-interactive-tasking Using a C2 platform, you don't generally have the ability to send follow-on information to a running task. Each task is run in isolation, especially if you're running a "shell" command. However, there are times when it's very useful to have an interactive shell on the target host. Depending on the operating system and configuration, you could of course attempt to use SOCKS to make a local SSH connection, or even use RPFWD to cause a shell to make a "remote" connection to yourself and tunnel it externally. These have their own pros/cons. Another option would be to allow follow-on input within your current task without requiring additional network connections or going to something like `sleep 0` for it to work. This is where "interactive" tasking comes into play. If your command specifies a `supported_ui_feature` of `task_response:interactive`, then Mythic will display a slightly different interface for that task's output. Notice here that as part of the task response, there's a whole new set of controls and input fields. The controls allow you to send things like CtrlC, CtrlZ, CtrlD through the browser down to your agent. Additionally, on the right-hand side there's a control for what kind of line endings to set when you hit enter - None, LF, CR, or CRLF. The `None` option is particularly useful you want to send something along like "q" or " " to the other side without sending "q\n" or " \n". Mythic 4.0 uses an xterm-based terminal renderer with improved keyboard and scroll handling. Operators can also switch compatible stored output among terminal, plaintext, JSON, and Markdown views and control wrapping from the response toolbar. Just like with SOCKS and RPFWD, you can use MythicRPCProxyStart to open up a port with this sort of task. If you do, then you can interactively work with the task through your own terminal instead of through the web UI. **Note:** When tasking through the UI, all input is tracked as normal tasking, but no "tasks" are created when interacting via the opened port. Output is still saved and displayed within the UI. For information on the wire format, see [Interactive Tasking Messages](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/8.-interactive-tasking). Message types `100`–`102` are reserved for the separate [File Editor Protocol](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol). # Commands Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/adding-commands/commands ## What do Commands track? Command information is tracked in your Payload Type's container. Each command has its own Python class or GoLang struct. In Python, you leverage `CommandBase` and `TaskArguments` to define information about the command and information about the command's arguments. **CommandBase** defines the metadata about the command as well as any pre-processing functionality that takes place before the final command is ready for the agent to process. This class includes the `create_go_tasking` ([Create\_Tasking](/version-4.0/customizing/payload-type-development/create_tasking)) and `process_response` ([Process Response](/version-4.0/customizing/payload-type-development/process-response)) functions. \*\*\*\*[**TaskArguments**](/version-4.0/customizing/payload-type-development/adding-commands/commands#taskarguments) does two things: 1. defines the parameters that the command needs 2. verifies / parses out the user supplied arguments into their proper components * this includes taking user supplied free-form input (like arguments to a sleep command - `10 4`) and parsing it into well-defined JSON that's easier for the agent to handle (like `{"interval": 10, "jitter": 4}`). This can also take user-supplied dictionary input and parse it out into the rightful CommandParameter objects. * This also includes verifying all the necessary pieces are present. Maybe your command requires a source and destination, but the user only supplied a source. This is where that would be determined and error out for the user. This prevents you from requiring your agent to do that sort of parsing in the agent. If you're curious how this all plays out in a diagram, you can find one here: [#operator-submits-tasking](/version-4.0/message-flow#operator-submits-tasking). ## CommandBase ```python theme={"system"} from mythic_payloadtype_container.PayloadBuilder import * from mythic_payloadtype_container.MythicCommandBase import * class ScreenshotCommand(CommandBase): cmd = "screenshot" needs_admin = False help_cmd = "screenshot" description = "Use the built-in CGDisplay API calls to capture the display and send it back over the C2 channel. No need to specify any parameters as the current time will be used as the file name" version = 1 author = "" attackmapping = ["T1113"] argument_class = ScreenshotArguments browser_script = BrowserScript(script_name="screenshot", author="@its_a_feature_") attributes = CommandAttributes( spawn_and_injectable=True, supported_os=[SupportedOS.MacOS], builtin=False, load_only=False, suggested_command=False, ) script_only = False async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) return response async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) return resp ``` Creating your own command requires extending this CommandBase class (i.e. `class ScreenshotCommand(CommandBase)` and providing values for all of the above components. * `cmd` - this is the command name. The name of the class doesn't matter, it's this value that's used to look up the right command at tasking time * `needs_admin` - this is a boolean indicator for if this command requires admin permissions * `help_cmd` - this is the help information presented to the user if they type `help [command name]` from the main active callbacks page * `description` - this is the description of the command. This is also presented to the user when they type help. * `supported_ui_features` - This is an array of values that indicates where this command might be used within the UI. For example, from the active callbacks page, you see a table of all the callbacks. As part of this, there's a dropdown you can use to automatically issue an `exit` task to the callback. How does Mythic know which command to actually send? It's this array that dictates that. The following are used by the callback table, file browser, and process listing, but you're able to add in any that you want and leverage them via browser scripts for additional tasking: * supported\_ui\_features = \["callback\_table:exit"] * supported\_ui\_features = \["file\_browser:list"] * supported\_ui\_features = \["process\_browser:list"] * supported\_ui\_features = \["file\_browser:download"] * supported\_ui\_features = \["file\_browser:remove"] * supported\_ui\_features = \["file\_browser:upload"] * supported\_ui\_features = \["task\_response:interactive"] * `version` - this is the version of the command you're creating/editing. This allows a helpful way to make sure your commands are up to date and tracking changes * `argument_class` - this correlates this command to a specific `TaskArguments` class for processing/validating arguments * `attackmapping` - this is a list of strings to indicate MITRE ATT\&CK mappings. These are in "T1113" format. * `agent_code_path` is automatically populated for you like in building the payload. This allows you to access code files from within commands in case you need to access files, functions, or create new pieces of payloads. This is really useful for a `load` command so that you can find and read the functions you're wanting to load in. * You can optionally add in the `attributes` variable. This is a new class called `CommandAttributes` where you can set whether or not your command supports being injected into a new process (some commands like `cd` or `exit` don't make sense for example). You can also provide a list of supported operating systems. This is helpful when you have a payload type that might compile into multiple different operating system types, but not all the commands work for all the possible operating systems. Instead of having to write "not implemented" or "not supported" function stubs, this will allow you to completely filter this capability out of the UI so users don't even see it as an option. * Available options are: * `supported_os` an array of SupportedOS fields (ex: `[SupportedOS.MacOS]`) (in Python for a new SupportedOS you can simply do `SupportedOS("my os name")`. * `spawn_and_injectable` is a boolean to indicate if the command can be injected into another process * `builtin` is a boolean to indicate if the command should be always included in the build process and can't be unselected * `load_only` is a boolean to indicate if the command can't be built in at the time of payload creation, but can be loaded in later * `suggested_command` is a boolean to indicate if the command should be pre-selected for users when building a payload * `filter_by_build_parameter` is a dictionary of `parameter_name:value` for what's required of the agent's build parameters. This is useful for when some commands are only available depending on certain values when building your agent (such as agent version). * You can also add in any other values you want for your own processing. These are simply `key=value` pairs of data that are stored. Some people use this to identify if a command has a dependency on another command. This data can be fetched via RPC calls for things like a `load` command to see what additional commands might need to be included. * This ties into the CommandParameter fields `choice_filter_by_command_attributes`, `choices_are_all_commands`, and `choices_are_loaded_commands`. * The `create_go_tasking` function is very broad and covered in [Create\_Tasking](/version-4.0/customizing/payload-type-development/create_tasking#create_tasking) * The `process_response` is similar, but allows you to specify that data shouldn't automatically be processed by Mythic when an agent checks in, but instead should be passed to this function for further processing and to use Mythic's RPC functionality to register the results into the system. The data passed here comes from the `post_response` message ([Process Response](/version-4.0/customizing/payload-type-development/process-response)). * The `script_only` flag indicates if this Command will be use strictly for things like issuing [subtasking](/version-4.0/customizing/payload-type-development/sub-tasking-task-callbacks), but will NOT be compiled into the agent. The nice thing here is that you can now generate commands that don't need to be compiled into the agent for you to execute. These tasks never enter the "submitted" stage for an agent to pick up - instead they simply go into the [create\_tasking](/version-4.0/customizing/payload-type-development/create_tasking) scenario (complete with subtasks and full RPC functionality) and then go into a completed state. ## TaskArguments The TaskArguments class defines the arguments for a command and defines how to parse the user supplied string so that we can verify that all required arguments are supplied. Mythic now tracks *where* tasking came from and can automatically handle certain instances for you. Mythic now tracks a `tasking_location` field which has the following values: * `command_line` - this means that the input you're getting is just a raw string, like before. It could be something like `x86 13983 200` with a series of positional parameters for a command, it could be `{"command": "whoami"}` as a JSON string version of a dictionary of arguments, or anything else. In this case, Mythic really doesn't know enough about the source of the tasking or the contents of the tasking to provide more context. When issuing tasks via Mythic's Scripting, they'll always come through as a tasking\_location of `command_line`. * `parsed_cli` - this means that the input you're getting is a dictionary that was parsed by the new web interface's CLI parser. This is what happens when you type something on the command line for a command that has arguments (ex: `shell whoami` or `shell -command whoami`). Mythic can successfully parse out the parameters you've given into a single parameter\_group and gives you a `dictionary` of data. * `modal` - this means that the input you're getting is a dictionary that came from the tasking modal. Nothing crazy here, but it does at least mean that there shouldn't be any silly shenanigans with potential parsing issues. * `browserscript` - if you click a tasking button from a browserscript table and that tasking button provides a dictionary to Mythic, then Mythic can forward that down as a dictionary. If the tasking button from a browserscript table submits a `String` instead, then that gets treated as `command_line` in terms of parsing. With this ability to track where tasking is coming from and what form it's in, an agent's command file can choose to parse this data differently. By default, all commands must supply a `parse_arguments` function in their associated `TaskArguments` subclass. If you do nothing else, then *all* of these various forms will get passed to that function as strings (if it's a dictionary it'll get converted into a JSON string). However, you can provide another function, `parse_dictionary` that can handle specifically the cases of parsing a given dictionary into the right CommandParameter objects as shown below: ```python theme={"system"} async def parse_arguments(self): if len(self.command_line) == 0: raise ValueError("Must supply arguments") if self.command_line[0] == "{": try: self.load_args_from_json_string(self.command_line) return except Exception as e: pass # if we got here, we weren't given a JSON string but raw text to parse # here's an example, though error prone because it splits on " " characters pieces = self.command_line.split(" ") self.add_arg("arg1", pieces[0]) self.add_arg("arg2", pieces[1]) async def parse_dictionary(self, dictionary_arguments): self.load_args_from_dictionary(dictionary_arguments) ``` In `self.args` we define an array of our arguments and what they should be along with default values if none were provided. In `parse_arguments` we parse the user supplied `self.command_line` into the appropriate arguments. The hard part comes when you allow the user to type arguments free-form and then must parse them out into the appropriate pieces. ```python theme={"system"} class LsArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ CommandParameter( name="path", type=ParameterType.String, default_value=".", description="Path of file or folder on the current system to list", parameter_group_info=[ParameterGroupInfo( required=False )] ) ] async def parse_arguments(self): self.add_arg("path", self.command_line) async def parse_dictionary(self, dictionary): if "host" in dictionary: # then this came from the file browser self.add_arg("path", dictionary["path"] + "/" + dictionary["file"]) self.add_arg("file_browser", type=ParameterType.Boolean, value=True) else: self.load_args_from_dictionary(dictionary) ``` The main purpose of the TaskArguments class is to manage arguments for a command. It handles parsing the `command_line` string into `CommandParameters`, defining the `CommandParameters`, and providing an easy interface into updating/accessing/adding/removing arguments as needed. As part of the `TaskArguments` subclass, you have access to the following pieces of information: * `self.command_line` - the parameters sent down for you to parse * `self.raw_command_line` - the original parameters that the user typed out. This is useful in case you have additional pieces of information to process or don't want information processed into the standard JSON/Dictionary format that Mythic uses. * `self.tasking_location` - this indicates where the tasking came from * `self.task_dictionary` - this is a dictionary representation of the task you're parsing the arguments for. You can see things like the initial `parameter_group_name` that Mythic parsed for this task, the user that issued the task, and more. * `self.parameter_group_name` - this allows you to manually specify what the parameter group name should be. Maybe you don't want Mythic to do automatic parsing to determine the parameter group name, maybe you have additional pieces of data you're using to determine the group, or maybe you plan on adjusting it later on. Whatever the case might be, if you set `self.parameter_group_name = "value"`, then Mythic won't continue trying to identify the parameter group based on the current parameters with values. The class **must** implement the `parse_arguments` method and define the `args` array (it can be empty). This `parse_arguments` method is the one that allows users to supply "short hand" tasking and still parse out the parameters into the required JSON structured input. If you have defined command parameters though, the user can supply the required parameters on the command line (via `-commandParameterName` or via the popup tasking modal via `shift+enter`). When syncing the command with the UI, Mythic goes through each class that extends the CommandBase, looks at the associated `argument_class`, and parses that class's `args` array of `CommandParameters` to create the pop-up in the UI. While the TaskArgument's `parse_arguments` method simply parses the user supplied input and sets the values for the named arguments, it's the CommandParameter's class that actually verifies that every required parameter has a value, that all the values are appropriate, and that default values are supplied if necessary. ### CommandParameters CommandParameters, similar to BuildParameters, provide information for the user via the UI and validates that the values are all supplied and appropriate. ```python theme={"system"} class CommandParameter: def __init__( self, name: str, type: ParameterType, display_name: str = None, cli_name: str = None, description: str = "", choices: [any] = None, default_value: any = None, validation_func: callable = None, value: any = None, supported_agents: [str] = None, supported_agent_build_parameters: dict = None, choice_filter_by_command_attributes: dict = None, choices_are_all_commands: bool = False, choices_are_loaded_commands: bool = False, dynamic_query_function: callable = None, parameter_group_info: [ParameterGroupInfo] = None ): self.name = name if display_name is None: self.display_name = name else: self.display_name = display_name if cli_name is None: self.cli_name = name else: self.cli_name = cli_name self.type = type self.user_supplied = False # keep track of if this is using the default value or not self.description = description if choices is None: self.choices = [] else: self.choices = choices self.validation_func = validation_func if value is None: self._value = default_value else: self.value = value self.default_value = default_value self.supported_agents = supported_agents if supported_agents is not None else [] self.supported_agent_build_parameters = supported_agent_build_parameters if supported_agent_build_parameters is not None else {} self.choice_filter_by_command_attributes = choice_filter_by_command_attributes if choice_filter_by_command_attributes is not None else {} self.choices_are_all_commands = choices_are_all_commands self.choices_are_loaded_commands = choices_are_loaded_commands self.dynamic_query_function = dynamic_query_function if not callable(dynamic_query_function) and dynamic_query_function is not None: raise Exception("dynamic_query_function is not callable") self.parameter_group_info = parameter_group_info if self.parameter_group_info is None: self.parameter_group_info = [ParameterGroupInfo()] ``` * `name` - the name of the parameter that your agent will use. `cli_name` is an optional variation that you want user's to type when typing out commands on the command line, and `display_name` is yet another optional name to use when displaying the parameter in a popup tasking modal. * `type`- this is the parameter type. The valid types are: * String - gets a string value * Boolean - gets a boolean value * File * Upload a file through your browser. In your create tasking though, you get a String UUID of the file that can be used via SendMythicRPC\* calls to get more information about the file or the file contents * Array * An Array of string values * TypedArray * An array of arrays, ex: `[ ["int": "5"], ["char*", "testing"] ]` * ChooseOne - gets a string value * ChooseMultiple * An Array of string values * ChooseOneCustom - gets a string value from a list of `choices` OR a user supplied value * Credential\_JSON * Select a specific credential that's registered in the Mythic credential store. In your create tasking, get a JSON representation of all data for that credential * Number * Payload * Select a payload that's already been generated and get the UUID for it. This is helpful for using that payload as a template to automatically generate another version of it to use as part of lateral movement or spawning new agents. * ConnectionInfo * Select the Host, Payload/Callback, and P2P profile for an agent or callback that you want to link to via a P2P mechanism. This allows you to generate random parameters for payloads (such as named-pipe names) and not require you to remember them when linking. You can simply select them and get all of that data passed to the agent. * When this is up in the UI, you can also track new payloads on hosts in case Mythic isn't aware of them (maybe you moved and executed payloads in a method outside of Mythic). This allows Mythic to track that payload X is now on host Y and you can use the same selection process as the first bullet to filter down and select it for linking. * LinkInfo * Get a list of all active/dead P2P connections for a given agent. Selecting one of these links gives you all the same information that you'd get from the `ConnectionInfo` parameter. The goal here is to allow you to easily select to "unlink" from an agent or to re-link to a very specific agent on a host that you were previously connected to. * `description` - this is the description of the parameter that's presented to the user when the modal pops up for tasking * `choices` - this is an array of choices if the type is `ChooseOne` or `ChooseMultiple` * If your command needs you to pick from the set of commands (rather than a static set of values), then there are a few other components that come into play. If you want the user to be able to select any command for this payload type, then set `choices_are_all_commands` to `True`. Alternatively, you could specify that you only want the user to choose from commands that are already loaded into the callback, then you'd set `choices_are_loaded_commands` to `True`. As a modifier to either of these, you can set `choice_filter_by_command_attributes` to filter down the options presented to the user even more based on the parameters of the Command's `attributes` parameter. This would allow you to limit the user's list down to commands that are loaded into the current callback that support MacOS for example. An example of this would be: ```python theme={"system"} CommandParameter(name="test name", type=ParameterType.ChooseMultiple, description="so many choices!", choices_are_all_commands=True, choice_filter_by_command_attributes={"supported_os": [SupportedOS.MacOS]}), ``` * `choices` - for the `TypedArray` type, the `choices` here is the list of options you want to provide in the dropdown for the user. So if you have choices as `["int", "char*"]`, then when the user adds a new array entry in the modal, those two will be the options. Additionally, if you set the `default_value` to `char*`, then `char*` will be the value selected by default. * `validation_func` - this is an additional function you can supply to do additional checks on values to make sure they're valid for the command. If a value isn't valid, an exception should be raised * `value` - this is the final value for the parameter; it'll either be the default\_value or the value supplied by the user. This isn't something you set directly. * `default_value` - this is a value that'll be set if the user doesn't supply a value * `supported_agents` - If your parameter type is `Payload` then you're expecting to choose from a list of already created payloads so that you can generate a new one. The `supported_agents` list allows you to narrow down that dropdown field for the user. For example, if you only want to see agents related to the `apfell` payload type in the dropdown for this parameter of your command, then set `supported_agents=["apfell"]` when declaring the parameter. * `supported_agent_build_parameters` - allows you to get a bit more granular in specifying which agents you want to show up when you select the `Payload` parameter type. It might be the case that a command doesn't *just* need instance of the `atlas` payload type, but maybe it only works with the `Atlas` payload type when it's compiled into .NET 3.5. This parameter value could then be `supported_agent_build_parameters={"atlas": {"version":"3.5"}}` . This value is a dictionary where the key is the name of the payload type and the value is a dictionary of what you want the build parameters to be. * `dynamic_query_function` - More information can be found [here](/version-4.0/customizing/payload-type-development/dynamic-parameter-values), but you can provide a function here for ONLY parameters of type ChooseOne or ChooseMultiple where you dynamically generate the array of choices you want to provide the user when they try to issue a task of this type. * `typedarray_parse_function` - This allows you to have typed arrays more easily displayed and parsed throughout Mythic (useful for BOF/COFF work). More information for this can be found [here](/version-4.0/customizing/payload-type-development/12-typedarray-parse-function). Most command parameters are pretty straight forward - the one that's a bit unique is the File type (where a user is uploading a file as part of the tasking). When you're doing your tasking, this `value` will be the base64 string of the file uploaded. ### parameter\_group\_info To help with conditional parameters, Mythic 2.3 introduced parameter groups. Every parameter must belong to at least one parameter group (if one isn't specified by you, then Mythic will add it to the `Default` group and make the parameter `required`). You can specify this information via the `parameter_group_info` attribute on `CommandParameter` class. This attribute takes an array of `ParameterGroupInfo` objects. Each one of these objects has three attributes: `group_name` (string), `required`(boolean) `ui_position` (integer). These things together allow you to provide conditional parameter groups to a command. A note about **required**: This indicates if you require a value from the user. If you can provide a sane default\_value for a parameter, then it isn't *required*. Your agent might need a value, but if the `default_value` works, then it isn't *required* as far as Mythic is concerned. The `required` attribute here tells Mythic that if a user didn't explicitly provide a parameter, then it needs to open up the dialog modal to ask them to provide one. For example: the `path` parameter for listing a directory might not be required because if one isn't provided by the user, you can assume to list the contents of the current working directory. However, the `path` parameter for something like `download` would be required because if the user just typed `download` on the command line, you'd have no sane default value to use instead. Let's look at an example - the new `apfell` agent's `upload` command now leverages conditional parameters. This command allows you to either: * specify a `remote_path` and a `filename` - Mythic then looks up the filename to see if it's already been uploaded to Mythic before. If it has, Mythic can simply use the same file identifier and pass that along to the agent. * specify a `remote_path` and a `file` - This is uploading a new file, registering it within Mythic, and then passing along that new file identifier Notice how both options require the `remote_path` parameter, but the `file` and `filename` parameters are mutually exclusive. ```python theme={"system"} class UploadArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ CommandParameter( name="file", cli_name="new-file", display_name="File to upload", type=ParameterType.File, description="Select new file to upload", parameter_group_info=[ ParameterGroupInfo( required=True, group_name="Default" ) ] ), CommandParameter( name="filename", cli_name="registered-filename", display_name="Filename within Mythic", description="Supply existing filename in Mythic to upload", type=ParameterType.ChooseOne, dynamic_query_function=self.get_files, parameter_group_info=[ ParameterGroupInfo( required=True, group_name="specify already uploaded file by name" ) ] ), CommandParameter( name="remote_path", cli_name="remote_path", display_name="Upload path (with filename)", type=ParameterType.String, description="Provide the path where the file will go (include new filename as well)", parameter_group_info=[ ParameterGroupInfo( required=True, group_name="Default", ui_position=1 ), ParameterGroupInfo( required=True, group_name="specify already uploaded file by name", ui_position=1 ) ] ), ] ``` So, the `file` parameter has one `ParameterGroupInfo` that calls out the parameter as required. The `filename` parameter also has one `ParameterGroupInfo` that calls out the parameter as required. It also has a `dynamic_query_function` that allows the task modal to run a function to populate the selection box. Lastly, the `remote_path` parameter has TWO `ParameterGroupInfo` objects in its array - one for each group. This is because the `remote_path` parameter applies to both groups. You can also see that we have a `ui_position` specified for these which means that regardless of which option you're viewing in the tasking modal, the parameter `remote_path` will be the first parameter shown. This helps make things a bit more consistent for the user. If you're curious, the function used to get the list of files for the user to select is here: ```python theme={"system"} async def get_files(self, inputMsg: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse: fileResponse = PTRPCDynamicQueryFunctionMessageResponse(Success=False) file_resp = await MythicRPC().execute("get_file", callback_id=inputMsg.Callback, limit_by_callback=False, filename="", max_results=-1) if file_resp.status == MythicRPCStatus.Success: file_names = [] for f in file_resp.response: if f["filename"] not in file_names and f["filename"].endswith(".exe"): file_names.append(f["filename"]) fileResponse.Success = True fileResponse.Choices = file_names return fileResponse else: fileResponse.Error = file_resp.error return fileResponse ``` In the above code block, we're searching for files, not getting their contents, not limiting ourselves to just what's been uploaded to the callback we're tasking, and looking for all files (really it's all files that have "" in the name, which would be all of them). We then go through to de-dupe the filenames and return that list to the user. ### Processing Order So, with all that's going on, it's helpful to know what gets called, when, and what you can do about it. When you send a task to Mythic (from a modal, typing it out, via scripting, etc), the first thing that happens is Mythic stores it in the database. A bunch of data around a task (associated callback, payload, c2, etc) is sent to the PayloadType container for processing. This container first creates an instance of your command's TaskArguments function and passes it the database data. It then calls either the `parse_dictionary` or `parse_arguments` functions depending on if the `params` it has are JSON or not and if the `parse_dictionary` function is even provided. At this point, your code has been invoked to help fill out and set some of the parameters. The PayloadType container then does a check to see which parameters were explicitly set, and based on that, which parameter group is being used. If that can't be determined, then an exception is thrown. Otherwise, any non-required parameters for that parameter group get their default values that weren't already explicitly set. If there's a required parameter that hasn't been explicitly set though, then another exception is thrown. Once all of that parsing is done, that finalized TaskArguments class is attached to an instance of your Command's class and first, that Command's `opsec_pre` function is called. If that returns that everything is good to go, all of the above stuff happens again, but then your Command's `create_go_tasking` function is called. If that one returns success, then your Command's `opsec_post` function is called. If that also returns success, then your task is finally in the `Submitted` state and ready for an agent to pick it up. If your command is `script_only=True` though, then at this point your task is flipped to completed and not picked up by the agent. # 5. Browser Scripting Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/browser-scripting Render payload command responses with Mythic 4.0 browser scripts. Browser scripts are JavaScript response renderers. A payload type can register a default renderer with a command, and each operator can enable, disable, or customize their own copy in Mythic. ```python theme={"system"} browser_script = BrowserScript(script_name="process_list", author="@operator") ``` Store the referenced JavaScript in the payload type's `browser_scripts` directory. An operator-created script is useful for experimentation, but register the final script with the payload type if it should survive a new database and be distributed to other operators. ## Function contract Mythic calls the script with the current task object and an array of response strings. Return an object containing one or more supported top-level keys. ```javascript theme={"system"} function(task, responses) { if (task.status.toLowerCase().includes("error")) { return { plaintext: responses.join("") }; } const parsed = responses.flatMap((response) => { try { const value = JSON.parse(response); return Array.isArray(value) ? value : [value]; } catch (error) { return []; } }); return { plaintext: `${parsed.length} process records`, table: [{ title: "Processes", headers: [ { plaintext: "name", type: "string", fillWidth: true }, { plaintext: "pid", type: "number", width: 120 }, { plaintext: "user", type: "string", width: 220 } ], rows: parsed.map((item) => ({ name: { plaintext: item.name }, pid: { plaintext: item.pid }, user: { plaintext: item.user } })) }] }; } ``` Run error handling before parsing structured output. Responses can arrive incrementally, so the function must also tolerate a partially completed task and incomplete application-level data. ## Supported output | Key | Shape | Purpose | | ----------- | ---------------------- | ------------------------------------------------------------------------ | | `plaintext` | string | Render text with plaintext, JSON, Markdown, and terminal view options. | | `table` | array of tables | Render sortable/filterable structured rows and tasking controls. | | `media` | array of media objects | Preview and download Mythic file records through authenticated requests. | | `graph` | graph object | Render nodes and edges for relationship output. | | `tabs` | array of tab objects | Organize any supported output recursively into named tabs. | The keys can be combined in one result. Unknown keys are ignored. ### Plaintext ```javascript theme={"system"} return { plaintext: responses.join("") }; ``` The operator can switch plain output among raw text, formatted JSON, Markdown, and xterm-based terminal rendering. A browser script does not need to implement ANSI or Markdown rendering itself. ### Tables Each table has a `title`, `headers`, and `rows`. A header's `plaintext` is also the key used to find its cell in each row. ```javascript theme={"system"} return { table: [{ title: "Network Connections", headers: [ { plaintext: "remote", type: "string", fillWidth: true }, { plaintext: "port", type: "number", width: 100 }, { plaintext: "state", type: "string", width: 140 } ], rows: connections.map((connection) => ({ remote: { plaintext: connection.remote }, port: { plaintext: connection.port }, state: { plaintext: connection.state } })) }] }; ``` Table cells support plaintext plus the current copy, tasking-button, style, and value formats exposed by the UI. Keep raw agent values in the row so sorting and filtering remain useful. ### Media Use `media` for downloaded files, screenshots, text, hex, and SQLite previews. Mythic looks up the file record by `agent_file_id` and adds Bearer authentication to preview and download requests. ```javascript theme={"system"} return { media: [{ agent_file_id: result.file_id, filename: result.filename, editable: false }] }; ``` Set `editable: true` only for a workflow that intentionally permits changing the Mythic-side file record. The interactive remote [file-editor protocol](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol) is a separate task-response feature. ### Tabs Each tab has a `title` and `content`. The content accepts the same keys as a top-level browser-script result. ```javascript theme={"system"} return { tabs: [ { title: "Summary", content: { plaintext: summary } }, { title: "Records", content: { table: [recordsTable] } }, { title: "Artifact", content: { media: [{ agent_file_id: fileId }] } } ] }; ``` ### Graphs Return `graph` when nodes and edges communicate the result more clearly than rows. Graph definitions can supply nodes directly or build them from browser-script elements and can include layout/view configuration. Validate graph output in the script editor's preview because invalid node or edge references cannot be rendered. ## Tasking buttons Table cells can issue additional tasking. Pass the command, callback context, and parameter value required by the target action instead of constructing direct API requests in the browser script. Use supported UI features where possible so the same action remains available outside the custom renderer. ## Removed v4 renderers The legacy top-level `screenshot`, `download`, and `search` result keys were removed in Mythic 4.0. Convert screenshot/download output to `media`; convert search links to current table/tasking controls or ordinary supported output. Browser scripts that fetch protected routes must use the UI's authenticated mechanisms because the `mythic` cookie is no longer accepted. Test each migrated script against completed, partially completed, error, empty, and multi-response tasks. The Browser Scripts editor can preview a script against matching task output from the current operation. # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking Manipulate tasking before it's sent to the agent ## Create Tasking All commands must have a create\_go\_tasking function with a base case like: ``` async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) return response ``` `create_go_tasking` is new in Mythic v3.0.0. Prior to this, there was the `create_tasking` function. The new change supports backwards compatibility, but the new function provides a lot more information and structured context that's not available in the `create_tasking` function. The `create_go_tasking` function also mirrors the GoLang's `create_tasking` function. ``` TaskFunctionCreateTasking: func(taskData *agentstructs.PTTaskMessageAllData) agentstructs.PTTaskCreateTaskingMessageResponse { response := agentstructs.PTTaskCreateTaskingMessageResponse{ Success: true, TaskID: taskData.Task.ID, } return response }, ``` When an operator types a command in the UI, whatever the operator types (or whatever is populated based on the popup modal) gets sent to this function after the input is parsed and validated by the TaskArguments and CommandParameters functions mentioned in [Commands](/version-4.0/customizing/payload-type-development/adding-commands/commands). It's here that the operator has full control of the task before it gets sent down to an agent. The task is currently in the "preprocessing" stage when this function is executed and allows you to do many things via Remote Procedure Calls (RPC) back to the Mythic server. A graphical flow of what goes on is here: [tasking flow](/version-4.0/message-flow/operator-submits-tasking). ### Available Context So, from this create tasking function, what information do you immediately have available? [https://github.com/MythicMeta/MythicContainerPyPi/blob/main/mythic\_container/MythicCommandBase.py#L1071-L1088](https://github.com/MythicMeta/MythicContainerPyPi/blob/main/mythic_container/MythicCommandBase.py#L1071-L1088) \<-- this class definition provides the basis for what's available. * `taskData.Task` - Information about the Task that's issued * `taskData.Callback` - Information about the Callback for this task * `taskData.Payload` - Information about the packing payload for this callback * `taskData.Commands` - A list of the commands currently loaded into this callback * `taskData.PayloadType` - The name of this payload type * `taskData.BuildParameters` - The build parameters and their values used when building the payload for this callback * `taskData.C2Profiles` - Information about the C2 Profiles included inside of this callback. * `taskData.args` - access to the associated arguments class for this command that already has all of the values populated and validated. Let's say you have an argument called "remote\_path", you can access it via `taskData.args.get_arg("remote_path")` . * Want to change the value of that to something else? `taskData.args.add_arg("remote_path", "new value")`. * Want to change the value of that to a different type as well? `taskData.args.add_arg("remote_path", 5, ParameterType.Number)` * Want to add a new argument entirely for this specific instance as part of the JSON response? `taskData.args.add_arg("new key", "new value")`. The `add_arg` functionality will overwrite the value if the key exists, otherwise it'll add a new key with that value. The default ParameterType for args is `ParameterType.String`, so if you're adding something else, be sure to change the type. **Note**: If you have multiple parameter groups as part of your tasking, make sure you specify *which* parameter group your new argument belongs to. By default, the argument gets added to the `Default` parameter group. This could result in some confusion where you add an argument, but it doesn't get picked up and sent down to the agent. * You can also remove args `taskData.args.remove_arg("key")`, rename args `taskData.args.rename_arg("old key", "new key")` * You can also get access to the user's commandline as well via `taskData.args.commandline` * Want to know if an arg is in your args? `taskData.args.has_arg("key")` * `taskData.Task.TokenID` - information about the token that was used with the task. This requires that the callback has at some point returned tokens for Mythic to track, otherwise this will be 0. In the `PTTaskCreateTaskingMessageResponse`, you can set a variety of attributes to reflect changes back to Mythic as a result of your processing: [https://github.com/MythicMeta/MythicContainerPyPi/blob/main/mythic\_container/MythicCommandBase.py#L820](https://github.com/MythicMeta/MythicContainerPyPi/blob/main/mythic_container/MythicCommandBase.py#L820) * `Success` - did your processing succeed or not? If not, set `Error` to a string value representing the error you encountered. * `CommandName` - If you want the agent to see the command name for this task as something *other* than what the actual command's name is, reflect that change here. This can be useful if you are creating an alias for a command. So, your agent has the command `ls`, but you create a script\_only command `dir`. During the processing of `dir` you set the `CommandName` to `ls` so that the agent sees `ls` and processes it as normal. * `TaskStatus` - If something went wrong and you want to reflect a specific status to the user, you can set that value here. Status that start with `error: `will appear `red` in the UI. * `Stdout` and `Stderr` - set these if you want to provide some additional stdout/stderr for the task but don't necessarily want it to clutter the user's interface. This is helpful if you're doing additional compliations as part of your tasking and want to store debug or error information for later. * `Completed` - If this is set to `True` then Mythic will mark the task as done and won't allow an agent to pick it up. * `CompletionFunctionName` - if you want to have a specific local function called when the task completes (such as to do follow-on tasking or more RPC calls), then specify that function name here. This requires a matching entry in the command's `completion_functions` like follows: ``` completion_functions = {"formulate_output": formulate_output} ``` * `ParameterGroupName` - if you want to explicitly set the parameter group name instead of letting Mythic figure it out based on which parameters have values, you can specify that here. * `DisplayParams` - you can set this value to a string that you'd want the user to see instead of the `taskData.Task.OriginalParams`. This allows you to leverage the JSON structure of the popup modals for processing, but return a more human-friendly version of the parameters for operators to view. There's a new menu-item in the UI when viewing a task that you can select to view all the parameters, so on a case-by-case basis an operator can view the original JSON parameters that were sent down, but this provides a nice way to prevent large JSON blobs that are hard to read for operators while still preserving the nice JSON scripting features on the back-end. ### RPC Functionality This additional functionality is broken out into a series of files ([https://github.com/MythicMeta/MythicContainerPyPi/tree/main/mythic\_container/MythicGoRPC](https://github.com/MythicMeta/MythicContainerPyPi/tree/main/mythic_container/MythicGoRPC)) file that you can import at the top of your Python command file. They all follow the same format: ``` async def SendMythicRPC*(MythicRPC*Message) -> MythicRPC*MessageResponse ``` # Overview Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding This section talks about the different components for creating messages *from* the agent *to* a C2 docker container and how those can be structured within a C2 profile. Specifically, this goes into the following components: * How [agent messages](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-message-format) are formatted * How to perform [initial checkins](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/initial-checkin) and do encrypted [key exchanges](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/initial-checkin#encrypted-key-exchange-checkins) * How to [Get Tasking](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action_get_tasking) * How to [Post Responses](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response) * [Uploading](/version-4.0/customizing/hooking-features/action-upload) Files Another major component of the agent side coding is the actual C2 communications piece within your agent. This piece is how your agent actually implements the C2 components to do its magic. Every C2 profile has zero or more C2 Parameters that go with it. These describe things like callback intervals, API keys to use, how to format web requests, encryption keys, etc. These parameters are specific to that C2 profile, so any agent that "speaks" that c2 profile's language will leverage these parameters. If you look at the parameters in the UI, you'll see: * `Name` - When creating payloads or issuing tasking, you will get a dictionary of `name` -> `user supplied value` for you to leverage. This is a unique key per C2 profile (ex: `callback_host`) * `description` - This is what's presented to the user for the parameter (ex: `Callback host or redirector in URL format`) * `default_value` - If the user doesn't supply a value, this is the default one that will be used * `verifier_regex` - This is a regex applied to the user input in the UI for a visual cue that the parameter is correct. An example would be `^(http|https):\/\/[a-zA-Z0-9]+` for the `callback_host` to make sure that it starts with http\:// or https\:// and contains at least one letter/number. * `required` - Indicate if this is a required field or not. * `randomized` - This is a boolean indicating if the parameter should be randomized each time. This comes into play each time a payload is generated with this c2 profile included. This allows you to have a random value in the c2 profile that's randomized for each payload (like a named pipe name). * `format_string` - If `randomized` is `true`, then this is the regex format string used to generate that random value. For example, `[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}` will generate a UUID4 each time. # 8. Interactive Tasking Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/8.-interactive-tasking ## Message Structure Messages for interactive tasking have three pieces: ```json theme={"system"} { "task_id": "UUID of task", "data": "base64 of data", "message_type": int enum of types } ``` If you have a command called `pty` and issue it, then when that task gets sent to your agent, you have your normal tasking structure. That tasking structure includes an id for the task that's a UUID. All follow-on interactive input for that task uses the same UUID (`task_id` in the above message). The `data` is pretty straight forward - it's the base64 of the raw data you're trying to send to/from this interactive task. The `message_type` field is an enum of `int`. It might see complicated at first, but really it boils down to providing a way to support sending control codes through the web UI, scripting, and through an opened port. ```go theme={"system"} const ( Input = 0 Output = 1 Error = 2 Exit = 3 Escape = 4 //^[ 0x1B CtrlA = 5 //^A - 0x01 - start CtrlB = 6 //^B - 0x02 - back CtrlC = 7 //^C - 0x03 - interrupt process CtrlD = 8 //^D - 0x04 - delete (exit if nothing sitting on input) CtrlE = 9 //^E - 0x05 - end CtrlF = 10 //^F - 0x06 - forward CtrlG = 11 //^G - 0x07 - cancel search Backspace = 12 //^H - 0x08 - backspace Tab = 13 //^I - 0x09 - tab CtrlK = 14 //^K - 0x0B - kill line forwards CtrlL = 15 //^L - 0x0C - clear screen CtrlN = 16 //^N - 0x0E - next history CtrlP = 17 //^P - 0x10 - previous history CtrlQ = 18 //^Q - 0x11 - unpause output CtrlR = 19 //^R - 0x12 - search history CtrlS = 20 //^S - 0x13 - pause output CtrlU = 21 //^U - 0x15 - kill line backwards CtrlW = 22 //^W - 0x17 - kill word backwards CtrlY = 23 //^Y - 0x19 - yank CtrlZ = 24 //^Z - 0x1A - suspend process end ) ``` When something is coming from Mythic -> Agent, you'll typically see `Input`, `Exit`, or `Escape` -> `CtrlZ`. When sending data back from Agent -> Mythic, you'll set either `Output` or `Error`. This enum example also includes what the user typically sees in a terminal (ex: `^C` when you type CtrlC) along with the hex value that's normally sent. Having data split out this way can be helpful depending on what you're trying to do. Consider the case of trying to do a `tab-complete`. You want to send down data *and* the tab character (in that order). For other things though, like `escape`, you might want to send down `escape` and then data (in that order for things like control sequences). You'll probably notice that some letters are missing from the control codes above. There's no need to send along a special control code for `\n` or `\r` because we can send those down as part of our input. Similarly, clearing the screen isn't useful through the web UI because it doesn't quite match up as a full TTY. ## Message Location This data is located in a similar way to SOCKS and RPFWD: ```json theme={"system"} { "action": "some action", "interactive": [ {"task_id": UUID, "data": "base64", "message_type": 0 } ] } ``` the `interactive` keyword takes an array of these sorts of messages to/from the agent. This keyword is at the same level in the JSON structure as `action`, `socks`, `responses`, etc. This means that if you send a `get_tasking` request OR a `post_response` request, you could get back `interactive` data. The same goes for `rpfwd`, `socks`, and `delegates`. When sending responses back for interactive tasking, you send back an array in the `interactive` keyword just like you got the data in the first place. ## Reserved file-editor messages Mythic 4.0 reserves message types `100` through `199` for interactive file editing. The currently defined types are: | Value | Name | Direction | | ----- | -------------------- | -------------- | | `100` | `FileEditorRequest` | Mythic → agent | | `101` | `FileEditorResponse` | agent → Mythic | | `102` | `FileEditorError` | agent → Mythic | Their base64-decoded `data` is a JSON file-editor message, not terminal bytes. Do not feed these values to a PTY or reuse them for a payload-specific extension. See [File Editor Protocol](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol) for the request, snapshot, conflict, and error schemas. # 4. Submitting Responses Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response The main difference between submitting a response with a `post_response` and submitting responses with `get_tasking` is that in a `get_tasking` message with a `responses` key, you'll **also** get back additional tasking that's available. With a `post_response` message and a `responses` key, you *won't* get back additional tasking that's ready for your agent. You can still get `socks`, `rpfwd`, `interact`, and `delegates` messages as part of your message back from Mythic, but you won't have a `tasks` key. ## Message Request The contents of the JSON message from the agent to Mythic when posting tasking responses is as follows: ```json theme={"system"} Base64( CallbackUUID + JSON( { "action": "post_response", "responses": [ { "task_id": "uuid of task", ... response message (see below) }, { "task_id": "uuid of task", ... response message (see below) } ], //if we were passing messages on behalf of other agents "delegates": [ {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"}, {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"} ] } ) ) ``` There are two things to note here: * `responses` - This parameter is a list of all the responses for each tasking. * For each element in the responses array, we have a dictionary of information about the response. We also have a `task_id` field to indicate which task this response is for. After that though, comes the actual response output from the task. * If you don't want to hook a certain feature (like sending keystrokes, downloading files, creating artifacts, etc), but just want to return output to the user, the response section can be as simple as: `{"task_id": "uuid of task", "user_output": "output of task here"}` * You can find many fields to send in the hooking features section, but outside of that you can set: * `completed` - boolean field to indicate that the task is done or not * `status` - string field to indicate the current status of the task. If the task completes successfully, you can set this to `success`, otherwise you can use it to indicate a generic error mesage to the user. If you start the status with `error: `then in the Mythic UI that status message will turn red to help indicate an error. Any other status you set will appear as blue text. If you want to return a more verbose error message, then you can set `completed: true`, `status: "error: auth failed`, and then `user_output: "some more complex output that displays in the body of the UI under the task where you can have much more room`. * Each response style is described in [Hooking Features](/version-4.0/customizing/hooking-features). The format described in each of the Hooking features sections replaces the `... response message` piece above * To continue adding to that JSON response, you can indicate that a command is finished by adding `"completed": true` or indicate that there was an error with `"status": "error"`. * `delegates` - This parameter is not required, but allows for an agent to forward on messages from other callbacks. This is the peer-to-peer scenario where inner messages are passed externally by the egress point. Each of these messages is a self-contained "[Agent Message](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-message-format)". Anything you put in `user_output` will go directly to the user to see. There's no additional processing that happens. If you want to perform additional processing on the response, then instead of `user_output` use the `process_response` key. This will allow you to perform additional processing on whatever is passed through the `process_response` key - from here, if you want to register something for the user to see, you'll need to use MythicRPCCreateResponse (you can use any MythicRPC at this point to register files, create credentials, etc). ### Agent RPC Mythic 4.0 lets an agent ask its payload type container to run a named payload-wide RPC hook. Add `agent_rpc` to one response entry: ```json theme={"system"} { "task_id": "task-uuid", "agent_rpc": { "name": "resolve_target", "arguments": { "host": "workstation-01", "service": "spooler" } } } ``` The request is asynchronous. A later `get_tasking` or `post_response` result returns a normal response entry with the original agent task UUID, `status`, and the hook's `output`. See [Agent RPC](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-rpc) for Python and Go handlers. ### v4 file responses Agent-to-Mythic downloads can now use `total_size` plus zero-based `chunk_offset`, and can resume an existing `file_id` with `resume: true`. Keep numbered `chunk_num` and offset `chunk_offset` modes separate. See [File Download Agent → Mythic](/version-4.0/message-flow/file-download-agent-greater-than-mythic). Interactive message types `100`, `101`, and `102` are reserved for the [File Editor Protocol](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol). ## Message Response Mythic responds with the following message format for post\_response requests: ```json theme={"system"} Base64( CallbackUUID + JSON( { "action": "post_response", "responses": [ { "task_id": UUID, "status": "success" or "error", "error": 'error message if it exists' } ], //if we were passing messages on behalf of other agents "delegates": [ {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"}, {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"} ] } ) ) ``` If your initial `responses` array to Mythic has something improperly formatted and Mythic can't deserialize it into GoLang structs, then Mythic will simply set the `responses` array going back as empty. So, you can't *always* check for a matching response array entry for each response you send to Mythic. In this case, Mythic can't respond back with `task_id` in this response array because it failed to deserialize it completely. There are two things to note here: * `responses` - This parameter is always a list and contains a success or error + error message for each task that was responded to. * `delegates` - This parameter contains any responses for the messages that came through in the first message This message format also can take in `socks`, `rpfwd`, `interactive`, `alerts`, `edges`, and `delegates` keys with their data as well. Just like with the `get_tasking` message, you can send all of that data along with each message. # 3. Get Tasking Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action_get_tasking This page describes the format for getting new tasking ## Message Request The contents of the JSON message from the agent to Mythic when requesting tasking is as follows: ``` Base64( CallbackUUID + JSON( { "action": "get_tasking", "tasking_size": 1, //indicate the maximum number of tasks you want back //if passing on messages for other agents, include the following "delegates": [ {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"}, {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"} ], "get_delegate_tasks": true, //optional, defaults to true } ) ) ``` There are two things to note here: * `tasking_size` - This parameter defaults to one, but allows an agent to request how many tasks it wants to get back at once. If the agent specifies `-1` as this value, then Mythic will return *all* of the tasking it has for that callback. * `delegates` - This parameter is not required, but allows for an agent to forward on messages from other callbacks. This is the peer-to-peer scenario where inner messages are passed externally by the egress point. Each of these agentMessage is a self-contained "[Agent Message](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-message-format)" and the `c2_profile` indicates the name of the C2 Profile used to connect the two agents. This allows Mythic to properly decode/translate the messages even for nested messages. * `get_delegate_tasks` - This is an optional parameter. If you don't include it, it's assumed to be `True`. This indicates whether or not this `get_tasking` request should also check for tasks that belong to callbacks that are reachable from this callback. So, if agentA has a route to agentB, agentB has a task in the `submitted` state, and agentA issues a `get_tasking`, agentA can decide if it wants just its own tasking or if it also wants to pick up agentB's task as well. * Why does this matter? This is helpful if your linked agents issue their own periodic `get_tasking` messages rather than simply waiting for tasking to come to them. This way the parent callback (agentA in this case) doesn't accidentally consume and toss aside the task for agentB; instead, agentB's own periodic `get_tasking` message has to make its way up to Mythic for the task to be fetched. ## Message Response Mythic responds with the following message format for get\_tasking requests: ``` Base64( CallbackUUID + JSON( { "action": "get_tasking", "tasks": [ { "command": "command name", "parameters": "command param string", "timestamp": 1578706611.324671, //timestamp provided to help with ordering "id": "task uuid", } ], //if we were passing messages on behalf of other agents "delegates": [ {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"}, {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"} ] } ) ) ``` There are a few things to note here: * `tasks` - This parameter is always a list, but contains between 0 and `tasking_size` number of entries. * `parameters` - this encapsulates the parameters for the task. If a command has parameters like: `{"remote_path": "/users/desktop/test.png", "file_id": "uuid_here"}`, then the `params` field will have that JSON blob as a STRING value (i.e. the command is responsible to parse that out). * `delegates` - This parameter contains any responses for the messages that came through in the first message. This `get_tasking` request **CAN** also include a `responses` field, `socks`, `rpfwd`, `edges`, `alerts`, and `interactive` fields. This means you can technically *only* do `checkin` and `get_tasking` messages since you can forward responses in this message. The reason for this is you might not want to have to send TWO messages per sleep interval - ex: you don't want to post the response from an output *and* make a `get_tasking` request back-to-back, but you also don't want to *not* do `get_tasking` requests while you're periodically sending task responses back. # 1. Agent Message Format Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-message-format This page describes how an agent message is formatted All messages go to the `/agent_message` endpoint via the associated C2 Profile docker container. These messages can be: * POST request * message content in body * GET request * message content in FIRST header value * message content in FIRST cookie value * message content in FIRST query parameter * For query parameters, the Base64 content must be URL Safe Encoded - this has different meaning in different languages, but means that for the "unsafe" characters of `+` and `/`, they need to be swapped out with `-` and `_` instead of %encoded. Many languages have a special Base64 Encode/Decode function for this. If you're curious, this is an easy site to check your encoding: [https://www.base64url.com/](https://www.base64url.com/) * message content in body All agent messages have the same general structure, but it's the message inside the structure that varies. Each message has the following general format shown below. The message is a JSON string, which is then typically encrypted (doesn't have to be though), with a UUID prepended, and then the entire thing base64 encoded: ``` base64( UUID + EncBlob( //the following is all encrypted JSON({ "action": "", //indicating what the message is - required "...": ... // JSON data relating to the action - required //this piece is optional and just for p2p mesh forwarding "delegates": [ {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"}, {"message": agentMessage, "c2_profile": "ProfileName", "uuid": "uuid here"} ] }) ) ) ``` There are a couple of components to note here in what's called an `agentMessage`: * `UUID` - This UUID varies based on the phase of the agent (initial checkin, staging, fully staged). This is a 36 character long of the format `b50a5fe8-099d-4611-a2ac-96d93e6ec77b` . Optionally, if your agent is dealing with more of a binary-level specification rather than strings, you can use a 16 byte **big**-endian value here for the binary representation of the UUID4 string. * `EncBlob` - This section is encrypted, typically by an AES256 key, but when agents are staging, this could be encrypted with RSA keys or as part of some other custom crypto/staging you're doing as part of your payload type container. . * `JSON` - This is the actual message that's being sent by the agent to Mythic or from Mythic to an agent. If you're doing your own custom message format and leveraging a translation container, this this format will obviously be different and will match up with your custom version; however, in your translation container you will need to convert back to this format so that Mythic can process the message. * `action` - This specifies what the rest of the message means. This can be one of the following: * staging\_rsa * checkin * get\_tasking * post\_response * translation\_staging (you're doing your own staging) * `...` - This section varies based on the action that's being performed. The different variations here can be found in [Hooking Features](/version-4.0/customizing/hooking-features) , [Initial Checkin](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/initial-checkin), and [Agent Responses](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action_get_tasking) * `delegates` - This section contains messages from other agents that are being passed along. This is how messages from nested peer-to-peer agents can be forwarded out through and egress callback. If your agent isn't forwarding messages on from others (such as in a p2p mesh or as an egress point), then you don't need this section. More info can be found here: [Delegates (p2p)](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/delegates) * `+` - when you see something like `UUID + EncBlob`, that's referring to byte concatenation of the two values. You don't need to do any specific processing or whatnot, just right after the first elements bytes put the second elements bytes Let's look at a few concrete examples without encryption and already base64 decoded: ```json theme={"system"} a21bab2e-462e-49ab-9800-fbedaf53ad15 { "action":"checkin", "uuid":"a21bab2e-462e-49ab-9800-fbedaf53ad15", "user":"bob", "domain":"domain.com", "pid":123, } ``` ```json theme={"system"} a21bab2e-462e-49ab-9800-fbedaf53ad15 { "action":"get_tasking", "tasking_size": -1 } ``` ```json theme={"system"} a21bab2e-462e-49ab-9800-fbedaf53ad15 { "action":"get_tasking", "tasking_size": -1, "delegates": [ {"message": agentMessage, "c2_profile": "tcp", "uuid": "uuid here"}, {"message": agentMessage, "c2_profile": "smb", "uuid": "uuid here"} ] } ``` ```json theme={"system"} a21bab2e-462e-49ab-9800-fbedaf53ad15 { "action":"post_response", "responses": [ { "task_id": "c34bab2e-462e-49ab-9800-fbedaf53ad15", "completed": true, "user_output": "hello world", }, { "task_id": "bab3ab2e-462e-49ab-9800-fbedaf53ad15", "completed": false, "user_output": "downloading file...", "download": { "total_chunks": 12, "chunk_size": 512000, "filename": "test.txt", "full_path": "C:\\Users\\test\\test.txt", "host": "ABC.COM", "is_screenshot": false } }, ] } ``` ## Message Format for Custom Agent Messages If you want to have a completely custom agent message format (different format for JSON, different field names/formatting, a binary or otherwise formatted protocol, etc), then there's only two things you have to do for it to work with Mythic. 1. Base64 encode the message 2. The first bytes of the message must be the associated UUID (payload, staging, callback). Mythic uses these first few bytes to do a lookup in its database to find out everything about the message. Specifically for this case, it looks up if the associated payload type has a translation container, and if so, ships the message off to it first before trying to process it. # 9. Agent RPC Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/agent-rpc Asynchronous payload-type container requests initiated by an agent Agent RPC lets an agent ask its payload type container to perform an asynchronous, command-specific operation. The request arrives in a normal `post_response` message and the result is delivered in the callback's next response. ## Agent request Add `agent_rpc` to a response for the task that owns the request: ```json theme={"system"} {"action": "post_response", "responses": [ { "task_id": "agent-task-uuid", "agent_rpc": { "name": "lookup", "arguments": { "key": "host-configuration", "include_metadata": true } } } ] } ``` `arguments` can be any JSON-compatible value. Mythic uses the task to route the request to the command's payload type container and preserve callback/task correlation. ## Python handler Override `agent_rpc` on the command class: ```python theme={"system"} from typing import Any from mythic_container.MythicCommandBase import ( PTTaskAgentRPCMessageResponse, PTTaskMessageAllData, ) async def agent_rpc( self, task: PTTaskMessageAllData, name: str, arguments: Any, ) -> PTTaskAgentRPCMessageResponse: if name != "lookup": return PTTaskAgentRPCMessageResponse( Status="error", Output=f"unsupported RPC {name}", ) return PTTaskAgentRPCMessageResponse( Status="success", Output={"value": await lookup_value(arguments["key"])}, ) ``` ## Go handler Register a payload-wide handler on the payload definition: ```go theme={"system"} agentstructs.AllPayloadData.Get("myagent").AddAgentRPCFunction( func( ctx context.Context, task *agentstructs.PTTaskMessageAllData, name string, arguments any, ) agentstructs.PTTaskAgentRPCMessageResponse { if name != "lookup" { return agentstructs.PTTaskAgentRPCMessageResponse{ Status: "error", Output: "unsupported RPC", } } return agentstructs.PTTaskAgentRPCMessageResponse{ Status: "success", Output: map[string]any{"value": "example"}, } }, ) ``` The Mythic sets `callback_id` and `agent_task_id` on the response. Handler code supplies only `status` and `output`. ## Agent result Mythic returns the asynchronous result in the normal `responses` array: ```json theme={"system"} { "action": "get_tasking", "responses": [ { "task_id": "agent-task-uuid", "status": "success", "output": {"value": "example"} } ] } ``` Treat `output` as an application-level response. Validate its shape on the agent and handle an error status without assuming tasking will also be present. # 7. Peer-to-peer messages Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/delegates ## What are delegate messages Delegate messages are messages that an agent is forwarding on behalf of another agent. The use case here is an agent forwarding peer-to-peer messages for a linked agent. Mythic supports this by having an optional `delegates` array in messages. An example of what this looks like is in the next section, but this `delegates` array can be part of any message from an agent to mythic. ## Delegate parameter format When sending delegate messages, there's a simple standard format: #### Agent -> Mythic Message ```json theme={"system"} { "action": "some action here", "delegates": [ { "message": agentMessage, "uuid": UUID, "c2_profile": "ProfileName" } ] } ``` Within a delegates array are a series of JSON dictionaries: * `UUID` - This field is some UUID identifier used by the agent to track where a message came from and where it should go back to. Ideally this is the same as the UUID for the callback on the other end of the connection, but can be any value. If the agent uses a value that does not match up with the UUID of the agent on the other end, Mythic will indicate that in the response. This allows the middle-man agent to generate some UUID identifier as needed upon first connection and then learn of and use the agent's real UUID once the messages start flowing. * `message` - this is the actual message that the agent is transmitting on behalf of the other agent * `c2_profile` - This field indicates the name of the C2 Profile associated with the connection between this agent and the delegated agent. This allows Mythic to know how these two agents are talking to each other when generating and tracking connections. #### Mythic -> Agent Reply ```json theme={"system"} { "action": "some action here", "delegates": [ { "message": agentMessage, "uuid": "same UUID as the message agent -> mythic", "new_uuid": UUID that mythic uses } ] } ``` The `new_uuid` field indicates that the `uuid` field the agent sent doesn't match up with the UUID in the associated message. If the agent uses the right UUID with the agentMessage then the response would be: ```json theme={"system"} { "action": "some action here", "delegates": [ { "message": agentMessage, "uuid": "same UUID as the message agent -> mythic" } ] } ``` Why do you care and why is this important? This allows an agent to randomly generate its own UUID for tracking connections with other agents and provides a mechanism for Mythic to reveal the right UUID for the callback on the other end. This implicitly gives the agent the right UUID to use if it needs to announce that it lost the route to the callback on the other end. If Mythic didn't correct the agent's use of UUID, then when the agent loses connection to the P2P agent, it wouldn't be able to properly indicate it to Mythic. This means that if you send a `get_tasking` request OR a `post_response` request, you could get back `delegates` data. The same goes for `rpfwd`, `interactive`, and `socks`. ## Example walkthrough Ok, so let's walk through an example: * agentA is an egress agent speaking HTTP to Mythic. agentA sends messages directly to Mythic, such as the `{"action": "get_tasking", "tasking_size": 1}`. All is well. * somehow agentB gets deployed and executed, this agent (for sake of example) opens a port on its host (same host as agentA or another one, doesn't matter) * agentA connects to agentB (or agentB connects to agentA if agentA opened the port and agentB did a connection to it) over this new P2P protocol (smb, tcp, etc) * agentB sends to agentA a staging message if it's doing EKE, a checkin message if it's already an established callback (like the example of re-linking to a callback), or a checkin message if it's doing like a static PSK or plaintext. The format of this message is exactly the same as if it wasn't going through agentA * agentA gets this message, and is like "new connection, who dis?", so it makes a random UUID to identify whomever is on the other end of the line and forwards that message off to Mythic with the next message agentA would be sending anyway. So, if the next message that agentA would send to Mythic is another get tasking, then it would look like: `{"action": "get_tasking", "tasking_size": 1, "delegates": [ {"message": agentB's message, "c2_profile": "Name of the profile we're using to communicate", "uuid": "myRandomUUID"} ] }`. That's the message agentA sends to Mythic. * Mythic gets the message, processes the get\_tasking for agentA, then sees it has `delegate` messages (i.e. messages that it's passing along on behalf of other agents). So Mythic recursively processes each of the messages in this array. Because that `message` value is the same as if agentB was talking directly to Mythic, Mythic can parse out the right UUIDs and information. The `c2_profile` piece allows Mythic to look up any c2-specific encryption information to pass along for the message. Once Mythic is done processing the message, it sends a response back to agentA like: `{"action": "get_tasking", "tasks": [ normal array of tasks ], "delegates": [ {"message": "response back to what agentB sent", "uuid": "myRandomUUID that agentA generated", "new_uuid": "the actual UUID that Mythic uses for agentB"} ] }`. If this is the first time that Mythic has seen a delegate from agentB through agentA, then Mythic knows that there's a route between the two and via which C2 profile, so it can automatically display that in the UI * agentA gets the response back, processes its get\_tasking like normal, sees the `delegates` array and loops through those messages. It sees "oh, it's myRandomUUID, i know that guy, let me forward it along" and also sees that it's been calling agentB by the wrong name, it now knows agentB's real name according to Mythic. This is important because if agentA and agentB ever lose connection, agentA can report back to Mythic that it can no longer to speak to agentB with the right UUID that Mythic knows. This same process repeats and just keeps nesting for agentC that would send a message to agentB that would send the message to agentA that sends it to Mythic. agentA can't actually decrypt the messages between agentB and Mythic, but it doesn't need to. It just has to track that connection and shuttle messages around. Now that there's a "route" between the two agents that Mythic is aware of, a few things happen: * when agentA now does a `get_tasking` message (with or without a delegate message from agentB), if mythic sees a tasking for agentB, Mythic will automatically add in the same `delegates` message that we saw before and send it back with agentA so that agentA can forward it to agentB. That's important - agentB never had to ask for tasking, Mythic automatically gave it to agentA because it knew there was a route between the two agents. * if you DON"T want that to happen though - if you want agentB to keep issuing get\_tasking requests through agentA with periodic beaconing, then in agentA's get\_tasking you can add `get_delegate_tasks` to False. i.e (`{"action": "get_tasking", "tasking_size": 1, "get_delegate_tasks": false}`) then even if there are tasks for agentB, Mythic WILL NOT send them along with agentA. agentB will have to ask for them directly What happens when agentA and agentB can no longer communicate though? agentA needs to send a message back to Mythic to indicate that the connection is lost. This can be done with the [edges](/version-4.0/customizing/hooking-features/linking-agents/action-p2p_info) key. Using all of the information agentA has about the connection, it can announce that Mythic should remove a edge between the two callbacks. This can either happen as part of a response to a tasking (such as an explicit task to unlink two agents) or just something that gets noticed (like a computer rebooted and now the connection is lost). In the first case, we see the example below as part of a normal post\_response message: ```json theme={"system"} { "user_output": "some user output here", "task_id": "uuid of task here", "edges": [ { "source": "uuid of source callback", "destination": "uuid of destination callback", "action": "remove" "c2_profile": "name of the c2 profile used in this connection" } ] } ``` If this wasn't part of some task, then there would be no task\_id to use. In this case, we can add the same `edges` structure at a higher point in the message: ```json theme={"system"} { "action": "get_tasking" (could be "post_response", "upload", etc) "edges": [ { "source": "uuid of source callback", "destination": "uuid of destination callback", "action": "add" or "remove" "c2_profile": "name of the c2 profile used in this connection" } ] } ``` ### Agent Expectations How is an agent supposed to work with a Peer-to-peer (P2P) profile? It's pretty simple and largely the same as working with a Push C2 egress connection: * If a payload is executed (it's not a callback yet), then make a connection to your designated P2P method (named pipes, tcp ports, etc). Once a connection is established, start your normal encrypted key exchange or checkin process. * If an existing callback loses connection for some reason, then make a connection to your designated P2P method (named pipes, tcp ports, etc). Once a connection is established, send your checkin message again to inform Mythic of your existence * At this point, just wait for messages to come to you (no need to do a get\_tasking poll) and as you get any data (socks, edges, alerts, responses, etc) just send them out through your p2p connection. # 10. File Editor Protocol Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/file-editor-protocol Implement Mythic's interactive, versioned remote text editor in an agent command Commands that advertise `task_response:file_editor` in the `supported_ui_features` use reserved interactive task message types to exchange editor requests and snapshots. | Message type | Value | Direction | | -------------------- | ----: | --------------- | | `FileEditorRequest` | `100` | Mythic to agent | | `FileEditorResponse` | `101` | Agent to Mythic | | `FileEditorError` | `102` | Agent to Mythic | The surrounding interactive object is unchanged: ```json theme={"system"} {"action": "post_response", "interactive": [ { "task_id": "agent-task-uuid", "data": "base64-encoded JSON document", "message_type": 100 } ] } ``` ## Request document The decoded JSON data request always contains `action` and a unique `request_id`. ```json theme={"system"} { "action": "save", "request_id": "b2fbcc2c-bac9-4ee7-954f-d42c69ca279f", "file_id": "Mythic staged-file UUID", "expected_sha1": "last snapshot SHA-1", "force_overwrite": false } ``` Supported actions: * `refresh`: read the current remote file and return a new snapshot; * `save`: fetch the staged `file_id`, compare `expected_sha1`, write the file, and return a new snapshot; * `close`: finish the editor session and complete the parent task. `force_overwrite` is omitted unless the operator explicitly chooses **Save anyway** after reviewing a conflict. ## Successful snapshot Register the current file contents with Mythic and return its UUID. The UI fetches the tracked file using authenticated download handling and uses its metadata for filename, remote path, size, and SHA-1. ```json theme={"system"} { "request_id": "b2fbcc2c-bac9-4ee7-954f-d42c69ca279f", "file_id": "snapshot-file-uuid" } ``` Send this JSON as base64 `data` with message type `101`. Every successful refresh/save becomes a version-history entry. ## Errors and conflicts Return message type `102` with an application error: ```json theme={"system"} { "request_id": "b2fbcc2c-bac9-4ee7-954f-d42c69ca279f", "code": "write_failed", "message": "permission denied" } ``` If the remote file changed since the last snapshot, do not overwrite it. Return a conflict and the current hash: ```json theme={"system"} { "request_id": "b2fbcc2c-bac9-4ee7-954f-d42c69ca279f", "code": "conflict", "message": "file changed after the editor snapshot", "current_sha1": "current remote SHA-1", "file_id": "optional reusable staged-file UUID" } ``` The UI lets the operator refresh, inspect the staged edit, or deliberately resend `save` with `force_overwrite: true`. ## Limits and lifecycle * The UI accepts valid UTF-8 text up to 2,000,000 bytes. * Keep the parent task open while editing; follow-on requests use it as their parent. * After `close`, mark the parent task complete. History remains visible but read-only. * Correlate responses by `request_id`; multiple refresh/save operations may be in flight over an unreliable C2 path. # 2. Checkin Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/initial-checkin This page has the various different ways the initial checkin can happen and the encryption schemes used. ### A note about UUIDs You will see a bunch of UUIDs mentioned throughout this section. All UUIDs are UUIDv4 formatted UUIDs (36 characters in length) and formatted like: ``` b446b886-ab97-49b2-b240-969a75393c06 ``` In general, the UUID concatenated with the encrypted message provides a way to give context to the encrypted message without requiring a lot of extra pieces and without having to do a bunch of nested base64 encodings. 99% of the time, your messages will use your callbackUUID in the outer message. The outer UUID gives Mythic information about how to decrypt or interpret the following encrypted blob. In general: * **payloadUUID** as the outer UUID tells Mythic to look up that payload UUID, then look up the C2 profile associated with it, find a parameter called `AESPSK`, and use that as the key to decrypt the message * **tempUUID** as the outer UUID tells Mythic that this is a staging process. So, look up the UUID in the staging database to see information about the blob, such as if it's an RSA encrypted blob or is part of a Diffie-Hellman key exchange * **callbackUUID** as the outerUUID tells Mythic that this is a full callback with an established encryption key or in plaintext. However, when your payload first executes, it doesn't have a callbackUUID, it's just a payloadUUID. This is why you'll see clarifiers as to which UUID we're referring to when doing specific messages. The whole goal of the `checkin` process is to go from a payload (and thus payloadUUID) to a full callback (and thus callbackUUID), so at the end of staging and everything you'll end up with a new UUID that you'll use as the outer UUID. If your already existing callback sends a checkin message more than once, Mythic simply uses that information to *update* information about the callback rather than trying to register a new callback. In egress agent messages, you can opt for a 16 Byte big endian format for the UUID. If Mythic gets a message from an agent with this format of UUID, then it will respond with the same format for the UUID. However, currently for P2P messages Mythic doesn't track the format for the UUID of the agent, so these will get the standard 36 character long UUID String. ## Plaintext Checkin The plaintext checkin is useful for testing or when creating an agent for the first time. When creating payloads, you can generate encryption keys *per c2 profile*. To do so, the C2 Profile will have a parameter that has an attribute called `crypto_type=True`. This will then signal to Mythic to either generate a new per-payload AES256\_HMAC key or (if your agent is using a translation container) tell your agent's translation container to generate a new key. In the `http` profile for example, this is a `ChooseOne` option between `aes256_hmac` or `none`. If you're doing plaintext comms, then you need to set this value to `none` when creating your payload. Mythic looks at that outer `PayloadUUID` and checks if there's an associated encryption key with it in the database. If there is, Mythic will automatically try to decrypt the rest of the message, which will fail. This checkin has the following format: ```json theme={"system"} Base64( PayloadUUID + JSON({ "action": "checkin", // required "uuid": "payload uuid", //uuid of the payload - required "ips": ["127.0.0.1"], // internal ip addresses - optional "os": "macOS 10.15", // os version - optional "user": "its-a-feature", // username of current user - optional "host": "spooky.local", // hostname of the computer - optional "pid": 4444, // pid of the current process - optional "architecture": "x64", // platform arch - optional "domain": "test", // domain of the host - optional "integrity_level": 3, // integrity level of the process - optional "external_ip": "8.8.8.8", // external ip if known - optional "encryption_key": "base64 of key", // encryption key - optional "decryption_key": "base64 of key", // decryption key - optional "process_name": "osascript", // name of the current process - optional }) ) ``` `integrity_level` is an integer from 1-4 that indicates the integrity level of the callback. On Windows, these levels correspond to low integrity (1) , medium integrity (2), high integrity (3), or SYSTEM integrity (4). On Linux, these don't have a great mapping, but you can think of (2) as a standard user, (3) as a user that's in the sudoers file or is able to run sudo, and (4) as the root user. * The JSON section is not encrypted in any way, it's all plaintext. Here's an example checkin message message: ``` ODA4NDRkMTktOWJmYy00N2Y5LWI5YWYtYzZiOTE0NGMwZmRjeyJhY3Rpb24iOiJjaGVja2luIiwiaXBzIjpbIjE3Mi4xNi4xLjEiLCIxOTIuMTY4LjAuMTE4IiwiMTkyLjE2OC4yMjguMCIsIjE5Mi4xNjguNTMuMSIsIjE5OC4xOS4yNDkuMyIsImZkMDc6YjUxYTpjYzY2OjA6YTYxNzpkYjVlOmFiNzplOWYxIiwiZmQ1MzpkYTlmOjk4MWE6NWI0Mjo4YjA6MzNjOTplMGE1OjIyNTYiLCJmZTgwOjoxIiwiZmU4MDo6MTQ3ZDpkYWZmOmZlZWM6YjQ2NCIsImZlODA6OjE0N2Q6ZGFmZjpmZWVjOmI0NjUiLCJmZTgwOjoxNDdkOmRhZmY6ZmVlYzpiNDY2IiwiZmU4MDo6MTQ3ZDpkYWZmOmZlZWM6YjQ2NyIsImZlODA6OjIyOmQxYzk6MWMyZTo5Mjk3IiwiZmU4MDo6MzQ3ZDpkYWZmOmZlY2U6M2ExNyIsImZlODA6OjNjMmQ6ODZiYjo4ZDk5OjJjNjciLCJmZTgwOjo4ODU3OjJhZmY6ZmU2NToyNTExIiwiZmU4MDo6ODg1NzoyYWZmOmZlNjU6MjUxMSIsImZlODA6OmFlZGU6NDhmZjpmZTAwOjExMjIiLCJmZTgwOjpjZTgxOmIxYzpiZDJjOjY5ZSIsImZlODA6OmQxMDM6N2IyNDo2YzliOjhlMjIiXSwib3MiOiJWZXJzaW9uIDEzLjQgKEJ1aWxkIDIyRjY2KSIsInVzZXIiOiJpdHNhZmVhdHVyZSIsImhvc3QiOiJzcG9va3kubG9jYWwiLCJwaWQiOjY1ODYsInV1aWQiOiI4MDg0NGQxOS05YmZjLTQ3ZjktYjlhZi1jNmI5MTQ0YzBmZGMiLCJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImRvbWFpbiI6IiIsImludGVncml0eV9sZXZlbCI6MiwiZXh0ZXJuYWxfaXAiOiIiLCJwcm9jZXNzX25hbWUiOiIvVXNlcnMvaXRzYWZlYXR1cmUvRG9jdW1lbnRzL015dGhpY0FnZW50cy9wb3NlaWRvbi9QYXlsb2FkX1R5cGUvcG9zZWlkb24vcG9zZWlkb24vYWdlbnRfY29kZS9wb3NlaWRvbl93ZWJzb2NrZXRfaHR0cC5iaW4ifQ== ``` The checkin has the following response: ```json theme={"system"} Base64( PayloadUUID + JSON({ "action": "checkin", "id": "UUID", // new UUID for the agent to use "status": "success" }) ) ``` From here on, the agent messages use the new UUID instead of the payload UUID. This allows Mythic to track a payload trying to make a new callback vs a callback based on a payload. ## Static Encryption Checkin This method uses a static AES256 key for all communications. This will be different for each payload that's created. When creating payloads, you can generate encryption keys *per c2 profile*. To do so, the C2 Profile will have a parameter that has an attribute called `crypto_type=True`. This will then signal to Mythic to either generate a new per-payload AES256\_HMAC key or (if your agent is using a translation container) tell your agent's translation container to generate a new key. In the `http` profile for example, this is a `ChooseOne` option between `aes256_hmac` or `none`. The key passed down to your agent during build time will be the base64 encoded version of the 32Byte key. The message sent will be of the form: ```json theme={"system"} Base64( PayloadUUID + AES256( JSON({ "action": "checkin", // required "uuid": "payload uuid", //uuid of the payload - required "ips": ["127.0.0.1"], // internal ip addresses - optional "os": "macOS 10.15", // os version - optional "user": "its-a-feature", // username of current user - optional "host": "spooky.local", // hostname of the computer - optional "pid": 4444, // pid of the current process - optional "architecture": "x64", // platform arch - optional "domain": "test", // domain of the host - optional "integrity_level": 3, // integrity level of the process - optional "external_ip": "8.8.8.8", // external ip if known - optional "encryption_key": "base64 of key", // encryption key - optional "decryption_key": "base64 of key", // decryption key - optional "process_name": "osascript", // name of the current process - optional }) ) ) ``` Here's an example message with encryption key of `hfN9Nk29S8LsjrE9ffbT9KONue4uozk+/TVMyrxDvvM=` and message: ``` ODA4NDRkMTktOWJmYy00N2Y5LWI5YWYtYzZiOTE0NGMwZmRjnZ/FcM9jnfvzAv/RYFPAvkGH8+nWHAGqxcBXSlPvq8jbCRoZrVvSSZOxNwg15q3Etz9hEb7Qunv1Sm3/8SSzp+ne4fxFObunQWzHo+7tS68csvn/uxqhiyvD83KK66xtPyGzPFlK1ZXD+wxDbo2M3iSYPEp0m5w+rQhzm5aTA6Gk6p0KSXovYvnY3TsJtdgVPlY1cFt75UzTd0iIFU8hJ+KbhyMUjJujLA6++sVrXuFps2TbAi21Z5Hr/g3/S6HAk/RSedKyXEZ6Hbbgx3gESsHa/QuVjP9Lz+Y6H9I4DtgEunCHddvruJUPqYxFGT2m8WbGc6AH6+m2ucexym0yBUryuFWfsrW6QSfcGUaVb4DWrVHtqHcXctYRNb7pOf0T/P26pFt77fgii4j0RgzTGod9QDWhSfvte+ffUWjsWKyixUffjIffj45sgDS0tvtT2Rej8gFiIpAs9F/oOH/ps5pRQeflULd1eH0GKh5WUcDwsjUa89KeOcts44J+E5+7trQ3q2q9Uy8S96DM8Nr5QryokeCD7J0goKZQPdutVXzwIvI9RT7zCQpV8CrRTpQ63L9P9IhIpyT+TDvorQd0v/I/DGb6Ev/ZUAxbyAR0JLJGjYYv1NUno5Ru2Plv1wsn82YanVF1V2LE1ii6DC7jclrkgfKN9Qhli+hIiUwSJ3YvFTT1ybHf/Fyw4ZZ6PiOIZIWgcJmHUHx//1TNvlTrmABitRpwb75yuJ6ZfYnKv/BlrQtJ9nFveNeYKP/rL7uYwPq3RY9IJRK7DBOqy53qiiysRfhimraW//sXc6duBmASW0ijZ21HKaqdVr72PMIJpEWghIznzpzEVpJqYj0uR9K/bL5W6kfIP43dyDBzGAGd87VBIcUTsIJLWaOHGPVmO3OmmtIfW34ivsX1TElTVjyrmKneQ+OTWww0RbXZdE5swvucXqC8wTuwybgwQWVPCvrBTBlv3iXgkP4dOjbvr1YZS+HpdbT5OEhwIqnDCXIqItVYx9Hz5BdfcBFbXUXk0SIQzWQj9xw+olYYQMrxomNvjuGxBkOmhTJf6yUyRK1Mp8b992FPBzLVRexYFc5FZxrI8CJeS91R3C21gb3SZH4EdKk1S3mR40O427TGYG5Hcqzqz5n0M6+cWORxUp7LKT34kDwgzHQK1h5kEoaGvGB1QDtx8GLsbfk/BqBoV2oHGJP1HHbVgYMgBTrkYObXOKFW8WyaUWcB1p/dSmW5Ww== ``` The message response will be of the form: ```json theme={"system"} Base64( PayloadUUID + AES256( JSON({ "action": "checkin", "id": "callbackUUID", // callback UUID for the agent to use "status": "success" }) ) ) ``` Here's that sample message's response: ``` ODA4NDRkMTktOWJmYy00N2Y5LWI5YWYtYzZiOTE0NGMwZmRjyHcKh56jliiv87ReJE7QqK8edpLcV5cfywt8Lg1jWJzPc8b37zB9/mliG1HKH0dyF/jZqiSzUfSWEjgfhKa3DoLUqJOvnbpOYYsL3GvfWrps3/HQhZogSjwXnQmTehbADhXrOqA4622YMFjJbpykxdq7kpufn+12GDidwNybOlbg9ej8D/PpZVVdqL2RdASe ``` From here on, the agent messages use the new UUID instead of the payload UUID. This first message from Agent -> Mythic has the Payload UUID as the outer UUID *and* the Payload UUID inside the checkin JSON message. Once the agent gets the reply with a callbackUUID, all future messages will have this callbackUUID as the outer UUID. With that same example from above, the agent gets back a response of success with a new callback UUID. From there on, since it's a static encryption, we'll see a get tasking message like the following: ``` ODIyYmZmMWItYmRhMC00YmNlLWE0ZDMtYTZiZGIxMWI4YTVm3F56rkDEESX1GBAOQy3yaGiiAQABGkGxY66lNP7JS1rie8e7KbFHXwICOj67vvXpo5cik/9LYBqfQ8Ce5E3eUF1mExFX3EOzgAJd6Ey4fR93LoUTeMQQQZ3+ZMCnphaaDVbvJXCuWgoTMr/wO17H1k4zoAaMi+PHk0BXaaNyHMc= ``` Notice how the outer UUID is different, but the encryption key is still the same. #### AES256 Encryption Details * Padding: PKCS7, block size of 16 * Mode: CBC * IV is 16 random bytes * Final message: IV + Ciphertext + HMAC * where HMAC is SHA256 with the same AES key over (IV + Ciphertext) ## Encrypted Key Exchange Checkins There are two currently supported options for doing an encrypted key exchange in Mythic: * Client-side generated RSA keys * leveraged by the apfell-jxa and poseidon agents * Agent specific custom EKE ### EKE by generating client-side RSA keys The agent starts running and generates a new 4096 bit Pub/Priv RSA key pair in memory. The agent then sends the following message to Mythic: ```json theme={"system"} Base64( PayloadUUID + AES256( JSON({ "action": "staging_rsa", "pub_key": "base64 of public RSA key", "session_id": "20char string", // unique session ID for this callback }) ) ) ``` where the AES key initially used is defined as the initial encryption value when generating the payload. When creating payloads, you can generate encryption keys *per c2 profile*. To do so, the C2 Profile will have a parameter that has an attribute called `crypto_type=True`. This will then signal to Mythic to either generate a new per-payload AES256\_HMAC key or (if your agent is using a translation container) tell your agent's translation container to generate a new key. In the `http` profile for example, this is a `ChooseOne` option between `aes256_hmac` or `none`. When it says "base64 of public RSA key" you can do one of two things: * Base64 encode the entire PEM exported key (including the ---BEGIN and ---END blocks) * Use the already base64 encoded data that's inbetween the ---BEGIN and ---END blocks Here is an example of the first message using encryption key `hfN9Nk29S8LsjrE9ffbT9KONue4uozk+/TVMyrxDvvM=`: ``` ODA4NDRkMTktOWJmYy00N2Y5LWI5YWYtYzZiOTE0NGMwZmRj8g4Anp52+vJpizSe8aymY4zNe2qz6xOMb1P69phayqfka57u2gdDBPzOKlkCEYjWlqIFr4Cpfa0krrXDTiaLLyT/wWKulFO8Z7h+/YqIi/6S8pW4hi+5Ht8543vJvlfuVMnK3YIL9ci/xJvkXoUPUI0Gb2fz2+AILD/+9mJrLx4OuJ/FAlVgSlfC4MOMJSOnOKX0D2Q2zsThJyfxzMs/sY9wUEOuYJMVZG5OZzupb7r7GPwZ0ZyeZrxDukR3r979E+2ZTSYWTDMv58PeyRUtLcaMhqPCZJTyDy4ZNJ04MxHbIQCYXsnlcybHczwMGUYw99/bqd1XVD9GKP5zmj3bP600+PbHg0G0N1qHhSrcagCQAIRka1ybSyYmlYILKYUwgmlVCmIT5ERmlXbJu9xqxzKCzfxYoBWpy6I72goDPpZDoK+LFsCIpQAJoRUA/u0KD61ujJCvr+gs/TRv9UIcd+AzR0r7m/ziawaoh6YdYJfPoJBEWi4eozNSaxrnQBOkCul3cOW/SZbZ/UVP84fThFlFLQdGiajmayoa0aLGDnKSh1l8pyX4Of1fajKX3XbY2bLALeU8Tw99E9daNSKhORqMAlmIrfvAHhDHs1vj3ZXj+rKl5We4JYSNSFOL9JzB5OlctV5bd+IuruFc3fLZVkdivjpGczz9iXh3p7Q3M5Xt6m+ZxUwuGa1otrJV55skF3Lns7p6owDw71weJH0h9JvvgoXOTtf1u9HI0ACBzHxThX+yMhmBBP0wU1Lngl6hF4o/1uwNk96fbAGLg0b9njziGC2OQ0D88kaqZ8jJ7C2XQyf4hetQCZCyYPSgtjMw1Yq1qRM0fHbU5cAkKvQmiJMeByHetctfDcs4SvnY28Tb1SfGCnxzxMJ+IQIbatKcQhwbhpq0iavsuG7NUVIPGBhB/8hw3PkkKDb3gqgoKuOD0y8zRK/+DrVbDT3DmzGrmJAkfFXqahjW/aaSNHmqdxxXoI/3Ft1FGocLYAj9bGclW4nzjarRpvtA8fUwMg/vX1RZqFVN15FTp8qsjzKsL8ld0aWlaGcRulfQr9oIKyC+P0EV3a0rMuBO2q2SuSWefyVRaMWCx0gY2Gtrm+bN3ddb+koyUsNdoI7lTY5HirQ3qG0unq28D6Clm8Cok91kMQEaGZ28pZvFZVs3iaLxiPxhmfj+UAQ95ncziJqGrbAiJgTVAmF0bUHAOSD2HORzVeGHxKgFsqSnJvK5B1NUCDIa1ok3sbGo8yg7tc/63pcUPBGcMRRQg3WBN8msj14fDoXAJg3MGG+qzomagdyRFQieMfFeOm1O8SU/a3U9uFwSqhwo4EsE5sIgKPTwN7OVEFbEzNA5tpr65lBxlzC4y1o9Juo25G5QXhuCuSN2frsu4QMlTnxi8P0HHed17hJjY8kaBG6Pm3h9HH098nxiIStZBWSYWQPSXy4AImrT+3vcovjLXPColbKd3M5wRog5WQ1j05O2NQGZwBFDktWioMqIDGCWECvHWgCvPiLmeeCwsWEncqnmrRCwLpI+DXxUVEX9oJFBZhlnfX2iaWeuDw== ``` This message causes the following response: ```json theme={"system"} Base64( PayloadUUID + AES256( JSON({ "action": "staging_rsa", "uuid": "UUID", // new UUID for the next message "session_key": Base64( RSAPub( new aes session key ) ), "session_id": "same 20 char string back" }) ) ) ``` Here's that sample response from our above sample message: ``` ODA4NDRkMTktOWJmYy00N2Y5LWI5YWYtYzZiOTE0NGMwZmRjN6UJrODGAkQnyC4NyX2XVAzF9U95TS8xKaPdVd1MFVeWWDZE6f81wxuwzZBmZogjLzm+PNznszFvrSSXvRDiBy8ZpXUCirDOLlblL/LXMJ/aD8hzghvhf+q628NR9XX43IY2kNdQ2VMONDWpwwa1YLvrNe6YU2cCRmbE8mjrVhrj4j0t4tg1Kor6IXBhcZDTmFxBTHb19LSegwmjjr6Inmx8jCN0hnR77o5PsE6l4q+S9FPrlajMpsPKfs1fgdse0Qn6Fv/yJ3t6AyRAJJkxjtgRE64aHXm4cbSw73M9/QnCnzgFWVIlhwNKuHYfMo05XatXUOV76DXut9nkdzY288xQqRB7AV0mNkhR5BhuuUtHFJZ2/rgLl8Kp8B/9Izz4F7JZm4fyx4l1t0d0zAwx1lz32f4LUhX4cvhKm2qsICw7q34mcSgNYZVJu7KYgOZPb6D9GNtyTLWsnwK8mJmK+9xtVrtNM2ncdifcXVXhohVUAO+cWJUZyYQ2fu3Jx7zowAoUz/huSJ0SsvTYzifM8A+Ab6V2I2UE46TZwcnVBwsHCrVXLcDlHrzGLEynepq/RG5yNetx3nUka7hgX0sByWxTsiDwb6ks+TA9365GtKD351FTouEjraDbWYL6tOuqy/OtHVRenhuow7xH1vsUN/3bfCeaKCrow0SSxL12hNgDk/dbhQlV90F54EkYjFB7VKWjBlwngaF07akdQTgPhYy/bl94dwjHFzhWUDGWJBagzyQOHJo7UOrtN7qoWvbSRFwACd7yz7ugZmo7X6DVhcvIMFdeBA/nMmRSC4CbxSxvVYJWZwO4SFGYHDXLFmcpdM/MuPSXljMDZa+n4NqvWFpHV7bI0fAqut4oVv9Hd/X4q5gMzJnXhWgL+EwQbR3jSb0fR6iLK5jD3mRB4zIugkFHZFouhHJKKzjkMwCl8oPsZskN5INnFnqNz8+lBKcFd4Qh64CZAzLE5dZ62apb9GAG9DRPyxXqp1miCLKJsSENdPU80HQPQMxl01sehlC67RvFYM/8dc7VldDEP99Sa6l9/sJSfynnCA2lsPc5PsnSiCnnk9n85ZqDXy0daheEUA7DpJDO0pWl1f2U2edNKpXhn1oirsLOOSpaZbN/gFpVirfa0Vt8oe5FB2IHgw4B+K85eUuZsdFcGu+xhlRwE1pi34RspdBDeiWISyALXG0QRvRtviZmkX+gn41NrpmIFhOaDBCE3lrWzJjasHSr3H4kgRFrFy2qCDwtrdmVeh6Tpad8ZQN3DZQE6mtnpDgQLgT9/mRQr1/pyEn4CiRacIOvBu+xoiAhLrOcJoTAQI3pVYZaPDNQLyum9CTFXRmEEXTmRao0+qq/tCjYhF11b7u5BBB73gy+YLs1hT/RKNOFqBuQ/ywz+g0BOYJt1lvKU08FtVOJq6ddXhVtyvxQz7OriA1Giji1SayQNLIgVxUmEjhBkgccdD98YhTAMPBeeVru14cny/87Ohd8toqK9DW7MEYI4RyOYbVgpWSfdYAC6T2VbC/d87mb31vX4oCDOqZWL2nsvlybzWCObDi76hbPzhP2H6xcE81qo9QKGKG+2ZfNIwK/aHhPznO5fQ5Qcyuos/jzYVuwxau4S8vOnu7Wraivf8BoVZT+lLqC35bS3Xfhfz3yWqHcVJNjs9AlsC86HYwUfRPJDSrDFSMla7bhQ8fuJXAXKSxfjCspvaIu/5UQ7zFQl+jOEuCbmKcYhLJThEBXVJhTShYc/Euz4+I7wzmhBmfxueXlerB5Kg7tfQZDp8zsE+nccFpVJ5yTjKv+CgLFyRVNLpK9ISukKIKj3BXwhGjJEyY6A1BAwl6v4JnllLd+GLo4ZSWrBIkkednbImRATrFsHChkTkf4Crhj5Ihrc5objEx6sxC9ss3OvcagSbKZF8t/ojN5R1m7LyIXEInKuZktNwOY0tCRvCIEaObD+CRDLGx5sB6Jy8S+dLxmF3P2e9zs+/RG0qmPyKyuaSbkIPHB5mZh/GDbV+86n24QxpIk+udi0IDE2cgBBJBEhhEFF44+MX2E0DgY/f698RWpSNuZcWsOmpmcsk1vH9L2Mv3meairLxT3EptYLX2Tcg6RQDs+ZdFT5eoe3ld4NpHZgecr/RRy868jSPPNU5lL4DPsJSXNXz6cD1jvgqpLaOQCtq0fOreSgG1dL1F92lAeXkCf9P1UU4BeYST8Ar03/oZb+DlXrpzqJt9jE6zs+79ywV9ZSUwXoVMPaMre8p+anHf82qL6DVUMebzyI9JBEtMqsbEqrXuXgFOVj/GM1wqJjGXHb82BKtichi2QfxeS8vUxfxV+SBfJ7qT3i6jp5OC1na8xu+v6tME0ywlZd/LrOp2Rgqj0A58Jmw6HZ4b4SD4SOT2tyBkhIjyMrZiBvXAPwzesFdrYSA3hfj5VEJCHlr9dKo8q/emOmEb8womZ3qADTwzhKYu0fxGFY3vXqMgrpasHj6uoY6xrtNf1CBDCudq+dHQUclPx2PyRL7qcR+7f0ntbc1xEGgofhLdmFMiBskQSNSYnGZAEzOwdCFwiZlzwjqPltHge ``` The response is encrypted with the same initial AESPSK value as before. However, the `session_key` value is encrypted with the public RSA key that was in the initial message and base64 encoded. The response also includes a new staging UUID for the agent to use. This is *not* the final UUID for the new callback, this is a temporary UUID to indicate that the next message will be encrypted with the new AES key. The next message from the agent to Mythic is as follows: ```json theme={"system"} Base64( tempUUID + AES256( JSON({ "action": "checkin", // required "uuid": "payload uuid", //uuid of the payload - required "ips": ["127.0.0.1"], // internal ip addresses - optional "os": "macOS 10.15", // os version - optional "user": "its-a-feature", // username of current user - optional "host": "spooky.local", // hostname of the computer - optional "pid": 4444, // pid of the current process - optional "architecture": "x64", // platform arch - optional "domain": "test", // domain of the host - optional "integrity_level": 3, // integrity level of the process - optional "external_ip": "8.8.8.8", // external ip if known - optional "encryption_key": "base64 of key", // encryption key - optional "decryption_key": "base64 of key", // decryption key - optional "process_name": "osascript", // name of the current process - optional }) ) ) ``` With our new temp UUID, the agent sends the following: ``` MzAzMzY1M2UtMjlkOC00ZDRjLTkxMDgtNTZjMTUxZjc3OWQ34uGQ1yO25qWInocvzRCTRenTlUB7u1oRScx+09PeZZfUrJtdfiEeMD6Xz/kdKUsZjr9LWhFdFcu/AmHzAqH3LmIuSOxnMexmlGT9ngU7NuMvSdRlYvVcsIPYMLbRptletLttCBIu7LhDbuifYFRNQ21TBDkpVgYTXoUk5+JzzTesGdWAhOwLlWvijpKM4nrPLx0fZagEHH4SycHRUuHlei8T7F1YFPm8RhxbONMAd1ckjDnPm5kdUPx0JwpuP975MV4cuHdez+mR6C/JP4B9yeP9hhnHmSFKq7OghnHQQ39prPQ9WArSJ+N8UJ+XOiACpjYon2Qyf0FqhRDdoojrY4sCRrF3Khw9mry+5j5WlHubsICpfi52X9QQMAGzUNeuUve6jMKLQwSclb+IzJ2KKUHtA0qcsdvqyQ2mvXxicAn0OinnP6Vk7ktqsn35UQi3+uuPP0PWf53Iji25/mRCO9MbEa8WQ7epon4H4Erc1yw+Dvfb61BoasPbzspFFVtcuqRkeUYUiIHkR9uzVmSgUJqk/R4cRFico7nK+Q0E6gL1Qyk4P7yPLm3E98wkvoB1Y108r9tKyAFjfZ13MrZpZCsdt7y335hrymeZpt0V6/+ug3BIY1brcxE74fAnO4H5fUan7kPnvQmf/SsO4B9nHHRR/2pC1KYZF+vGw1My6alFPyyGZpzBnrsqyouFqhqOjS3iv8Yv3JY/IxpgJ/T4tXEs1MvgI19lufsBqX1PK9PiB04Y8+Igld6+6RTAwF+vf4utJTp4I/eeH8b0KZ9ABWzvjrPwj1nf1hN2Q0FU4YYBoXzKZ5kE8yZvYtfJpSqDGbsGW2gFr0nC2DybQ2QweLQ1RJAcBlU462jwP4h1ohuHRL2cynGqKaJa7XeILg5Da0ubMTg5fdEPoXMxaNYHdwbzV04WE7zKCru2T16AoncwWxzwcTqy6bRONdRPNFY72HlXmLgQ3R0a1J8VhlAt+7Z5I7rnz8S67rKL9xD1B4mJhhSeCj4k8y1/AgQ9i7ZIeLrcjsUcOo7Hw5Dl4QOBncuOn74DHWnZHgxUDQtB41GBwbJyeoi6ryHdMjUBEOK34f5msUh+HMDCHkLZi+4oM84K3L5oCQrPh1+b6FH5oXGO8pOXi2wHCAtzfF9LF5MfEAa5Lt6ZJpk9fzqZ6fPbFB0R/X/lKLyp6VaMGk5CBpLvwmyNGZnjSXra1r212sqIqFdGu9sVzMJ5daVLsjFPCg== ``` This checkin data is the same as all the other methods of checking in, the key things here are that the tempUUID is the temp UUID specified in the other message, the inner uuid is the payload UUID, and the AES key used is the negotiated one. It's with this information that Mythic is able to track the new messages as belonging to the same staging sequence and confirm that all of the information was transmitted properly. The final response is as follows: ```json theme={"system"} Base64( tempUUID + AES256( JSON({ "action": "checkin", "id": "UUID", // new UUID for the agent to use "status": "success" }) ) ) ``` With our example, the agent gets back the following: ``` MzAzMzY1M2UtMjlkOC00ZDRjLTkxMDgtNTZjMTUxZjc3OWQ3Xgjq3vE9vduJliEd24jskrB+0gcqLc1ROCegwkvSjrqBLGFhrurNCsQnKIFYZ+YP6AGNjgzIlAXbLAPlsRAa6ge6BLQOsywskyHsE/2+65etgEH9plUzOdEv/nknwfdJKV7n7PHQsQ9w4nsV7j9DkeiuIQ+CnlBBRaPpCGYKo8m8keswNY7DssL1FE1t0DQ5 ``` From here on, the agent messages use the new UUID instead of the payload UUID or temp UUID and continues to use the new negotiated AES key. Lastly, here's an example after that exchange with the new callback UUID doing a get tasking request: ``` NTU4NWI2YzMtMmEzOC00ZGZlLWIwMDItNDI5ZjQ5Mzk4YzIxtpTh3cK5yOJ+RlbVJkeVLSRd8ExZbahaQoXg9AbW5SD+wdueD+tPhtB18kcJqy9s10qfsTx/8gMlcw5emRMVm+w9bnScW0BKARoldBlp+31La3/+HsqEKvYaEK9gGcBlEK7mDVqaJlYxgkwWRNGZs4i3eIHpKCc9Gyyz7dyaQUk= ``` #### AES256 Encryption Details * Padding: PKCS7, block size of 16 * Mode: CBC * IV is 16 random bytes * Final message: IV + Ciphertext + HMAC * where HMAC is SHA256 with the same AES key over (IV + Ciphertext) #### RSA Encryption Details * PKCS1\_OAEP * This is specifically OAEP with SHA1 * 4096Bits in size ### Your Own Custom EKE ``` Base64( payloadUUID + customMessage ) ``` Mythic looks up the information for the payloadUUID and calls your translation container's `translate_from_c2_format` function. That function gets a dictionary of information like the following: ```json theme={"system"} { "enc_key": None or base64 of key if Mythic knows of one, "dec_key": None or base64 of key if Mythic knows of one, "uuid": uuid of the message, "profile": name of the c2 profile, "mythic_encrypts": True or False if Mythic thinks Mythic does the encryption or not, "type": None or a keyword for the type of encryption. currently only option besides None is "AES256" "message": base64 of the message that's currently in c2 specific format } ``` To get the `enc_key`, `dec_key`, and `type`, Mythic uses the payloadUUID to then look up information about the payload. It uses the `profile` associated with the message to look up the C2 Profile parameters and look for any parameter with a `crypto_type` set to `true`. Mythic pulls this information and forwards it all to your `translate_from_c2_format` function. Ok, so that message gets your payloadUUID/crypto information and forwards it to your translation container, but then what? Normally, when the `translate_to_c2_format` function is called, you just translate from your own custom format to the standard JSON dictionary format that Mythic uses. No big deal. However, we're doing EKE here, so we need to do something a little different. Instead of sending back an action of `checkin`, `get_tasking`, `post_response`, etc, we're going to generate an action of `staging_translation`. Mythic is able to do staging and EKE because it can save temporary pieces of information between agent messages. Mythic allows you to do this too if you generate a response like the following: ```json theme={"system"} { "action": "staging_translation", "session_id": "some string session id you want to save", "enc_key": the bytes of an encryption key for the next message, "dec_key": the bytes of a decryption key for the next message, "crypto_type": "what type of crypto you're doing", "next_uuid": "the next UUID that'll be in front of the message", "message": "the raw bytes of the message that'll go back to your agent" } ``` Let's break down these pieces a bit: * `action` - this must be "staging\_translation". This is what indicates to Mythic once the message comes back from the `translate_from_c2_format` function that this message is part of staging. * `session_id` - this is some random character string you generate so that we can differentiate between multiple instances of the same payload trying to go through the EKE process at the same time. * `enc_key` / `dec_key` - this is the raw bytes of the encryption/decryption keys you want for the next message. The next time you get the `translate_from_c2_format` message for this instance of the payload going through staging, THESE are the keys you'll be provided. * `crypto_type` - this is more for you than anything, but gives you insight into what the `enc_key` and `dec_key` are. For example, with the `http` profile and the `staging_rsa`, the crypto type is set to `aes256_hmac` so that I know exactly what it is. If you're handling multiple kinds of encryption or staging, this is a helpful way to make sure you're able to keep track of everything. * `next_uuid` - this is the next UUID that appears in front of your message (instead of the payloadUUID). This is how Mythic will be able to look up this staging information and provide it to you as part of the next `translate_from_c2_format` function call. * `message` - this is the actual raw bytes of the message you want to send back to your agent. This process just repeats as many times as you want until you finally return from `translate_from_c2_format` an actual `checkin` message. What if there's other information you need/want to store though? There are three RPC endpoints you can hit that allow you to store arbitrary data as part of your build process, translation process, or custom c2 process: * `create_agentstorage` - this take a unique\_id string value and the raw bytes data value. The `unique_id` is something that you need to generate, but since you're in control of it, you can make sure it's what you need. This returns a dictionary: * \{"unique\_id": "your unique id", "data": "base64 of the data you supplied"} * `get_agentstorage` - this takes the unique\_id string value and returns a dictionary of the stored item: * \{"unique\_id": "your unique id", "data": "base64 of the data you supplied"} * `delete_agentstorage` - this takes the unique\_id string value and removes the entry from the database # 6. Reverse Port Forward Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/rpfwd How does Reverse Port Forward work within Mythic ## What is Reverse Port Forward? Reverse port forwards provide a way to tunnel incoming connections on one port out to another IP:Port somewhere else. It normally provides a way to expose an internal service to a network that would otherwise not be able to directly access it. ## What do RPFWD messages look like? Agents transmit dictionary messages that look like the following: ```json theme={"system"} { "exit": True, "server_id": 1234567, "data": "", "port": 80, // optional, but if you support multiple rpfwd ports within a single callback, you need this so Mythic knows which rpfwd you're getting data from } ``` These messages contain three components: * `exit` - boolean True or False. This indicates to either Mythic or your Agent that the connection has been terminated from one end and should be closed on the other end (after sending `data`). Because Mythic and 2 HTTP connections sit between the actual tool you're trying to proxy and the agent that makes those requests on your tool's behalf, we need this sort of flag to indicate that a TCP connection has closed on one side. * `server_id` - unsigned int32. This number is how Mythic and the agent can track individual connections. Every new connection will generate a new `server_id` . Unlike SOCKS where Mythic is getting the initial connection, the agent is getting the initial connection in a reverse port forward. In this case, the agent needs to generate this random uint32 value to track connections. * `data` - base64 string. This is the actual bytes that the proxied tool is trying to send. * `port` - an optional uint32 value that specifies the port you're listening on within your agent. If your agent allows for multiple rpfwd commands within a single callback, then you need to specify this `port` so that Mythic knows *which* rpfwd command this data is associated with and can redirect it out to the appropriate remote IP:Port combination. This `port` value is specifically the local port your agent is listening on, *not* the port for the remote connection. In Python translation containers, if `exit` is True, then `data` can be `None` ## How does this fit into Agent Messages? These RPFWD messages are passed around as an array of dictionaries in `get_tasking` and `post_response` messages via a (added if needed) `rpfwd` key: ```json theme={"system"} { "action": "get_tasking", "tasking_size": 1, "rpfwd": [ { "exit": False, "server_id": 2, "data": "base64 string", "port": 80, },{ "exit": True, "server_id": 1, "data": "", "port": 445, } ], "delegates": [] } ``` or in the `post_response` messages: ```json theme={"system"} { "action": "post_response", "responses": [ { "user_output": "blah", "task_id": "uuid here", "completed": true } ], "rpfwd": [ { "exit": False, "server_id": 2, "data": "base64 string", "port": 80, },{ "exit": True, "server_id": 1, "data": "", "port": 80, } ], "delegates": [] } ``` Notice that they're at the same level of "action" in these dictionaries - that's because they're not tied to any specific task, the same goes for delegate messages. This means that if you send a `get_tasking` request OR a `post_response` request, you could get back `rpfwd` data. The same goes for `socks`, `interactive`, and `delegates`. ## How does an agent handle RPFWD? For the most part, the message processing is pretty straight forward: 1. Agent opens port X on the target host where it's running 2. ServerA makes a connection to PortX 3. Agent accepts the connection, generates a new uint32 server\_id, and sends any data received to Mythic via `rpfwd` key. If the agent is tracking multiple ports, then it should also send the port the connection was received on with the message. 4. Mythic looks up the `server_id` (and optionally port) for that Callback if Mythic has seen this server\_id, then it can pass it off to the appropriate thread or channel to continue processing. If we've never seen the server\_id before, then it's likely a new connection that opened up, so we need to handle that appropriately. Mythic makes a new connection out to the RemoteIP:RemotePort specified when starting the `rpfwd` session. Mythic forwards the data along and waits for data back. Any data received is sent back via the `rpfwd` key the next time the agent checks in. 5. For existing connections, the agent looks at if `exit` is True or not. If `exit` is True, then the agent should close its corresponding TCP connection and clean up those resources. If it's not exit, then the agent should base64 decode the `data` field and forward those bytes through the existing TCP connection. 6. The agent should also be streaming data back from its open TCP connections to Mythic in its `get_tasking` and `post_response` messages. That's it really. The hard part is making sure that you don't exhaust all of the system resources by creating too many threads, running into deadlocks, or any number of other potential issues. While not perfect, the poseidon agent have a generally working implementation for Mythic: [https://github.com/MythicAgents/poseidon/blob/master/Payload\_Type/poseidon/poseidon/agent\_code/rpfwd/rpfwd.go](https://github.com/MythicAgents/poseidon/blob/master/Payload_Type/poseidon/poseidon/agent_code/rpfwd/rpfwd.go) # 5. SOCKS Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/socks How does SOCKS work within Mythic ## What is SOCKS? Socks provides a way to negotiate and transmit TCP connections through a proxy ([https://en.wikipedia.org/wiki/SOCKS](https://en.wikipedia.org/wiki/SOCKS)). This allows operators to proxy network tools through the Mythic server and out through supported agents. SOCKS5 allows a lot more options for authentication compared to SOCKS4; however, Mythic currently doesn't leverage the authenticated components, so it's important that if you open up this port on your Mythic server that you lock it down. Opened SOCKS5 ports in Mythic do not leverage additional authentication, so MAKE SURE YOU LOCK DOWN YOUR PORTS. ## What do SOCKS messages look like? Without going into all the details of the SOCKS5 protocol, agents transmit dictionary messages that look like the following: ```json theme={"system"} { "exit": True, "server_id": 1234567, "data": "" } ``` These messages contain three components: * `exit` - boolean True or False. This indicates to either Mythic or your Agent that the connection has been terminated from one end and should be closed on the other end (after sending `data`). Because Mythic and 2 HTTP connections sit between the actual tool you're trying to proxy and the agent that makes those requests on your tool's behalf, we need this sort of flag to indicate that a TCP connection has closed on one side. * `server_id` - uint32. This number is how Mythic and the agent can track individual connections. Every new connection from a proxied tool (like through proxychains) will generate a new `server_id` that Mythic will send with data to the Agent. * `data` - base64 string. This is the actual bytes that the proxied tool is trying to send. In Python translation containers, if `exit` is True, then `data` can be `None` ## How does this fit into Agent Messages? These SOCKS messages are passed around as an array of dictionaries in `get_tasking` and `post_response` messages via a (added if needed) `socks` key: ```json theme={"system"} { "action": "get_tasking", "tasking_size": 1, "socks": [ { "exit": False, "server_id": 2, "data": "base64 string" },{ "exit": True, "server_id": 1, "data": "" } ], "delegates": [] } ``` or in the `post_response` messages: ```json theme={"system"} { "action": "post_response", "responses": [ { "user_output": "blah", "task_id": "uuid here", "completed": true } ], "socks": [ { "exit": False, "server_id": 2, "data": "base64 string" },{ "exit": True, "server_id": 1, "data": "" } ], "delegates": [] } ``` Notice that they're at the same level of "action" in these dictionaries - that's because they're not tied to any specific task, the same goes for delegate messages. This means that if you send a `get_tasking` request OR a `post_response` request, you could get back `socks` data. The same goes for `rpfwd`, `interactive`, and `delegates`. ## How does an agent handle SOCKS? For the most part, the message processing is pretty straight forward: 1. Get a new SOCKS array 2. Get the first element from the list 3. If we know the `server_id`, then we can forward the message off to the appropriate thread or channel to continue processing. If we've never seen the server\_id before, then it's likely a new connection that opened up from an operator starting a new tool through proxychains, so we need to handle that appropriately. 4. For new connections, the first message is always a [SOCKS Request message](https://datatracker.ietf.org/doc/html/rfc1928#section-4) with encoded data for IP:PORT to connect to. This means that SOCKS authenticaion is already done. There's also a very specific message that gets sent back as a response to this. This small negotiation piece isn't something that Mythic created, it's just part of the [SOCKS protocol](https://datatracker.ietf.org/doc/html/rfc1928) to ensure that a tool like proxychains gets confirmation the agent was able to reach the desired IP:PORT 5. For existing connections, the agent looks at if `exit` is True or not. If `exit` is True, then the agent should close its corresponding TCP connection and clean up those resources. If it's not exit, then the agent should base64 decode the `data` field and forward those bytes through the existing TCP connection. 6. The agent should also be streaming data back from its open TCP connections to Mythic in its `get_tasking` and `post_response` messages. That's it really. The hard part is making sure that you don't exhaust all of the system resources by creating too many threads, running into deadlocks, or any number of other potential issues. While not perfect, the poseidon agent have a generally working implementation for Mythic: [https://github.com/MythicAgents/poseidon/blob/master/Payload\_Type/poseidon/poseidon/agent\_code/socks/socks.go](https://github.com/MythicAgents/poseidon/blob/master/Payload_Type/poseidon/poseidon/agent_code/socks/socks.go) [Previous4. Submitting Responses](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/action-post_response)[Next6. Reverse Port Forward](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/rpfwd) # 6. Dynamic Command Parameter Values Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/dynamic-parameter-values Populate command choices from callback context and Mythic RPC Dynamic command parameters let a payload type calculate the choices shown to an operator when a tasking modal opens. Use them when the valid values depend on callback state, another parameter, Mythic data, or an external service. Dynamic queries run before a task exists. They can query Mythic by callback, but cannot use task-scoped RPC calls. ## Define a query Set `dynamic_query_function` on a `ChooseOne` or `ChooseMultiple` command parameter. The function receives a `PTRPCDynamicQueryFunctionMessage` and returns a `PTRPCDynamicQueryFunctionMessageResponse`. ```python theme={"system"} class ShellArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = { "command": CommandParameter( name="command", type=ParameterType.String, description="Command to run", ), "file": CommandParameter( name="file", type=ParameterType.ChooseOne, dynamic_query_function=self.get_executables, ), } async def get_executables( self, input_msg: PTRPCDynamicQueryFunctionMessage ) -> PTRPCDynamicQueryFunctionMessageResponse: file_result = await SendMythicRPCFileSearch( MythicRPCFileSearchMessage( CallbackID=input_msg.Callback, LimitByCallback=False, Filename="", ) ) if not file_result.Success: return PTRPCDynamicQueryFunctionMessageResponse( Success=False, Error=file_result.Error, ) requested_suffix = input_msg.OtherParameters.get("suffix", ".exe") choices = sorted( { entry.Filename for entry in file_result.Files if entry.Filename.endswith(requested_suffix) } ) return PTRPCDynamicQueryFunctionMessageResponse( Success=True, Choices=choices, ) async def parse_arguments(self): if not self.command_line: raise ValueError("Missing arguments") if self.command_line[0] == "{": self.load_args_from_json_string(self.command_line) else: self.add_arg("command", self.command_line) ``` `OtherParameters` contains the current values of the other fields in the modal. This lets one dynamic choice depend on an earlier operator selection. ## Request fields The request contains: | Python field | JSON field | Purpose | | -------------------- | ---------------------- | --------------------------------------------------------------------- | | `Command` | `command` | Command whose modal is open | | `ParameterName` | `parameter_name` | Parameter requesting choices | | `PayloadType` | `payload_type` | Payload type of the callback | | `CommandPayloadType` | `command_payload_type` | Payload type that owns the command, including augmentation containers | | `Callback` | `callback` | Internal callback ID for RPC calls | | `CallbackDisplayID` | `callback_display_id` | Operation-scoped callback ID shown in the UI | | `AgentCallbackID` | `agent_callback_id` | Callback UUID known to the agent | | `PayloadOS` | `payload_os` | OS selected for the backing payload | | `PayloadUUID` | `payload_uuid` | UUID of the backing payload | | `Secrets` | `secrets` | Secrets available to the requesting operator | | `OtherParameters` | `other_parameters` | Current values from the rest of the modal | Return `Success=True` and a `Choices` list. Return `Success=False` with `Error` when the choices could not be generated. An empty successful list means there are currently no valid choices. ## Related dynamic parameters Mythic 4.0 also supports dynamic queries for payload build parameters and C2 profile parameters. Those definitions can return complex choices with separate stored and display values. See [Dynamic Build Parameters](/version-4.0/customizing/payload-type-development/payload-type-info/dynamic-build-parameters) and [C2 Profile Parameters](/version-4.0/customizing/c2-related-development/mythic-definition/2.1.2-c2-parameters). # 8. OPSEC Checking Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/opsec-checking ## What is OPSEC Checking It's often useful to perform some operational security checks before issuing a task based on everything you know so far, or after you've generated new artifacts for a task but before an agent picks it up. This allows us to be more granular and context aware instead of the blanket command blocking that's available from the Operation Management page in Mythic. ## Where is OPSEC Checking? OPSEC checks and information for a command is located in the same file where everything else for the command is located. Let's take an example all the way through: ```python theme={"system"} class ShellCommand(CommandBase): cmd = "shell" needs_admin = False help_cmd = "shell {command}" description = """This runs {command} in a terminal by leveraging JXA's Application.doShellScript({command}). WARNING! THIS IS SINGLE THREADED, IF YOUR COMMAND HANGS, THE AGENT HANGS!""" version = 1 author = "@its_a_feature_" attackmapping = ["T1059", "T1059.004"] argument_class = ShellArguments attributes = CommandAttributes( suggested_command=True ) async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse: response = PTTTaskOPSECPreTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPreBlocked=True, OpsecPreBypassRole="other_operator", OpsecPreMessage="Implemented, but not blocking, you're welcome!", ) return response async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse: response = PTTTaskOPSECPostTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPostBlocked=True, OpsecPostBypassRole="other_operator", OpsecPostMessage="Implemented, but not blocking, you're welcome! Part 2", ) return response async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) await SendMythicRPCArtifactCreate(MythicRPCArtifactCreateMessage( TaskID=taskData.Task.ID, ArtifactMessage="{}".format(taskData.args.get_arg("command")), BaseArtifactType="Process Create" )) response.DisplayParams = taskData.args.get_arg("command") return response ``` ### opsec\_pre / opsec\_post In the case of doing operational checks before a task's `create_tasking` is called, we have the `opsec_pre` function. Similarly, the `opsec_post` function happens after your `create_tasking`, but before your task is finally ready for an agent to pick it up. * `opsec_pre/post_blocked` - this indicates True/False for if the function decides the task should be blocked or not * `opsec_pre/post_message` - this is the message to the operator about the result of doing this OPSEC check * `opsec_pre/post_bypass_role` - this determines who should be able to bypass this check. The default is `operator` to allow any operator to bypass it, but you can change it to `lead` to indicate that only the lead of the operation should be able to bypass it. You can also set this to `other_operator` to indicate that somebody *other* than the operator that issued the task must approve it. This is helpful in cases where it's not necessarily a "block", but something you want to make sure operators acknowledge as a potential security risk As the name of the functions imply, the `opsec_pre` check happens before `create_tasking` function runs and the `opsec_post` check happens after the `create_tasking` function runs. If you set `opsec_pre_blocked` to True, then the `create_tasking` function isn't executed until an approved operator bypasses the check. Then, execution goes back to `create_tasking` and the `opsec_post`. If that one also sets blocked to True, then it's again blocked at the user to bypass it. At this point, if it's bypassed, the task status simply switched to `Submitted` so that an agent can pick up the task on next checkin. ## OPSEC Scripting From the `opsec_pre` and `opsec_post` functions, you have access to the entire task/callback information like you do in [Create\_Tasking](/version-4.0/customizing/payload-type-development/create_tasking#available-context). Additionally, you have access to the entire RPC suite just like in [Create\_Tasking](/version-4.0/customizing/payload-type-development/create_tasking#rpc-functionality). # 10. Process Response Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/process-response ## What is Process Response? Within a `Command` class, there are two functions - `create_go_tasking` and `process_response`. As the names suggest, the first one allows you to create and manipulate a task before an agent pulls it down, and the second one allows you to process the response that comes back. If you've been following along in development, then you know that Mythic supports many different fields in its `post_response` action so that you can automatically create artifacts, register keylogs, manipulate callback information, etc. However, all of that requires that your agent format things in a specific way for Mythic to pick them up and process. That can be tiring. ### Enter \`process\_response\` The `process_response` function takes in one argument class that contains two pieces of information: The `task` that generated the response in the first place, and the `response` that was sent back from the agent. Now, there's a specific `process_response` keyword you have to send for mythic to shuttle data off to this function instead of processing it normally. When looking at a `post_response` message, it's structured as follows: ```json theme={"system"} { "action": "post_response", "responses": [ { "task_id": "some uuid", "process_response": {"myown": "data format"}, // all of the other fields you want to leverage } ] } ``` Now, anything in that `process_response` key will get sent to the `process_response` function in your Payload Type container. This value for `process_response` can be any type - int, string, dictionary, array, etc. Some caveats: * You can send any data you want in this way and process it however you want. In the end, you'll be doing RPC calls to Mythic to register the data * Not all things make sense to go this route. Because this is an async process to send data to the container for processing, this happens asynchronously and in parallel to the rest of the processing for the message. For example, if your message has *just* the `task_id` and a `process_container` key, then as soon as the data is shipped off to your `process_response` function, Mythic is going to send the all clear back down to the agent and say everything was successful. It doesn't wait for your function to finish processing anything, nor does it expect any output from your function. * We do this sort of separation because your agent shouldn't be waiting on the hook for unnecessary things. We want the agent to get what it wants as soon as possible so it can go back to doing agent things. * Some functionality like SOCKS and file upload/download don't make sense for the `process_response` functionality because the agent needs the response in order to keep functioning. Compare this to something like registering a keylog, creating an artifact, or providing some output to the user which the agent tends to think of in a "fire and forget" style. These sorts of things are fine for async parallel processing with no response to the agent. The function itself is really simple: ```python theme={"system"} async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) return resp ``` where `task` is the same task data you'd get from your `create_go_tasking` function, and `response` is whatever you sent back. You have full access to all of the RPC methods to Mythic from here just like you do from the other functions. # 13. Reverse PortFwd Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/reverse-portfwd See the [C2 Related Development section](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/rpfwd) for more RPFWD specific message details. To start / stop RPFWD (or any interactive based protocol), use the `SendMythicRPCProxyStart` and `SendMythicRPCProxyStop` RPC calls within your Payload Type's tasking functions. For RPFWD, you want to set `LocalPort` to the port you want to open up on the host where your agent is running. `RemoteIP` and `RemotePort` are used for Mythic to make remote connections based on the incoming connections your agent gets on `LocalPort` within the target network. The `PortType` will be `CALLBACK_PORT_TYPE_RPORTFWD` (i.e. `rpfwd`). In general, your agent will open up `LocalPort` on your target machine. When it gets a connection to that port, it'll generate a random uint32 to identify that connection, and forward along what it gets from the connection to Mythic along with that identifier. Mythic will get that information and open a new connection to `RemoteIP:RemotePort` and forward that data along. At that point, both sides are just forwarding data back and forth. Eventually, one end of the connection will terminate and at that point a final message will get sent to tell the other side to also close. # 12. SOCKS Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/socks See the [C2 Related Development section](/version-4.0/customizing/payload-type-development/create_tasking/agent-side-coding/socks) for more SOCKS specific message details. To start / stop SOCKS (or any interactive based protocol), use the `SendMythicRPCProxyStart` and `SendMythicRPCProxyStop` RPC calls within your Payload Type's tasking functions. For SOCKS, you want to set `LocalPort` to the port you want to open up on the Mythic Server - this is where you'll point your proxy-aware tooling (like proxychains) to then tunnel those requests through your C2 channel and out your agent. For SOCKS, the RemotePort and RemoteIP don't matter. The `PortType` will be `CALLBACK_PORT_TYPE_SOCKS` (i.e. `socks`). # 7. Sub-tasking / Task Callbacks Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/sub-tasking-task-callbacks ## What is sub-tasking? Sub-tasking is the ability for a task to spin off sub-tasks and wait for them to finish before potentially entering a "submitted" state themselves for an agent to pick them up. When creating subtasks, your `create_go_tasking` function will finish completing like normal (it doesn't wait for subtasks to finish). When a task has outstanding subtasks, its status will change to "delegating" while it waits for them all to finish. ### Why do subtasks? Subtasking provides a way to separate out complex logic into multiple discrete steps. For example, if a specific task you're trying to do ends up with a complex series of steps, then it might be more beneficial for the agent developer and operator to see them broken out. For example, a `psexec` command actually involves a lot of moving pieces from making sure that you: * have a service executable (or some sort-lived task that is ok to get killed by the service control manager) * can access the remote file system * can write to the remote file system in some way (typically smb) * can create a scheduled task * can delete the scheduled task * can remove the remote file That's a lot of steps and conditionals to report back. If any step fails, are you able to track down *where* it failed and the status of any of the cleanup steps (if any performed at all)? That starts to become a massive task, especially when other parts of the task might already be separate tasks within the agent. Creating/manipulating scheduled tasks could be its own command, same with copying files to a remote share. So, at that point you're either duplicating code, or you have some sort of shared dependency. It would be easier if you could just issue these all as subtasks and let each one handle its job as needed in smaller, isolated chunks. ### How to create subtasks? Creating subtasks are pretty easy: ```python theme={"system"} async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: response = PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True, ) await SendMythicRPCTaskCreateSubtask(MythicRPCTaskCreateSubtaskMessage( TaskID=taskData.Task.ID, CommandName="run", Params="cmd.exe /S /c {}".format(taskData.args.command_line) )) return response ``` This function called be called from within your `create_go_tasking` or even task callbacks (in the next section). We're specifying the name of the command to run along with the parameters to issue (as a string). We can even specify a `SubtaskCallbackFunction` to get called within our current task when the subtask finishes. It's a way for the parent task to say "when this subtask is done, call this function so I can make more decisions based on what happened". These callback functions look like this: ```python theme={"system"} async def downloads_complete(completionMsg: PTTaskCompletionFunctionMessage) -> PTTaskCompletionFunctionMessageResponse: response = PTTaskCompletionFunctionMessageResponse(Success=True) ... response.Success = False response.TaskStatus = "error: Failed to search for files" await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage( TaskID=completionMsg.TaskData.Task.ID, Response=f"error: Failed to search for files: {files.Error}".encode() )) return response ``` Notice how this function's parameters don't start with `self`. This isn't a function in your command class, but rather a function *outside* of it. With the data passed in via the `PTTaskCompletionFunctionMessage` you should still have all you need to do MythicRPC\* calls though. This `PTTaskCompletionFunctionMessage` has all the normal information you'd expect for the parent task (just like you'd see in your `create_go_tasking` function) as well as all the same information for your `subtask`. This makes it easy to manipulate both tasks from this context. Note: If you want to modify the information about your current task, some fields can be updated via the `PTTaskCompletionFunctionMessageResponse`, but otherwise you'll need to use the `SendMythicRPCTaskUpdate` function call to update other attributes about your parent task (or anything about your subtask). These callback functions are called in the *parent* task that spawned the subtask in the first place. If you're creating subtasks and you want tokens associated with them (such as matching the token supplied for the parent task), then you **must** manually supply it as part of creating your subtask (ex: `Token=taskData.Task.TokenID`). Mythic doesn't assume subtasks also need the token applied. ### What does the flow look like? Here we have the flow for a command, `shell`, that issues a subtask called `run` and registers two completion handlers - one for when `run` completes and another for when `shell` completes. Notice how execution of `shell`'s create tasking function continues even after it issues the subtask `run`. That's because this is all asynchronous - the result you get back from issuing a subtask is only an indicator of if Mythic successfully registered the task to not, not the final execution of the task. ## What are task callbacks? Task callbacks are functions that get executed when a task enters a "completed=True" state (i.e. when it completes successfully or encounters an error). These can be registered on a task itself ```python theme={"system"} async def create_go_tasking(self, taskData: MythicCommandBase.PTTaskMessageAllData) -> MythicCommandBase.PTTaskCreateTaskingMessageResponse: response = MythicCommandBase.PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, CompletionFunctionName="formulate_output", Success=True, ) return response ``` or on a subtask: When Mythic calls these callbacks, it looks for the defined name in the command's `completed_functions` attribute like: ``` completion_functions = {"formulate_output": formulate_output} ``` Where the `key` is the same name of the function specified and the `value` is the actual reference to the function to call. ## Where are they? Like everything else associated with a Command, all of this information is stored in your command's Python/GoLang file. Sub-tasks are created via RPC functions from within your command's `create_tasking` function (or any other function - i.e. you can issue more sub-tasks from within task callback functions). Let's look at what a callback function looks like: ```python theme={"system"} async def formulate_output( task: PTTaskCompletionFunctionMessage) -> PTTaskCompletionFunctionMessageResponse: # Check if the task is complete response = PTTaskCompletionFunctionMessageResponse(Success=True, TaskStatus="success") if task.TaskData.Task.Completed is True: # Check if the task was a success if not task.TaskData.Task.Status.includes("error"): # Get the interval and jitter from the task information interval = task.TaskData.args.get_arg("interval") jitter = task.TaskData.args.get_arg("interval") # Format the output message output = "Set sleep interval to {} seconds with a jitter of {}%.".format( interval / 1000, jitter ) else: output = "Failed to execute sleep" # Send the output to Mythic resp = await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage( TaskID=taskData.Task.ID, Response=output.encode() )) if not resp.Success: raise Exception("Failed to execute MythicRPC function.") return response ``` ## Task Callbacks This is useful for when you want to do some post-task processing, actions, analysis, etc when a task completes or errors out. In the above example, the `formulate_output` function simply just displays a message to the user that the task is done. In more interesting examples though, you could use the `get_responses` RPC call like we saw above to get information about all of the output subtasks have sent to the user for follow-on processing. # 9. Translation Containers Source: https://docs.mythic-c2.net/version-4.0/customizing/payload-type-development/translation-containers ### Translation Containers 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. Mythic will base64 decode the message it gets, pull out the payload/staging/callback UUID in front, and look up information on it. When Mythic determines the backing payload type has a translation container, Mythic will send the UUID, encryption information, and encrypted blob to your translation container. When Mythic is done processing the message from your agent and it's time to send a response back, it'll send the message to your translation container and then forward the response back to the C2 Profile. **NOTE:** If your translation container is doing the encryption, then Mythic will expect that the message coming back from your translation container (mythic c2 to custom) will be encrypted, have the UUID attached, and be base64 encoded. Mythic will do **NOTHING** with the message it gets back. If Mythic is handling the encryption, then you will simply return the custom c2 bytes of your message and **MYTHIC** will be the one to add the UUID and base64 encode the response. ### 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: ```python theme={"system"} mythic_container.mythic_service.start_and_run_forever() ``` ### Go For the Go 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. Then, in our `main.go` code, we call the Initialize function and start the services: ```go theme={"system"} mytranslatorfunctions.Initialize() // sync over definitions and listen MythicContainer.StartAndRunForever([]MythicContainer.MythicServices{ MythicContainer.MythicServiceTranslationContainer, }) ``` ### Examples: These examples can be found at the MythicMeta organization on GitHub: [https://github.com/MythicMeta/ExampleContainers/tree/main/Payload\_Type](https://github.com/MythicMeta/ExampleContainers/tree/main/Payload_Type) 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` ## Turning a VM into a Translation Container 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 [#turning-a-vm-into-a-mythic-container](/version-4.0/customizing/payload-type-development#turning-a-vm-into-a-mythic-container) # MythicRPC Source: https://docs.mythic-c2.net/version-4.0/mythicrpc/mythicrpc ## What is MythicRPC MythicRPC lets container code execute scoped Mythic operations over RabbitMQ from tasking, response processing, payload builds, C2 functions, eventing functions, and other supported hooks. ## Where is MythicRPC MythicRPC lives as part of the `mythic_container` PyPi package (and `github.com/MythicMeta/MythicContainer` Go package) that's included in all of the `itsafeaturemythic` Docker images. This PyPi package uses RabbitMQ's RPC functionality to execute functions that exist within Mythic. The full list of commands can be found here: [https://github.com/MythicMeta/MythicContainerPyPi/tree/main/mythic\_container/MythicGoRPC](https://github.com/MythicMeta/MythicContainerPyPi/tree/main/mythic_container/MythicGoRPC) for Python and [https://github.com/MythicMeta/MythicContainer/tree/main/mythicrpc](https://github.com/MythicMeta/MythicContainer/tree/main/mythicrpc) for Go. ## MythicRPC Calls Your execution comes from a container rather than directly from the browser. In Mythic 4.0, the server and supported container libraries propagate a signed authenticated context through RabbitMQ and enforce scopes on RPC routes. The identifiers below still associate a request with the correct task, callback, payload, or operation, but they no longer replace authentication context. Update every container to a v4-compatible MythicContainer library. Hand-written RabbitMQ code must preserve the supplied auth-context header when forwarding a request or response. See [Container RPC Authentication](/version-4.0/message-flow/container-rpc-authentication). ### Types of IDs There are a few different kinds of IDs that get used throughout these MythicRPC calls and even in Mythic's Scripting. So let's take a moment to clarify them. * Agent\* - if the ID starts with "agent", then it's something from the agent's perspective. So an `agent_task_id` is the UUID that an agent gets that's associated with a task. A `agent_callback_id` is the UUID that an agent gets that's associated with their callback. * Display - Display IDs are what's shown to the user to make it easier to see what's going on and to have some consistency in each operation. The first task of every operation will have a DisplayID of 1. The first callback in every operation will have a DisplayID of 1. This is different from the actual ID of the task/callback which is a monotonically increasing number representing the row in the database that corresponds to the data. ### MythicRPC Naming Convention You'll notice as you look through the MythicRPC functions that there's a specific pattern: SendMythicRPC\[thing]\[action]. Take some of the following examples: * SendMythicRPCFileSearch - searching metadata about files (uploads, downloads, payloads, screenshots, etc) * SendMythicRPCFileGetContent - for a specific file, fetch the actual *contents* of the file. * SendMythicRPCProxyStart - ask Mythic to start proxying traffic (socks / rpfwds) * SendMythicRPCCallbackAddCommand - add a command to a specific callback so that it'll be available in the UI for tasking This makes it pretty easy to start looking for what you're trying to do. Every function takes one input and returns one output with similar names. For example: the `SendMythicRPCFileSearch` function takes in one parameter as `MythicRPCFileSearchMessage` and returns `MythicRPCFileSearchMessageResponse`. ## MythicRPC Functions This section will go through the different MythicRPC functions that are available and highlight which pieces are required or any potentially confusing pieces. Please refer to the links above to the respective Go and PyPi libraries for the exact code and typehints you'll be leveraging for your implementations. #### SendMythicRPCAgentStorageCreate This function allows you to add any arbitrary byte data to the database in the `AgentStorage` table by some unique string you specify. Both `UniqueID` (just a string, doesn't have to be a UUID or anything) and `DataToStore` (bytes) are required. #### SendMythicRPCAgentStorageRemove This function allows you to remove any arbitrary data from the `AgentStorage` table by looking up the data via the unique string provided when the SendMythicRPCAgentStorageCreate function was called. This function just takes in one required parameter, `UniqueID`, a string. #### SendMythicRPCAgentStorageSearch This function allows you to lookup any data from the `AgentStorage` table via the `SearchUniqueID` (string) field. #### SendMythicRPCAPITokenCreate This function creates a temporary opaque API token for Mythic scripting. Supply a context field such as `AgentTaskID`, `AgentCallbackID`, `PayloadUUID`, `ChatChannelID`, or `OperationID`, and pass the minimum resource `Scopes` required by the caller. The token uses `Authorization: Bearer ` and is invalidated with its associated context or short lifetime. `AgentTaskID` is a string, so it's the UUID value that the agent would know for a task. This is available as part of your `create_go_tasking` function. `AgentCallbackID` is a string, so it's the UUID value that the agent would know for its own callback. This is also available as part of your `create_go_tasking`, but this might also be the only piece of information you have if you're trying to do something like this from within a C2 Profile container. `PayloadUUID` is a string, so it's the `self.uuid` value you'd have access to during a payload's build. `OperationID` is an integer, so it's the actual operation id value from the database. Do not return a broad wildcard token to an agent when a dedicated file capability or narrower RPC can perform the same action. #### SendMythicRPCArtifactCreate This function allows you to create an artifact within Mythic and associate it with a specific task. The `TaskID` (int), `ArtifactMessage` (the string value of the actual artifact you're tracking), and the `BaseArtifactType` are all required. The `BaseArtifactType` can be whatever you want, but it's the more general piece like `ProcessCreate`. #### SendMythicRPCArtifactSearch This function allows you to search for already created artifacts. You must supply the `TaskID` so that the query can be scoped, and then you must supply `SearchArtifacts` which is an object of type `MythicRPCArtifactSearchArtifactData` containing what it is you're searching for. If you want to just search for all artifacts on a specific host, you can just supply the host value. If you want all artifacts from a specific task, supply the `task_id` value here. #### SendMythicRPCCallbackAddCommand This function allows you to track existing commands for a given payload type as being available within a specific callback. This does *NOT* create new commands. The `TaskID` or `AgentCallbackID` field is required so that the operation can be appropriately scoped. The `CallbackIDs` array (ints) says which callbacks you're trying to associate the `Commands` (string array). Say Callback 5 is an Apollo-based callback and you want to register the `whoami` command. If you don't specify the `PayloadType`, then Mythic will assume `whoami` is an Apollo command you're trying to register. However, if you have a `whoami` command from some other payload type that you're trying to make available within this callback, then you need to specify the `PayloadType` (string) here so that Mythic can look up the appropriate command. #### SendMythicRPCCallbackCommandSearch This function allows you to search for commands that are available within a specific callback. You must supply either `TaskID` (int) or `CallbackID` (int) so that the query can be scoped appropriately. The `SearchCommandNames`, `SearchSupportedUIFeatures`, `SearchScriptOnly`, and `SearchAttributes` are simply ways to limit your search within the callback for specific commands. #### SendMythicRPCCallbackCreate This function allows you to create a new callback in Mythic's UI. This can be particularly helpful for 3rd Party Service agents (like `bloodhound` or `ghostwriter`) that don't have an actual payload to execute, but are using the Mythic interface to provide a way to interact with these services. The `PayloadUUID` (string) and `C2ProfileName` fields are required, everything else is just a way to make the callback look like you want in the UI. #### SendMythicRPCCallbackDecryptBytes This function allows you to ask Mythic to decrypt a message from a specified callback. To do this, Mythic needs a few pieces of information that it would normally automatically get: * `AgentCallbackID` - this is the string UUID of the callback and how Mythic looks up a lot of crypto information * `Message` - the actual bytes of the message you want decrypted * `IncludesUUID` - indicating if `Message` starts with the `AgentCallbackID` or not * `IsBase64Encoded` - indicates if `Message` is a base64 blob or not * `C2Profile` - the name of the C2 profile that this message would have come from, this helps Mythic look up the proper crypto pieces if needed #### SendMythicRPCCallbackDisplayToRealIDSearch This function allows you to take a callback's Display ID and an associated OperationID or OperationName and look up the actual Callback ID. Remember, DisplayIDs are not unique and increase from 1 for each operation. This is why in order to get a callback's actual ID from a DisplayID (what you'd see in the UI looking at the active callbacks table), you need one other piece of information to find the associated operation. #### SendMythicRPCCallbackEdgeSearch This function allows you to look for "edges" that exist (or existed at one point) for a specific callback. You need to supply either `AgentCallbackID` (string uuid) or `CallbackID` (int) to do this search and then you can filter by `SearchC2ProfileName` or `SearchActiveEdgesOnly`. #### SendMythicRPCCallbackEdgeRemove This functions allows you to mark callback graph edges as done. You need to specify the source and destination CallbackID values as well as the name of the C2 Profile. This data can be easily gathered via the Search command above. #### SendMythicRPCCallbackEncryptBytes This function allows you to do the opposite of `SendMythicRPCCallbackDecryptBytes`. To encrypt a message, you need to supply a few pieces of information to help scope things properly: * `AgentCallbackID` - the string UUID that the agent callback knows itself by * `Message` - the bytes of the message you want to encrypt * `IncludeUUID` - indicate if the resulting encrypted blob should include the UUID in front or not * `Base64ReturnMessage` - indicate if you want the resulting message to be base64 encoded or not * `C2Profile` - the name of the c2 profile that this comms would have gone through #### SendMythicRPCCallbackNextCheckinRange This function allows you to more generically supply the last checkin time and the current sleep and jitter values. This function will give you the Min and Max times between which the callback should check in. #### SendMythicRPCCallbackRemoveCommand This function is the opposite of the `SendMythicRPCCallbackAddCommand` function and is subject to the same restrictions and requirements. #### SendMythicRPCCallbackSearch This function allows you to search for callbacks. You must supply either `AgentCallbackID` (string) or `CallbackID` (int) to scope the query down to the right operation. All of the other fields that start with `Search*` are ways that you can optionally filter down your search results. #### SendMythicRPCCallbackUpdate This function allows you to update aspects of a callback. You must supply one of the following: * `TaskID` - this scopes your operation and the callback to update is the one to which this task belongs * `CallbackID` (int) - this scopes your operation and updates this callback * `AgentCallbackID` (string) - this scopes your operation and updates this callback All of the other fields are things that you can update about the callback itself. The `UpdateLastCheckinTime` allows you to tell Mythic that it should not just update some attribute about the callback, but also pretend that the callback just checked in and update the last checkin time. If it's known and you're doing `UpdateLastCheckinTime`, you should also supply `UpdateLastCheckinTimeViaC2Profile` (string) with the name of the C2 profile that was "used". This helps mythic keep track of a few other things more precisely internally. #### SendMythicRPCCallbackTokenCreate This function allows you to associate a tracked "token" with a callback. The `TaskID` is required, as usual. The `CallbackTokens` is an array of objects that specify which tokens you want to associate. In this `MythicRPCCallbackTokenCreateCallbackTokenData` object, `action` will be `add`, and `TokenId` is going to be how you identify which token to add. This `TokenId` is provided by the agent when you add the token to be tracked originally. #### SendMythicRPCCallbackTokenRemove This function is the opposite of the `SendMythicRPCCallbackTokenCreate` function and all you need to do differently is switch the `action` to `remove` instead of `add`. #### SendMythicRPCCallbackTokenSearch This function takes in a CallbackID that you're interested in and returns data about all of the associated callback tokens. You can then feed this into the `SendMythicRPCCallbackTokenRemove` command if needed. #### SendMythicRPCCommandSearch This function allows you to search for commands in general across payload types (not ones loaded into callbacks or baked into payloads). All of the fields are optional, but you can scope your query down to a certain `SearchPayloadTypeName`, certain command names `SearchCommandNames`, and other ways. #### SendMythicRPCCredentialCreate This function allows you to register credentials within Mythic's credential store. THe `TaskID` is required so that the data can be appropriately scoped. the `Credentials` array, of type `MythicRPCCredentialCreateCredentialData`, defines the various credentials you want to add. You must provide the `Credential`, but everything else is optional. If you don't provide a `CredentialType`, it defaults to `plaintext`. The `CredentialType` options are: plaintext, certificate, hash, key, ticket, cookie, and hex. #### SendMythicRPCCredentialSearch This function allows you to search for specific credentials within Mythic's credential store. The `TaskID` is required so that the query can be scoped to the right operation. After that, the `SearchCredentials` object allows you to specify what kinds of data you're looking for. Mythic 4.0 credential records can also carry subtype, structured identity, metadata, custom display, validity, and update state. Use the current library's credential structures instead of assuming the v3 type/account/realm/value tuple is exhaustive. #### SendMythicRPCCustomBrowserSearch This command allows you to search for data in CustomBrowsers. You must supply a `TreeType` which is the kind of custom browser along with a, `OperationID`, `Host`, and a `CallbackGroup` which help scope your search down to the right data. #### SendMythicRPCFileCreate This function allows you to register bytes to a file within Mythic and track metadata about it. This is needed if you want to then chunk up and transfer a file down to an agent. The `TaskID` (int), `PayloadUUID` (string), or `AgentCallbackID` (string) values are required to appropriately scope the operation where this file will be created. Only one of them is needed. If you have none of those, `OperationID` (int) and `OperatorID` (int) can be used instead - these two things are supplied for the custom browser export functionality for this reason. `FileContents` is the actual bytes that you want saved to disk. `DeleteAfterFetch` allows you to specify if the file should be deleted from Mythic's disk after the agent successfully fetches it (this can be useful so that you don't clutter the disk or the UI with temporary files). The `Filename` is self explanatory, along with `IsScreenshot` and `IsDownloadFromAgent`. These pieces allow you to provide some extra context about the file. If this file is going to be hosted on a remote target somewhere, you can optionally specify `TargetHostName` and `RemotePathOnTarget` so that Mythic's UI can properly track it. #### SendMythicRPCFileGetContent This function allows you to get the actual file contents for a specified file. The `SendMythicRPCFileGetContentMessage` takes in one parameter, `AgentFileID` (string) which is the UUID of the file. If you upload a file as part of tasking, say for a parameter called `assembly`, then in your `create_go_tasking`, the `assembly` value will be this UUID which you can pass right into the `AgentFileID` field. #### SendMythicRPCDirectFileTokenCreate This v4 call creates a short-lived capability for one `AgentFileID` and one action such as `download`. Use it when a service needs direct access to a specific file without minting a general-purpose API token. Treat the returned token as a secret and do not reuse it for another file or action. #### SendMythicRPCFileSearch This function allows you to search for files that haven't been deleted within Mythic's tracking. The `TaskID` or `CallbackID` or `OperationId` is needed to scope the search unless you're specifically providing the `AgentFileID` (UUID that's unique enough). You can filter what you want to search by and if you want to limit your search to files associated with the current callback or more generally for this operation. You can also limit how many you want and if you want to include Payloads, Downloads (normally just uploads), and screenshots. #### SendMythicRPCFileUpdate This function allows you to update information about the file specified by `AgentFileID` (string). This `AgentFileID` is the UUID value of the string. You can adjust the comment on the file, change the filename, append or replace the file contents, and even mark it for deletion. If you set `DeleteAfterFetch` to `true`, then once any agent fetches this file from the Mythic server, Mythic will then delete the file off disk and mark it as deleted. This can be helpful for temporary stubs you generate that you want to be removed after the agent successfully fetches it. #### SendMythicRPCFileBrowserCreate This function allows you to register "files" within the File Browser. There's no actual file content with these, instead it's more metadata specific. The `TaskID` parameter is required because it allows Mythic to scope this data to the right operation and associate it with coming from a specific task. The content of the `FileBrowser` class is the same as when an agent hooks this functionality [from the agent side](/version-4.0/customizing/hooking-features/file-browser). #### SendMythicRPCFileBrowserParsePath This function allows you to pass in a `Path` (string) and have Mythic attempt to parse it into its specific sections. The resulting `AnalyzedPath` specifies the host (if one was specified as part of a UNC Path), the path separator used (`/` or `\`), and an array of all the pieces of the path. This function attempts to allow a more consistent approach for agents wanting to handle a variety of pathing options without needing to write parsing logic on their own. #### SendMythicRPCFileBrowserRemove This function allows you to mark files in the file browser as deleted. They'll hide from view unless you specifically click a button in the UI to see deleted files as well (they'll have a strikethrough). The `TaskID` field is required and allows Mythic to appropriately scope the queries to the right operation. The `RemovedFiles` array of objects identify the absolute path to the file/folder that's removed and (optionally) the Host if it's different than the host where the task/callback is running. #### SendMythicRPCHandleAgentJSON This function allows you to send a normal full agent JSON message to Mythic for processing. You need to specify either the `CallbackID` (int) or `AgentCallbackID` (uuid string) of the callback where this message is coming from. #### SendMythicRPCKeylogCreate This function allows you to register keystrokes within Mythic's database. The `TaskID` is required as it allows Mythic to scope the keylogs to the appropriate operation and task. The `Keylogs` array allows you to specify the User, Host, and Window title where the keystrokes are coming from, but only the actual keystrokes themselves are required. #### SendMythicRPCKeylogSearch This function allows you to search for keylogs by either specific keystrokes, window titles, or users. The `TaskID` parameter is required as it allows Mythic to scope the query to the appropriate operation. #### SendMythicRPCOperationEventLogCreate This function allows you to send a message to the event log. You need to provide either `TaskID`, `CallbackID`, `AgentCallbackID`, or `OperationID` to appropriately scope the message to the right operation. After that, the `message` is what you actually want to send and the `MessageLevel` indicates if you want it to be a `warning`, `debug`, or `info` message. #### SendMythicRPCPayloadAddCommand This function allows you to add a command to an already created payload so that when that payload is executed and turned into a callback, Mythic will track the command as available for use. You must supply the `PayloadUUID` value so Mythic knows exactly which payload and then the `Commands` array is where you specify the names of the commands you want to add. This can be particularly helpful during payload building if you decide that you need to add more commands to the payload than what the user selected (ex: the user selected a command that has a dependency but didn't select the dependency). #### SendMythicRPCPayloadCreateFromScratch This function allows you to create an entirely new payload from scratch by specify all the components - build parameters, os, c2 profiles and their parameters, commands, filename, description, etc. You must supply the `TaskID` because this allows Mythic to scope the build to the appropriate operation. #### SendMythicRPCPayloadCreateFromUUID This function allows you to create a payload based on an existing payload (specified by UUID), but allows you to modify the new payload's description, filename, and optionally if you want to register that it's going to be placed on a remote host. This remote host piece can be really helpful if you're doing this as part of a lateral movement technique so that the new payload will already be associated with the remote host in a `link` dropdown modal. The `PayloadUUID` and `TaskID` values are required so that Mythic can properly track not just that the new Payload is in the right operation, but specifically where / why the new payload is getting created. #### SendMythicRPCPayloadGetContent This function allows you to get the content of a payload based on its UUID. The `PayloadUUID` field is required. #### SendMythicRPCPayloadRemoveCommand This function allows you to remove commands from a payload that's tracked with Mythic. While this doesn't actually modify the payload file itself, it does inform Mythic that when the payload executes and becomes a callback, that the specified commands are no longer available for tasking. `PayloadUUID` and `Commands` are both required fields. #### SendMythicRPCPayloadSearch This function allows you to search for payloads. If you just provide the `PayloadUUID` value, then Mythic will get that specific payload's configuration. Otherwise, you need to provide the `CallbackID` (int) so that Mythic can scope the search to the appropriate operation. The other fields, `Description`, `Filename`, `PayloadTypes` array, and `BuildParameters` allow you to narrow your search to just specific payloads from specific payload types, ones with specific build parameter values, or with specific filename/descriptions. #### SendMythicRPCPayloadUpdateBuildStep This function allows you to update the status of build steps during the build process for your payload. You must supply the `PayloadUUID` so Mythic knows which payload and the `StepName` so Mythic knows which step this is referring to. Then you can specify if there's any `StepStdout` or `StepStderr` to capture. You then need to specify if the step was success/error (`StepSuccess`) or if the step was skipped `StepSkip`. #### SendMythicRPCPayloadOnHostCreate This function allows you to register a specific payload on a specific host. This is helpful for linking payloads if you know that a payload is already going to exist on a specific host. `TaskID` is required because it allows Mythic to scope to the right operation and to track which task caused the payload to end up on a specific host. This can be helpful as part of a `process_response` function call when doing lateral movement so that you can automatically update Mythic that your payload is on a remote host after you do something like upload it via SMB. #### SendMythicRPCProcessCreate This function allows you to create new processes for the Process Browser in the Mythic UI. The `TaskID` value is required as it allows Mythic to scope the data to the appropriate operation. The array of Process data is the same as what the [agent would supply](/version-4.0/customizing/hooking-features/process_list) when doing the same thing. #### SendMythicRPCProcessSearch This function allows you to search for processes across a wide variety of attributes. The `TaskID` value is required because it allows Mythic to scope the search to the right operation. #### SendMythicRPCProxyStart This function indicates to Mythic that it needs to track a new proxy connection. `TaskID` is a required value as it allows Mythic to appropriately scope to the right operation and callback. The `LocalPort` for a `PortType` of `socks` means the port that the Mythic server will open up. The `LocalPort` for a `PortType` of `rpfwd` is the port that the *agent* is going to open up. `RemoteIP` and `RemotePort` are both for `rpfwd` connections. You can optionally add authentication to your `socks` ports by supplying a `Username` and `Password` value. For `socks`, if you don't provide a local port and leave it at `0`, then Mythic will select a port for you and let you know which one it is in the response. #### SendMythicRPCProxyStop This function indicates to Mythic that it needs to stop a proxied capability; if this is a `socks` connection, it'll close the associated port. The `Port` here is the port that Mythic opened or the port that the agent opened. If you started a port with a `Username` and `Password`, then you need to supply those again to close it. #### SendMythicRPCResponseCreate This function allows you to send output a task's output in the Mythic UI. This requires the `TaskID` so Mythic knows which task to associate the new output with and then the raw bytes of the output. Mythic saves this as raw bytes to help reduce issues between encodings and potential null characters from assembly/bof outputs. #### SendMythicRPCResponseSearch This function allows you to search for content in responses from a specific task. Both `TaskID` and `Response` are required. #### SendMythicRPCTagCreate This function allows you to tag tasks, files, credentials, or mtyhictree (FileBrowser / ProcessBrowser) objects with tags. `TagTypeID` is a required field so that Mythic knows which tag this is an instance of. You also need to supply one of `TaskID`, `FileID`, `CredentialID`, or `MythicTreeID` so that Mythic can associate the tag with the appropriate object. #### SendMythicRPCTagSearch This function allows you to search through existing tags. The `TaskID` field is required as it allows Mythic to scope the query to the right operation. All of the `Search*` fields allow you to scope your search. #### SendMythicRPCTagTypeGetOrCreate This function allows you to create tag types in an idempotent manner - if the tagtype already exists, the existing object will be returned or a new one will get created. The `TaskID` field is required because it allows Mythic to scope this mutation to the right operation. Outside of that, you must supply either the TagTypeID to fetch the data associated with a specific tagtype or you must supply the name, color, and description to create your tagtype. #### SendMythicRPCTaskCreate This function allows you to create a new task for a specific callback. This isn't a subtask of an existing task, but a new task. There are a few things you must provide: * `AgentCallbackID` - this is the UUID string of the callback you want to task * `CommandName` - this is the name of the command you want to issue * `Params` - these are the string version of the parameters you want to provide. In the vast majority of the time, you should be using a dictionary to call out all of the parameters and then turn that dictionary into a json string for this. You can optionally provide `PayloadTypeName` if the command you're wanting to issue isn't part of the payload type for the callback. That might sound confusing, but a good example would be the `forge` CommandAugmentation container. You might want to issue a `forge` command to an `apollo` callback. You can optionally provide a `Token` (int) identifier as well if you want to indicate to your agent to use a specific token for the task. #### SendMythicRPCTaskCreateSubtask This function allows you to create a subtask of the current task. By doing things this way as compared to the above function, you can provide a `SubtaskCallbackFunction` name and get execution again when the subtask finishes. You must provide the `TaskID` value so that Mythic can associate this subtask with the appropriate task. Similarly to the above command, you can optionally provide the `PayloadTypeName` if the `CommandName` you're trying to issue isn't associated with the callback payload type, but another one. `Params` can be an empty string, but if you're providing parameters then ideally you provide a JSON string representation of the parameters by name. `SubtaskCallbackFunction` allows you to provide the name of a function for execution upon the subtask finishing (successfully or with error). #### SendMythicRPCTaskCreateSubtaskGroup This function allows you to create multiple subtasks of the current task all at once and register a single function to get execution when all of them finish. This is very similar to the `SendMythicRPCTaskCreateSubtask` function, except it's for a group rather than a single task. `TaskID` is a required field so that Mythic knows which task these new tasks will be under. `GroupName` is also required so that Mythic can group all of these tasks together. The `Tasks` list is the list of tasks to create which all require a `CommandName` at least. #### SendMythicRPCTaskDisplayToRealIDSearch This function allows you to take an operation (name or id) and a task's display ID and convert it into the Task's real ID (TaskID). In the vast majority of instances you shouldn't need to use this function, but it can be a helpful way to convert if you only happen to have a display ID for a task and an Operation Name/ID, but need the task's real ID. #### SendMythicRPCTaskSearch This function allows you to search for tasks that have already executed. You must provide `TaskID` so that your search can be scoped to the right operation. All of the `Search*` parameters allow you to narrow down your search. #### SendMythicRPCTaskUpdate This function allows you to update various attributes about a task. The `TaskID` field is required so that Mythic knows which task to update. All of the `Update*` fields allow you to control which things you want to update. It's very common to update the `UpdateStatus` field with custom status options and to do the `UpdateCompleted` to mark the task as finished. #### SendMythicRPCAPITokenCreate This function allows you to create a "token" within Mythic's token tracking. This is a precursor to adding a token to a callback. The `TaskID` field is required as it allows Mythic to scope this to the right operation. The list of `Tokens` is also required, but for each `Token`, you only have to provide the `TokenID` (this is an agent-created ID that allows Mythic and the agent to reference the same token). #### SendMythicRPCTokenRemove This function allows you to remove tokens from Mythic's token tracking. Similar to the create function, you must provide the `TaskID` field so that Mythic can scope this to the right operation. In the Tokens list, the only required field is the `TokenID` so that Mythic can look up the right tokens to mark them as deleted.