// ConsoleApplication1.cpp : Defines the entry point for the console application.
//
#include <windows.h>
#include <stdio.h>
//#include <appmodel.h>
//#include <malloc.h>
//#include <stdlib.h>
//#include <Psapi.h>
//#include <mmdeviceapi.h>
//#include <shlwapi.h>

//#include "process.h"
//#include "ipc.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "peloader.h"
//#include "util_process.h"

#include "injectedDll.h"

#ifdef _DEBUG
#define EXE_FILE_32 "D:\\Dev\\AppInjection\\ApiHookDll32.dll"
//#define EXE_FILE_64 "C:\\Users\\unknown\\stuff\\Lotus\\other_stuff\\Injection\\ApiHookDll64\\ApiHookDll64\\x64\\Debug\\ApiHookDll.dll"
#define EXE_FILE_64  "C:\\Users\\unknown\\stuff\\Lotus\\other_stuff\\Injection\\ApiHookDll64\\ApiHookDll64\\x64\\Release\\ApiHookDll.dll"
#else
#define EXE_FILE_32 "ApiHookDll32.dll"
#define EXE_FILE_64 "ApiHookDll64.dll"
#endif


#define CALC_OFFSET(type, ptr, offset) (type) (((ULONG64) ptr) + offset)
#define CALC_OFFSET_DISP(type, base, offset, disp) (type)((ULONG64)(base) + (ULONG64)(offset) + disp)
#define CALC_DISP(type, offset, ptr) (type) (((ULONG64) offset) - (ULONG64) ptr)


//load an exe from file and inject it into a remote process
int loader_inject_proc(int pid, PIPC_PARAMS *pIPC)
{
	HMODULE LibHandle;
	HANDLE	hProcess;
	size_t	size;
	FILE	*fp;
	PCHAR   *lpFileData = NULL;
	LPVOID  lpEntryPoint = NULL;
	SIZE_T   dwWR = 0;
	BOOL	bIsWow64 = FALSE;

	int sizeRaw;

	OutputDebugStringA("Loading file\n");

	//open the remote process
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProcess == NULL)
		return 0;

	//verify if it's a 32 or 64 bit process
	if (!IsWow64Process(hProcess, &bIsWow64))
	{
		OutputDebugStringA("IsWow64Process Failed\n");
		return 0;
	}

	//errno_t ret;
	//if (bIsWow64)
	//{
	//	if (ret = fopen_s(&fp, EXE_FILE_32, "rb"))
	//	{
	//		//OutputDebugStringA("Can't open DLL file \"%s\".", EXE_FILE_32);
	//		return 0;
	//	}
	//}
	//else
	//{
	//	if (fopen_s(&fp, EXE_FILE_64, "rb"))
	//	{
	//		//OutputDebugStringA("Can't open DLL file \"%s\".", EXE_FILE_64);
	//		return 0;
	//	}
	//}

	//fseek(fp, 0, SEEK_END);
	//size = ftell(fp);
	////alloc memory for the file
	//lpFileData = (unsigned char *)malloc(size);
	//fseek(fp, 0, SEEK_SET);

	////read the file content
	//fread(lpFileData, 1, size, fp);
	//fclose(fp);

	//load exe in remote memory
	//loader_map_exe(hProcess, lpFileData, &lpEntryPoint);
	loader_map_exe(hProcess, injectedDll, &lpEntryPoint);

	//typedef void(__stdcall *FunctionStartMain)(void);
	//FunctionStartMain ExeMain = (FunctionStartMain)lpEntryPoint;

	typedef BOOL(APIENTRY *FunctionStartMain)(DWORD, DWORD, DWORD);
	FunctionStartMain ExeMain = (FunctionStartMain)lpEntryPoint;

	//ipc parameters
	*pIPC = (PIPC_PARAMS)malloc(sizeof(IPC_PARAMS));

	//PTHREAD_PARAMS pThreadParams = (PTHREAD_PARAMS)malloc(sizeof(THREAD_PARAMS));
	//pThreadParams->hProcess = hProcess;
	//pThreadParams->pIPC		= pIPC;

	//alloc memory for input buffer
	(*pIPC)->lpBufIN = VirtualAllocEx(hProcess, 0, 1024, MEM_COMMIT, PAGE_READWRITE);

	//alloc memory for output buffer
	(*pIPC)->lpBufOUT = VirtualAllocEx(hProcess, 0, 1024, MEM_COMMIT, PAGE_READWRITE);

	//alloc mem for params struct to pass
	LPVOID lpParams = VirtualAllocEx(hProcess, 0, sizeof(IPC_PARAMS), MEM_COMMIT, PAGE_READWRITE);

	//write the params into remote process
	if (!WriteProcessMemory(hProcess, lpParams, *pIPC, sizeof(IPC_PARAMS), &dwWR))
	{
		GetLastError();
		return 0;
	}

	HANDLE thread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)ExeMain, lpParams, 0, NULL);

	if (thread == NULL)
	{
		CHAR  stringa[255];
		CHAR lpMsgBuf[512];

		FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), 0, (LPSTR)lpMsgBuf, sizeof(lpMsgBuf), NULL);
	}

	return 1;
}


