From 05d084bb6c12da6f8442cc2e168a6c6e781e6f74 Mon Sep 17 00:00:00 2001 From: Marmeus Date: Mon, 26 Jan 2026 09:35:31 +0000 Subject: [PATCH] Added develop commands doc file --- .../Cazalla/developing-commands.md | 666 ++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 documentation-payload/Cazalla/developing-commands.md diff --git a/documentation-payload/Cazalla/developing-commands.md b/documentation-payload/Cazalla/developing-commands.md new file mode 100644 index 0000000..58040d2 --- /dev/null +++ b/documentation-payload/Cazalla/developing-commands.md @@ -0,0 +1,666 @@ ++++ +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