Process Explorer fixed

This commit is contained in:
marcos.luna
2025-10-29 12:51:37 +01:00
parent b194f13cf6
commit aeb74259c7
7 changed files with 342 additions and 22 deletions
+53
View File
@@ -0,0 +1,53 @@
# Alternative Dockerfile for local development and customization
# This Dockerfile ensures that local changes are always picked up
# Use this if the standard Dockerfile doesn't pick up your changes
FROM itsafeaturemythic/mythic_python_base:latest
# --- ARGs que Mythic usa al construir payloads (mantenerlos) ---
ARG CA_CERTIFICATE
ARG NPM_REGISTRY
ARG PYPI_INDEX
ARG PYPI_INDEX_URL
ARG DOCKER_REGISTRY_MIRROR
ARG HTTP_PROXY
ARG HTTPS_PROXY
# --- Evita prompts (tzdata) y builds no reproducibles ---
ENV DEBIAN_FRONTEND=noninteractive \
PIP_NO_CACHE_DIR=1
# --- Instala toolchain y utilidades necesarias para compilar agentes en C ---
RUN apt-get update && \
apt-get install -y --no-install-recommends \
apt-utils ca-certificates git zip make build-essential \
libssl-dev zlib1g-dev libbz2-dev xz-utils tk-dev libffi-dev \
liblzma-dev libsqlite3-dev protobuf-compiler mingw-w64 nasm && \
rm -rf /var/lib/apt/lists/*
# --- Reconfirma pip y setuptools actualizados (ya viene, pero por si acaso) ---
RUN python -m pip install --upgrade pip setuptools wheel
# --- Instala tus dependencias (sin reinstalar mythic-container) ---
COPY requirements.txt /
RUN pip install --no-cache-dir --no-deps -r /requirements.txt
# --- Variables de entorno necesarias para comunicación con Mythic Core ---
ENV MYTHIC_SERVER_HOST="mythic_server"
ENV MYTHIC_SERVER_PORT="17443"
ENV MYTHIC_SERVER_USERNAME="mythic_admin"
ENV MYTHIC_SERVER_PASSWORD="mythic_password"
# --- Copia tu código de translator de forma explícita ---
# Esto asegura que todos los cambios locales se copien correctamente
WORKDIR /Mythic/
# Copiar estructura completa del proyecto
COPY ./ /Mythic/cazalla/
# Asegurar que los permisos sean correctos para Python
RUN chmod -R a+rX /Mythic/cazalla/
# --- Comando por defecto ---
CMD ["python3", "main.py"]
+69
View File
@@ -0,0 +1,69 @@
# Git files
.git
.gitignore
.gitattributes
# Python cache
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
dist/
build/
*.egg
# Virtual environments
venv/
env/
ENV/
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS files
.DS_Store
Thumbs.db
# Build artifacts
*.exe
*.dll
*.o
*.obj
*.a
*.lib
build/
*.pdb
*.ilk
# Logs
*.log
# Documentation (optional - remove if you want to include docs in container)
*.md
LICENSE*
# Test files (optional - remove if you want tests in container)
test/
tests/
*_test.py
test_*.py
# Temporary files
*.tmp
*.bak
*.cache
# Mythic specific (don't copy these as they're managed by Mythic)
.env
docker-compose.yml
mythic-cli
# Agent code build directories (will be built inside container)
cazalla/agent_code/cazalla/build/
+1
View File
@@ -35,6 +35,7 @@ ENV MYTHIC_SERVER_USERNAME="mythic_admin"
ENV MYTHIC_SERVER_PASSWORD="mythic_password"
# --- Copia tu código de translator ---
# This COPY ensures local changes are included when USE_BUILD_CONTEXT=true
WORKDIR /Mythic/
COPY ./ /Mythic/cazalla/
@@ -9,6 +9,9 @@
BOOL SelfIsWindowsVistaOrLater();
BOOL PackageAddFormatPrintf(PPaquete package, BOOL copySize, char* fmt, ...);
#include <tlhelp32.h>
#include <psapi.h>
// Nota: Removidas estructuras de PEB ya que no obtenemos command_line (como Xenon)
BOOL obtenerNombreDelToken(HANDLE hProcess, char* nombreCuenta, int tamano) {
HANDLE hToken;
@@ -28,6 +31,8 @@ BOOL obtenerNombreDelToken(HANDLE hProcess, char* nombreCuenta, int tamano) {
return result;
}
// Nota: Como Xenon, no obtenemos command_line para mantener simplicidad y compatibilidad
VOID listarProcesos(PAnalizador argumentos) {
SIZE_T tamanoUuid = 36;
@@ -49,38 +54,44 @@ VOID listarProcesos(PAnalizador argumentos) {
PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
if (Process32First(toolhelp, &pe)) {
do {
// Como Xenon: intentar abrir proceso para obtener info adicional
HANDLE hProcess = OpenProcess(SelfIsWindowsVistaOrLater() ? PROCESS_QUERY_LIMITED_INFORMATION : PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
DWORD sid = -1;
// Inicializar valores por defecto
nombreCuenta[0] = '\0';
BOOL isWow64 = FALSE;
if (hProcess) {
// Obtener información del usuario
if (!obtenerNombreDelToken(hProcess, nombreCuenta, sizeof(nombreCuenta))) {
nombreCuenta[0] = '\0';
}
// Obtener Session ID
if (!ProcessIdToSessionId(pe.th32ProcessID, &sid)) {
sid = -1;
}
BOOL isWow64 = IsWow64ProcessEx(hProcess);
PackageAddFormatPrintf(salida,
FALSE,
"%s\t%d\t%d\t%s\t%s\t%d\n",
pe.szExeFile,
pe.th32ParentProcessID,
pe.th32ProcessID,
isWow64 ? "x86" : arch,
nombreCuenta,
sid);
// Obtener arquitectura
isWow64 = IsWow64ProcessEx(hProcess);
CloseHandle(hProcess);
} else {
// Si no podemos abrir el proceso, intentar obtener al menos el Session ID
ProcessIdToSessionId(pe.th32ProcessID, &sid);
}
else {
PackageAddFormatPrintf(salida,
FALSE,
"%s\t%d\t%d\n",
pe.szExeFile,
pe.th32ParentProcessID,
pe.th32ProcessID);
}
CloseHandle(hProcess);
// Formato exacto como Xenon: nombre\tPPID\tPID\tarch\tuser\tsession\n
PackageAddFormatPrintf(salida,
FALSE,
"%s\t%d\t%d\t%s\t%s\t%d\n",
pe.szExeFile,
pe.th32ParentProcessID,
pe.th32ProcessID,
hProcess ? (isWow64 ? "x86" : arch) : arch, // Usar arch por defecto si no podemos abrir
nombreCuenta[0] != '\0' ? nombreCuenta : "",
sid != -1 ? sid : 0);
} while (Process32Next(toolhelp, &pe));
}
@@ -40,5 +40,11 @@ class PsCommand(CommandBase):
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
"""
Process response from agent. The translator automatically converts
tab-separated process list to Process Browser JSON format.
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# Note: The translator (commands_from_implant.py) handles conversion
# from tab-separated format to Process Browser JSON format automatically
return resp
@@ -86,6 +86,65 @@ def getTasking(data):
return dataJson
def parse_process_list(output_text):
"""
Parsea el texto tab-separated de procesos al formato JSON del Process Browser.
Formato esperado del agente (como Xenon): nombre\\tPPID\\tPID\\tarch\\tuser\\tsession\\n
Formato requerido por Mythic Process Browser según docs:
https://docs.mythic-c2.net/customizing/hooking-features/process_list
"""
processes = []
lines = output_text.strip().split('\n')
for line in lines:
line = line.strip()
if not line:
continue
parts = line.split('\t')
if len(parts) < 3:
continue # Necesitamos al menos nombre, PPID, PID
try:
# Campos requeridos según documentación de Mythic
process_id = int(parts[2]) if len(parts) > 2 and parts[2].strip().isdigit() else None
if process_id is None:
continue # Saltar si no hay PID válido
process = {
"process_id": process_id,
"name": parts[0] if len(parts) > 0 and parts[0].strip() else "",
}
# Si el nombre está vacío o es inválido, saltar este proceso
if not process["name"]:
continue
# Campos opcionales según documentación
if len(parts) > 1 and parts[1].strip().isdigit():
process["parent_process_id"] = int(parts[1])
if len(parts) > 3 and parts[3].strip():
process["architecture"] = parts[3].strip()
if len(parts) > 4 and parts[4].strip():
process["user"] = parts[4].strip()
# Session ID se guarda como metadata
if len(parts) > 5 and parts[5].strip().isdigit():
process["session_id"] = int(parts[5])
# Nota: No incluimos command_line como Xenon para mantener simplicidad
# Si se necesita en el futuro, se puede agregar lectura de PEB
processes.append(process)
except Exception as e:
print(f"[ERROR] Error parseando línea de proceso: {line}, error: {e}")
continue
return processes
def postResponse(data):
print(f"Tamaño del Base64 recibido: {len(data)} bytes")
@@ -103,11 +162,51 @@ def postResponse(data):
print(f"Fallo al decodificar la salida: {e}")
decoded_output = "[ERROR DECODIFICANDO]"
task_id_str = uuidTask.decode('cp850')
# Intentar detectar si es una lista de procesos (formato tab-separated)
# Formato esperado del comando ps: nombre\tPPID\tPID\tarch\tuser\tsession\n
is_process_list = False
processes = []
# Detectar si es una lista de procesos: formato tab-separated con al menos 3 columnas
# Formato: nombre\tPPID\tPID\t[arch]\t[user]\t[session]
if decoded_output and '\t' in decoded_output:
lines = [l.strip() for l in decoded_output.strip().split('\n') if l.strip()]
if len(lines) > 0:
# Verificar que la mayoría de líneas tengan formato de proceso
# (al menos 3 columnas: nombre, PPID, PID)
valid_lines = 0
for line in lines[:10]: # Revisar primeras 10 líneas
parts = line.split('\t')
if len(parts) >= 3:
# Verificar que PPID (índice 1) y PID (índice 2) sean números
# y que el nombre (índice 0) no esté vacío
if (parts[1].strip().isdigit() and
parts[2].strip().isdigit() and
parts[0].strip()):
valid_lines += 1
# Si al menos 80% de las líneas revisadas tienen formato válido
sample_size = len(lines[:10])
if sample_size > 0 and valid_lines >= max(1, sample_size * 0.8):
is_process_list = True
print(f"[PROCESS_BROWSER] Detectado formato de lista de procesos ({valid_lines}/{sample_size} líneas válidas), parseando...")
processes = parse_process_list(decoded_output)
print(f"[PROCESS_BROWSER] Parseados {len(processes)} procesos")
jsonTask = {
"task_id": uuidTask.decode('cp850'),
"user_output": decoded_output,
"task_id": task_id_str,
"completed": True,
}
# Siempre incluir user_output para que se vea en la consola
jsonTask["user_output"] = decoded_output
# Si detectamos una lista de procesos, también incluir el formato para Process Browser
if is_process_list and len(processes) > 0:
jsonTask["processes"] = processes
print(f"[PROCESS_BROWSER] Respuesta convertida al formato Process Browser con {len(processes)} procesos")
resTaks.append(jsonTask)
+81
View File
@@ -82,6 +82,87 @@ The agent will be cross-compiled for Windows using MinGW in a Docker container.
---
## 🔨 Customizing Cazalla
If you've forked Cazalla or made local changes and want to use your customized version, you need to configure Mythic to use local builds instead of remote images.
### Making Changes Work
By default, Mythic may use pre-built remote images for faster deployment. However, if you've made local changes to the agent code, translator, or any Python files, you need to configure Mythic to use your local build context.
#### Configure Build Variables
After installing Cazalla, Mythic will add these variables to your `~/Mythic/.env` file:
```bash
# Use local build context instead of remote image
CAZALLA_USE_BUILD_CONTEXT="true"
# Use direct mount instead of Docker volume (recommended for development)
CAZALLA_USE_VOLUME="false"
# Optional: Remote image URL (only used if USE_BUILD_CONTEXT is false)
CAZALLA_REMOTE_IMAGE="ghcr.io/your-org/cazalla:v1.0.0"
```
#### Variables Explanation
- **`CAZALLA_USE_BUILD_CONTEXT`**:
- Set to `"true"` to build from your local `Dockerfile` and include your local changes
- Set to `"false"` (default) to use the pre-built remote image
- **Important**: You must set this to `"true"` if you've modified any files!
- **`CAZALLA_USE_VOLUME`**:
- Set to `"false"` (default) to mount the local folder directly into the container
- Set to `"true"` to use a Docker volume instead
- Direct mount is better for development as changes are immediately visible
- **`CAZALLA_REMOTE_IMAGE`** (optional):
- Specifies a pre-built remote image URL
- Only used when `CAZALLA_USE_BUILD_CONTEXT="false"`
#### Apply Changes
After modifying these variables, rebuild the container:
```bash
cd ~/Mythic
sudo ./mythic-cli build cazalla
```
#### Verifying Your Changes
1. Make your code changes
2. Set `CAZALLA_USE_BUILD_CONTEXT="true"` in `~/Mythic/.env`
3. Run `sudo ./mythic-cli build cazalla`
4. Check logs: `sudo docker logs cazalla_translator`
If changes still don't appear:
- Verify the `Dockerfile` copies your modified files (see line 39: `COPY ./ /Mythic/cazalla/`)
- Check that file paths in the Dockerfile match your directory structure
- Ensure you're editing files in the correct location (inside `Payload_Type/cazalla/`)
#### Example: Customizing Agent Code
```bash
# 1. Edit agent C code
vim Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c
# 2. Ensure build context is enabled
echo 'CAZALLA_USE_BUILD_CONTEXT="true"' >> ~/Mythic/.env
# 3. Rebuild the container
cd ~/Mythic
sudo ./mythic-cli build cazalla
# 4. Rebuild a payload from the UI
# Your changes will now be included in the compiled agent
```
For more information, see the [Mythic documentation on customizing public agents](https://docs.mythic-c2.net/customizing/customizing-public-agent).
---
## 🛠️ Supported Commands
### File System Operations