//read a dll from disk and map it into memory
static int loader_map_exe(HANDLE hProcess, PCHAR lpFileData, LPVOID *lpEntryPoint)
{
	FILE	*fp;
	size_t	size;
	DWORD   dwRet = 0;

	OutputDebugStringA("Reading file\n");

	LPDWORD		pex[200];
	LONGLONG	lpAddressRet = NULL;
	DWORD		sizeAlloc = NULL;
	SIZE_T		dwWR;

	IMAGE_DOS_HEADER	DosHeader;
	IMAGE_NT_HEADERS	PEHeader;
	DWORD header_size = 0;
	DWORD size_myFunc = 0;

	//dos header
	memcpy(&DosHeader, lpFileData, sizeof(DosHeader));
	if (DosHeader.e_magic != IMAGE_DOS_SIGNATURE)
		return NULL;

	//pe header
	memcpy(&PEHeader, CALC_OFFSET(LPVOID, lpFileData, DosHeader.e_lfanew), sizeof(PEHeader));
	if (PEHeader.Signature != IMAGE_NT_SIGNATURE)
		return NULL;

	LPVOID lpProcMem = NULL, lpRemoteProcMem = NULL;

	//alloc memory (for local and remote process) where to map the exe
	if ((dwRet = loader_alloc_exe_mem(hProcess, &lpProcMem, &lpRemoteProcMem, PEHeader.OptionalHeader.SizeOfImage)) == 0)
		return 0;

	//copy file sections to mem
	loader_copy_sections(lpProcMem, lpFileData, &DosHeader, &PEHeader);

	//exe relocation
	loader_reloc_exe(lpProcMem, &PEHeader);

	//build IAT
	loader_build_iat(&PEHeader, lpProcMem);

	//exports
	LPDWORD lpExport = NULL;
	loader_build_exports(lpProcMem, &lpExport);

	//copy the mapped exe to remote proc memory
	WriteProcessMemory(hProcess, lpRemoteProcMem, lpProcMem, PEHeader.OptionalHeader.SizeOfImage, NULL);

	//protect the sections	
	loader_protect_sections(hProcess, lpRemoteProcMem, &DosHeader, &PEHeader);

	//address of entry point
	*lpEntryPoint = CALC_OFFSET(LPVOID, lpRemoteProcMem, PEHeader.OptionalHeader.AddressOfEntryPoint);

	return 1;
}


