+++ title = "Developing Commands" chapter = false weight = 20 pre = "3. " +++ # Developing New Commands for Cazalla This guide provides a step-by-step walkthrough for adding new commands to the Cazalla agent. Understanding this process is essential for extending Cazalla's capabilities. ## ๐Ÿ“‹ Overview Adding a new command to Cazalla requires coordination across multiple components: 1. **Python Command Definition** - Defines the command interface in Mythic 2. **Translator Mapping** - Maps command names to hex codes for agent communication 3. **C Agent Implementation** - Implements the actual command logic 4. **Command Dispatcher Registration** - Registers the handler in the command dispatcher 5. **Builder Configuration** - Enables command selection during payload creation ## ๐Ÿ—๏ธ Architecture Overview ```mermaid graph LR A[Python Command] --> B[Translator] B --> C[C Agent Handler] C --> D[Command Dispatcher] D --> E[Builder Mapping] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#e8f5e9 style D fill:#fce4ec style E fill:#f3e5f5 ``` **Data Flow:** - **Mythic UI** โ†’ Python command receives task - **Python** โ†’ Translator converts to binary format with hex code - **Agent** โ†’ C handler processes command and returns response - **Translator** โ†’ Converts response back to Mythic format ## ๐Ÿ“ Step-by-Step Guide ### Step 1: Create Python Command File Create a new Python file in `Payload_Type/cazalla/cazalla/agent_functions/` named after your command (e.g., `mycommand.py`). #### 1.1 Command Arguments Class Define the argument parsing logic: ```python from mythic_container.MythicCommandBase import * from mythic_container.MythicRPC import * class MyCommandArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ CommandParameter( name="param1", type=ParameterType.String, # or ParameterType.Number, etc. description="Description of parameter", parameter_group_info=[ParameterGroupInfo( required=True, # or False ui_position=1 )] ), # Add more parameters as needed ] async def parse_arguments(self): """Parse command-line arguments""" if len(self.command_line) == 0: raise ValueError("Parameter required") # Parse and validate arguments self.add_arg("param1", self.command_line) async def parse_dictionary(self, dictionary): """Parse dictionary arguments (from UI)""" self.load_args_from_dictionary(dictionary) ``` #### 1.2 Command Class Define the main command class: ```python class MyCommandCommand(CommandBase): cmd = "mycommand" # Command name (lowercase, no spaces) needs_admin = False # Set to True if command requires admin privileges help_cmd = "mycommand " description = "Detailed description of what the command does" version = 1 author = "YourName" argument_class = MyCommandArguments attackmapping = ["T1059"] # MITRE ATT&CK technique IDs attributes = CommandAttributes( supported_os=[SupportedOS.Windows], builtin=False, # Set to True if command should always be included suggested_command=False # Set to True to suggest in UI ) async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTaskOPSECPreTaskMessageResponse: """Optional: OPSEC check before task creation""" # Add warnings about detection risks return PTTaskOPSECPreTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPreBlocked=False, OpsecPreBypassRole="operator", OpsecPreMessage="OPSEC warning message here" ) async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTaskOPSECPostTaskMessageResponse: """Optional: OPSEC check after task creation""" return PTTaskOPSECPostTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPostBlocked=False, OpsecPostBypassRole="operator", OpsecPostMessage="Post-execution warning" ) async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: """Process task creation - called when task is created""" response = PTTaskCreateTaskingMessageResponse( TaskID=taskData.Task.ID, Success=True ) # Add any pre-processing logic here # Access arguments: taskData.args.get_arg("param1") return response async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: """Process agent response - called when agent sends response""" resp = PTTaskProcessResponseMessageResponse( TaskID=task.Task.ID, Success=True ) # Process response data here if needed return resp ``` #### 1.3 Key Attributes - **`cmd`**: Command name used in Mythic (must match translator mapping) - **`builtin`**: - `True` = Always included in payload (e.g., `exit`, `sleep`) - `False` = Can be selected during payload creation - **`needs_admin`**: Set to `True` if command requires administrator privileges - **`attackmapping`**: List of MITRE ATT&CK technique IDs (e.g., `["T1059"]`) ### Step 2: Add Command to Translator Edit `Payload_Type/cazalla/translator/commands_from_c2.py` and add your command to the `commands` dictionary: ```python commands = { # ... existing commands ... "mycommand": {"hex_code": 0x99, "input_type": "string"}, } ``` **Important Notes:** - **`hex_code`**: Choose an unused hex code (0x00-0xFF). Check `comandos.h` for existing codes - **`input_type`**: - `"string"` - String parameter - `"int"` - Integer parameter - `None` - No parameters - Special types: `"bof_special"`, `"assembly_special"`, `"socks_special"` (see existing commands) **Hex Code Allocation:** - `0x00-0x1F`: Reserved for core commands - `0x20-0x5F`: File system and standard commands - `0x60-0x6F`: Network tunneling commands - `0x70-0x7F`: Code execution commands - `0x80+`: Control commands ### Step 3: Implement C Agent Handler #### 3.1 Define Command Constant Edit `Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h` and add: ```c #define MYCOMMAND_CMD 0x99 // Must match hex_code from translator ``` #### 3.2 Implement Handler Function Create or edit the appropriate C file (e.g., `SistemadeFicheros.c` for file operations, or create a new file): ```c #include "cazalla.h" #include "comandos.h" #include "paquete.h" void MyCommandHandler(PAnalizador analizadorTarea) { _inf("===== PROCESSING MYCOMMAND_CMD (0x%02X) =====", MYCOMMAND_CMD); // Read task UUID (first 36 bytes) SIZE_T uuidLen = 36; PCHAR taskUuid = getString(analizadorTarea, &uuidLen); if (!taskUuid || uuidLen != 36) { _err("Error reading task UUID"); if (analizadorTarea) liberarAnalizador(analizadorTarea); return; } // Read parameters based on input_type // For string input_type: SIZE_T param1Len = 0; PCHAR param1 = getString(analizadorTarea, ¶m1Len); // For int input_type: // UINT32 param1 = (UINT32)getInt32(analizadorTarea); // Implement your command logic here // ... // Create response packet PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE adds agent UUID addString(respuesta, taskUuid, FALSE); // Add task UUID // Add output to response PackageAddFormatPrintf(respuesta, FALSE, "Command executed successfully\n"); PackageAddFormatPrintf(respuesta, FALSE, "Parameter: %.*s\n", (int)param1Len, param1); // Optional: Add artifacts (file writes, process creates, etc.) // addArtifact(respuesta, "File Write", "C:\\path\\to\\file"); // Optional: Add credentials if discovered // addCredential(respuesta, "plaintext", "DOMAIN", "password", "username"); // Send response Analizador* resp = mandarPaquete(respuesta); liberarPaquete(respuesta); if (resp) liberarAnalizador(resp); if (analizadorTarea) liberarAnalizador(analizadorTarea); _inf("===== MYCOMMAND_CMD COMPLETED ====="); } ``` #### 3.3 Key C Functions - **`getString(analizador, &len)`**: Read string from buffer - **`getInt32(analizador)`**: Read 32-bit integer - **`getByte(analizador)`**: Read single byte - **`getBytes(analizador, &len)`**: Read byte array - **`nuevoPaquete(type, add_uuid)`**: Create response packet - **`addString(paquete, str, copy)`**: Add string to packet - **`PackageAddFormatPrintf(paquete, copy, format, ...)`**: Add formatted output - **`addArtifact(paquete, type, data)`**: Add artifact (file write, process create, etc.) - **`addCredential(paquete, type, realm, credential, account)`**: Add credential - **`mandarPaquete(paquete)`**: Send packet to Mythic - **`liberarPaquete(paquete)`**: Free packet memory - **`liberarAnalizador(analizador)`**: Free analyzer memory ### Step 4: Register Handler in Command Dispatcher Edit `Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c` and add your command to the dispatch chain in `handleGetTasking()`: ```c #ifdef ENABLE_MYCOMMAND_CMD else if (tarea == MYCOMMAND_CMD) { _inf("===== PROCESSING MYCOMMAND_CMD (0x%02X) =====", tarea); MyCommandHandler(analizadorTarea); _inf("===== MYCOMMAND_CMD COMPLETED ====="); } #endif ``` **Placement:** - Add after `SLEEP_CMD` (which always closes with `}`) - Before the final `else` block for unknown commands - Use `else if` (not `} else if`) to maintain the chain **Important:** The command handler must be wrapped in `#ifdef ENABLE_MYCOMMAND_CMD` to support conditional compilation. ### Step 5: Add to Builder Mapping Edit `Payload_Type/cazalla/cazalla/agent_functions/builder.py` and add your command to the `command_to_define` dictionary (around line 209): ```python command_to_define = { # ... existing commands ... "mycommand": "ENABLE_MYCOMMAND_CMD", } ``` This maps the Python command name to the C preprocessor define name. ### Step 6: Update Includes (if needed) If your command requires a new header file: **In `comandos.h`:** ```c #ifdef ENABLE_MYCOMMAND_CMD #include "mycommand.h" #endif ``` **In `comandos.c`:** ```c #ifdef ENABLE_MYCOMMAND_CMD #include "mycommand.h" #endif ``` ### Step 7: Add Forward Declaration (if needed) If your handler function is defined in a separate file, add a forward declaration in `comandos.c`: ```c #ifdef ENABLE_MYCOMMAND_CMD void MyCommandHandler(PAnalizador analizadorTarea); #endif ``` ## ๐Ÿ” Complete Example: Adding a "ping" Command Let's walk through a complete example of adding a simple `ping` command that sends a message back to Mythic. ### Example 1: Python Command File (`ping.py`) ```python from mythic_container.MythicCommandBase import * from mythic_container.MythicRPC import * class PingArguments(TaskArguments): def __init__(self, command_line, **kwargs): super().__init__(command_line, **kwargs) self.args = [ CommandParameter( name="message", type=ParameterType.String, description="Message to echo back", parameter_group_info=[ParameterGroupInfo(required=False)] ), ] async def parse_arguments(self): if len(self.command_line) > 0: self.add_arg("message", self.command_line) else: self.add_arg("message", "pong") async def parse_dictionary(self, dictionary): self.load_args_from_dictionary(dictionary) class PingCommand(CommandBase): cmd = "ping" needs_admin = False help_cmd = "ping [message]" description = "Echo a message back to verify agent communication" version = 1 author = "YourName" argument_class = PingArguments attackmapping = [] attributes = CommandAttributes( supported_os=[SupportedOS.Windows], builtin=False ) async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: response = 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 ``` ### Example 2: Translator Mapping In `translator/commands_from_c2.py`: ```python commands = { # ... existing commands ... "ping": {"hex_code": 0x90, "input_type": "string"}, } ``` ### Example 3: C Header Definition In `comandos.h`: ```c #define PING_CMD 0x90 ``` ### Example 4: C Handler Implementation Create `ping.c`: ```c #include "cazalla.h" #include "comandos.h" #include "paquete.h" void PingHandler(PAnalizador analizadorTarea) { _inf("===== PROCESSING PING_CMD (0x%02X) =====", PING_CMD); // Read task UUID SIZE_T uuidLen = 36; PCHAR taskUuid = getString(analizadorTarea, &uuidLen); // Read message parameter SIZE_T messageLen = 0; PCHAR message = getString(analizadorTarea, &messageLen); // Create response PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); addString(respuesta, taskUuid, FALSE); // Echo the message back PackageAddFormatPrintf(respuesta, FALSE, "Ping received: %.*s\n", (int)messageLen, message); PackageAddFormatPrintf(respuesta, FALSE, "Agent is alive and responding!\n"); // Send response Analizador* resp = mandarPaquete(respuesta); liberarPaquete(respuesta); if (resp) liberarAnalizador(resp); if (analizadorTarea) liberarAnalizador(analizadorTarea); _inf("===== PING_CMD COMPLETED ====="); } ``` ### Example 5: Register in Dispatcher In `comandos.c`, add to the dispatch chain: ```c #ifdef ENABLE_PING_CMD else if (tarea == PING_CMD) { _inf("===== PROCESSING PING_CMD (0x%02X) =====", tarea); PingHandler(analizadorTarea); _inf("===== PING_CMD COMPLETED ====="); } #endif ``` ### Example 6: Builder Mapping In `builder.py`: ```python command_to_define = { # ... existing commands ... "ping": "ENABLE_PING_CMD", } ``` ## ๐ŸŽฏ Advanced Features ### Adding Artifacts Artifacts track system modifications for OPSEC and reporting: ```c // File write artifact addArtifact(respuesta, "File Write", "C:\\path\\to\\file"); // Process create artifact addArtifact(respuesta, "Process Create", "cmd.exe /c whoami"); // Network connection artifact addArtifact(respuesta, "Network Connection", "Connection to 192.168.1.1:80"); // Registry write artifact addArtifact(respuesta, "Registry Write", "HKEY_LOCAL_MACHINE\\..."); ``` ### Adding Credentials Automatically extract and report credentials: ```c // Plaintext password addCredential(respuesta, "plaintext", "DOMAIN", "password123", "username"); // NTLM hash addCredential(respuesta, "hash", "", "aad3b435b51404ee:hashvalue", "Administrator"); // Certificate addCredential(respuesta, "certificate", "", "cert_data_base64", "account_name"); ``` ### OPSEC Checking Implement OPSEC warnings in Python: ```python async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTaskOPSECPreTaskMessageResponse: message = "โš ๏ธ OPSEC WARNING\n\n" message += "This command may be detected by:\n" message += " โ€ข EDR/XDR solutions\n" message += " โ€ข Network monitoring\n" return PTTaskOPSECPreTaskMessageResponse( TaskID=taskData.Task.ID, Success=True, OpsecPreBlocked=False, # Set to True to block execution OpsecPreBypassRole="operator", OpsecPreMessage=message ) ``` ### Process Browser Integration To integrate with Mythic's Process Browser: ```python supported_ui_features = ["process_browser:list"] # or "process_browser:kill" ``` Then in the translator, format process data as JSON matching Mythic's Process Browser format. ### File Browser Integration To integrate with Mythic's File Browser: ```python supported_ui_features = ["file_browser:list"] ``` The translator will automatically convert file listings to File Browser format. ## ๐Ÿงช Testing Your Command ### 1. Restart Mythic Agent Container After adding your command, restart the Cazalla container: ```bash cd ~/Mythic ./mythic-cli restart cazalla ``` ### 2. Verify Command Appears 1. Open Mythic UI 2. Navigate to Payloads โ†’ Create Payload 3. Select Cazalla 4. Check that your command appears in the command selection list ### 3. Build Test Payload 1. Select your command (and any others you need) 2. Configure C2 profile 3. Build the payload 4. Check build logs for any compilation errors ### 4. Test Command Execution 1. Deploy payload to test system 2. Execute your command from Mythic UI 3. Verify response is received correctly 4. Check for any errors in agent logs ## ๐Ÿ› Common Issues and Solutions ### Issue: Command Not Appearing in UI **Solution:** - Verify Python file is in `agent_functions/` directory - Check that class name ends with `Command` (e.g., `PingCommand`) - Restart Mythic agent container - Check Mythic logs for import errors ### Issue: Compilation Errors **Solution:** - Verify hex code is unique (check `comandos.h`) - Ensure `#ifdef` blocks are properly closed - Check that handler function is declared before use - Verify all includes are correct ### Issue: Command Not Executing **Solution:** - Verify command was selected during payload build - Check that hex code matches in translator and `comandos.h` - Verify handler is registered in `comandos.c` - Check agent logs for error messages ### Issue: Response Not Received **Solution:** - Verify response packet is created correctly - Check that task UUID is added to response - Ensure `mandarPaquete()` is called - Verify packet is properly formatted ## ๐Ÿ“š Reference: Command Hex Codes Current hex code allocations in Cazalla: - `0x00`: GET_TASKING - `0x01`: POST_RESPONSE - `0x15`: PROCESS_CMD (ps) - `0x20-0x28`: File system commands (cd, ls, pwd, cp, mkdir, rm, cat, download, upload) - `0x29`: SCREENSHOT_CMD - `0x30-0x31`: Keylogging (start, stop) - `0x32-0x37`: Token operations (list_tokens, steal_token, make_token, rev2self, whoami, kill) - `0x38`: SLEEP_CMD - `0x40-0x42`: Browser and SAM (browser_info, browser_dump, samdump) - `0x54`: SHELL_CMD - `0x60-0x63`: Network tunneling (socks, rpfwd) - `0x70-0x71`: Code execution (inline_execute, inline_execute_assembly) - `0x80`: EXIT_CMD - `0xF1`: CHECKIN **Available ranges for new commands:** - `0x43-0x53`: Available - `0x55-0x5F`: Available - `0x64-0x6F`: Available - `0x72-0x7F`: Available - `0x81-0xF0`: Available (avoid 0xF1-F5, 0xF7-F8 which are used for markers) ## โœ… Checklist Use this checklist when adding a new command: - [ ] Python command file created in `agent_functions/` - [ ] Command class inherits from `CommandBase` - [ ] `cmd` attribute matches command name - [ ] `builtin` attribute set appropriately - [ ] Command added to translator `commands` dictionary - [ ] Hex code defined in `comandos.h` - [ ] C handler function implemented - [ ] Handler registered in `comandos.c` dispatch chain - [ ] Command added to `builder.py` `command_to_define` mapping - [ ] Handler wrapped in `#ifdef ENABLE_XXX_CMD` - [ ] Includes added if needed - [ ] Command tested in payload build - [ ] Command tested in execution - [ ] Documentation updated (optional) ## ๐Ÿ”— Related Documentation - [Commands Reference](commands.md) - Complete list of all commands - [Getting Started](getting-started.md) - Installation and setup - [Features Overview](features.md) - Detailed feature explanations - [OPSEC Guide](opsec.md) - Operational security considerations --- **Next Steps:** After implementing your command, consider: - Adding OPSEC warnings for risky operations - Implementing artifact tracking for system modifications - Adding credential extraction if applicable - Writing unit tests for your command logic - Documenting your command in the commands reference