Agents 2.2 -> 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 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
.
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.
More information on how to code browser scripts for the new UI can be found here: What is Browser Scripting?.
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:
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.
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 afilename
- 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 afile
- 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.
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:
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 likex86 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
orshell -command whoami
). Mythic can successfully parse out the parameters you've given into a single parameter_group and gives you adictionary
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 aString
instead, then that gets treated ascommand_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:
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.