//try to alloc memory at the same address for current and remote process
static int loader_alloc_exe_mem(HANDLE hProcess, LPVOID *lpProcMem, LPVOID *lpRemoteProcMem, DWORD dwMemSize)
{
	BOOL fAddress = FALSE;

	//try 10 times
	for (int i = 0; i < 100; i++)
	{
		*lpRemoteProcMem = VirtualAllocEx(hProcess, NULL, dwMemSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
		*lpProcMem = VirtualAllocEx(GetCurrentProcess(), *lpRemoteProcMem, dwMemSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

		if (*lpRemoteProcMem == *lpProcMem)
			return 1;

		//dealloc mem
		if (*lpRemoteProcMem)
			VirtualFreeEx(hProcess, *lpRemoteProcMem, dwMemSize, MEM_RELEASE);
		if (*lpProcMem)
			VirtualFreeEx(hProcess, *lpRemoteProcMem, dwMemSize, MEM_RELEASE);
	}

	return 0;
}

//copy sections from file to the current process memory
static int loader_copy_sections(LPVOID lpProcMem, LPVOID lpFileData, IMAGE_DOS_HEADER *pDos, IMAGE_NT_HEADERS *pPE)
{
	DWORD dwHeaderSize = 0;

	dwHeaderSize = pDos->e_lfanew + pPE->FileHeader.SizeOfOptionalHeader + sizeof(pPE->FileHeader) + 4;

	IMAGE_SECTION_HEADER section;
	LPVOID lpBufferPtr = CALC_OFFSET(LPVOID, lpFileData, dwHeaderSize);
	memcpy(&section, lpBufferPtr, sizeof(section));
	memcpy(lpProcMem, lpFileData, section.PointerToRawData);

	PIMAGE_SECTION_HEADER sections = CALC_OFFSET(PIMAGE_SECTION_HEADER, lpProcMem, dwHeaderSize);
	for (USHORT i = 0; i < pPE->FileHeader.NumberOfSections; i++, sections++)
	{
		LPVOID lpSectionBuffer = CALC_OFFSET(LPVOID, lpProcMem, sections->VirtualAddress);
		memcpy(lpSectionBuffer, CALC_OFFSET(LPVOID, lpFileData, sections->PointerToRawData), min(sections->SizeOfRawData, sections->Misc.VirtualSize));
	}

	return 1;
}

//reloc exe
static int loader_reloc_exe(LPVOID lpProcMem, PIMAGE_NT_HEADERS pPE)
{
	size_t deltaMem = (SIZE_T)((UCHAR*)lpProcMem - pPE->OptionalHeader.ImageBase);
	if (deltaMem == 0)
		return 0;

	PIMAGE_BASE_RELOCATION relocation;
	PIMAGE_DATA_DIRECTORY  directory = &pPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

	if (directory->Size == 0) {
		return (deltaMem == 0);
	}

	size_t sizeOfBaseReloc = sizeof(IMAGE_BASE_RELOCATION);

	relocation = (PIMAGE_BASE_RELOCATION)((UCHAR*)lpProcMem + directory->VirtualAddress);
	for (; relocation->VirtualAddress > 0;) {
		DWORD i;
		unsigned char *dest = (UCHAR*)lpProcMem + relocation->VirtualAddress;
		unsigned short *relInfo = (unsigned short *)((unsigned char *)relocation + sizeOfBaseReloc);
		for (i = 0; i<((relocation->SizeOfBlock - sizeOfBaseReloc) / 2); i++, relInfo++) {
			DWORD *patchAddrHL;
#ifdef _WIN64
			ULONGLONG *patchAddr64;
#endif
			int type, offset;

			// the upper 4 bits define the type of relocation
			type = *relInfo >> 12;
			// the lower 12 bits define the offset
			offset = *relInfo & 0xfff;

			switch (type)
			{
			case IMAGE_REL_BASED_ABSOLUTE:
				// skip relocation
				break;

			case IMAGE_REL_BASED_HIGHLOW:
				// change complete 32 bit address
				patchAddrHL = (DWORD *)(dest + offset);
				*patchAddrHL += (DWORD)deltaMem;
				break;

#ifdef _WIN64
			case IMAGE_REL_BASED_DIR64:
				patchAddr64 = (ULONGLONG *)(dest + offset);
				*patchAddr64 += (ULONGLONG)deltaMem;
				break;
#endif

			default:
				//printf("Unknown relocation: %d\n", type);
				break;
			}
		}

		// advance to next relocation block
		relocation = (PIMAGE_BASE_RELOCATION)(((char *)relocation) + relocation->SizeOfBlock);
	}

	return TRUE;
}


static BOOL loader_build_iat(PIMAGE_NT_HEADERS peHeader, LPVOID lpBaseAddr)
{
	unsigned char *codeBase = (UCHAR *)lpBaseAddr;
	PIMAGE_IMPORT_DESCRIPTOR importDesc;
	BOOL result = TRUE;
	WCHAR lpModName[512];

	PIMAGE_DATA_DIRECTORY directory = &peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
	if (directory->Size == 0) {
		return TRUE;
	}

	importDesc = (PIMAGE_IMPORT_DESCRIPTOR)(codeBase + directory->VirtualAddress);
	for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) {
		uintptr_t *thunkRef;
		FARPROC *funcRef;

		swprintf_s(lpModName, sizeof(lpModName) / 2, L"%hs", (codeBase + importDesc->Name));
		HMODULE hMod = LoadLibraryExW(lpModName, NULL, 0);
		if (hMod == NULL)
			return FALSE;

		if (importDesc->OriginalFirstThunk) {
			thunkRef = (uintptr_t *)(codeBase + importDesc->OriginalFirstThunk);
			funcRef = (FARPROC *)(codeBase + importDesc->FirstThunk);
		}
		else {
			// no hint table
			thunkRef = (uintptr_t *)(codeBase + importDesc->FirstThunk);
			funcRef = (FARPROC *)(codeBase + importDesc->FirstThunk);
		}
		for (; *thunkRef; thunkRef++, funcRef++) {
			if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) {
				*funcRef = GetProcAddress(hMod, (LPCSTR)IMAGE_ORDINAL(*thunkRef));
			}
			else {
				PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)(codeBase + (*thunkRef));
				*funcRef = GetProcAddress(hMod, (LPCSTR)&thunkData->Name);
			}
			if (*funcRef == 0) {
				result = FALSE;
				break;
			}
		}

		if (!result) {
			FreeLibrary(hMod);
			break;
		}
	}

	return result;
}


//build the export table
static int loader_build_exports(LPVOID lpProcMem, LPDWORD *lpExport)
{
	HMODULE hModule = (HMODULE)lpProcMem;

	PIMAGE_DOS_HEADER		pDos = (PIMAGE_DOS_HEADER)hModule;
	PIMAGE_NT_HEADERS		pPE = CALC_OFFSET(PIMAGE_NT_HEADERS, hModule, pDos->e_lfanew);
	PIMAGE_DATA_DIRECTORY	pExportDir = &pPE->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];

	if (pExportDir->Size != 0 && pExportDir->VirtualAddress != 0)
	{
		PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hModule + pExportDir->VirtualAddress);

		pExportDirectory->AddressOfNames += (DWORD)hModule;
		pExportDirectory->AddressOfFunctions += (DWORD)hModule;
		pExportDirectory->AddressOfNameOrdinals += (DWORD)hModule;

		LPDWORD ptrFunctions = (LPDWORD)pExportDirectory->AddressOfFunctions;
		LPDWORD ptrNames = (LPDWORD)pExportDirectory->AddressOfNames;

		for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
		{
			ptrFunctions[i] += (DWORD)hModule;
			ptrNames[i] += (DWORD)hModule;

			*lpExport = (LPDWORD)ptrFunctions[i];
		}
	}

	return 1;
}


static int loader_protect_sections(HANDLE hProcess, LPVOID lpRemoteMem, IMAGE_DOS_HEADER *pDos, IMAGE_NT_HEADERS *pPE)
{
	DWORD dwTmp, dwRet = 0, dwHeaderSize = 0;
	PIMAGE_SECTION_HEADER pSections = NULL;

	dwHeaderSize = pDos->e_lfanew + pPE->FileHeader.SizeOfOptionalHeader + sizeof(pPE->FileHeader) + 4;
	pSections = CALC_OFFSET(PIMAGE_SECTION_HEADER, lpRemoteMem, dwHeaderSize);

	for (USHORT i = 0; i < pPE->FileHeader.NumberOfSections; i++, pSections++)
	{
		LPVOID lpSectionBuffer = CALC_OFFSET(LPVOID, lpRemoteMem, pSections->VirtualAddress);
		if ((pSections->Characteristics & IMAGE_SCN_MEM_EXECUTE) == IMAGE_SCN_MEM_EXECUTE)
		{
			dwRet = VirtualProtectEx(hProcess, lpSectionBuffer, pSections->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &dwTmp);
		}
	}

	dwRet = VirtualProtectEx(hProcess, lpRemoteMem, dwHeaderSize, PAGE_READONLY, &dwTmp);

	return 1;
}
