// Checksum Tool
// By Bio (C) 2012

#define _VERSION_ "3.1"

#include "commonheaders.h"

// Return codes
#define RESULT_OK			0
#define RESULT_NOTFOUND		10
#define RESULT_READERROR	20
#define RESULT_NOTPE		30
#define RESULT_CORRUPTED	40
#define RESULT_INVALID		50
#define RESULT_NONE			100

#pragma comment(lib, "version.lib")

int debug = 0;

static void PatchResourcesDirectory(PIMAGE_RESOURCE_DIRECTORY pIRD, PBYTE pBase);

static void PatchResourceEntry(PIMAGE_RESOURCE_DIRECTORY_ENTRY pIRDE, PBYTE pBase)
{
	if (pIRDE->DataIsDirectory)
		PatchResourcesDirectory(PIMAGE_RESOURCE_DIRECTORY(pBase + pIRDE->OffsetToDirectory), pBase);
}

static void PatchResourcesDirectory(PIMAGE_RESOURCE_DIRECTORY pIRD, PBYTE pBase)
{
	UINT i;
	pIRD->TimeDateStamp = 0;

	PIMAGE_RESOURCE_DIRECTORY_ENTRY pIRDE = PIMAGE_RESOURCE_DIRECTORY_ENTRY(pIRD + 1);
	for (i = 0; i < pIRD->NumberOfNamedEntries; i++, pIRDE++)
		PatchResourceEntry(pIRDE, pBase);

	for (i = 0; i < pIRD->NumberOfIdEntries; i++, pIRDE++)
		PatchResourceEntry(pIRDE, pBase);
}

__forceinline bool Contains(PIMAGE_SECTION_HEADER pISH, DWORD address, DWORD size = 0)
{
	return (address >= pISH->VirtualAddress && address + size <= pISH->VirtualAddress + pISH->SizeOfRawData);
}

int PEChecksum(wchar_t *filename, BYTE digest[16])
{
	HANDLE hFile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
		return RESULT_NOTFOUND;

	// check minimum and maximum size
	DWORD hsize = 0;
	DWORD filesize = GetFileSize(hFile, &hsize);
	if (!filesize || filesize == INVALID_FILE_SIZE || hsize) {
		CloseHandle(hFile);
		return RESULT_INVALID;
	}

	if (filesize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
		CloseHandle(hFile);
		return RESULT_NOTPE;
	}

	PBYTE ptr = nullptr;
	HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_WRITECOPY, 0, 0, NULL);
	if (hMap)
		ptr = (PBYTE)MapViewOfFile(hMap, FILE_MAP_COPY, 0, 0, 0);

	int res = RESULT_OK;
	if (ptr) {
		PIMAGE_DOS_HEADER pIDH = (PIMAGE_DOS_HEADER)ptr;

		PIMAGE_NT_HEADERS pINTH = nullptr;
		if (pIDH->e_magic == IMAGE_DOS_SIGNATURE)
			pINTH = (PIMAGE_NT_HEADERS)(ptr + pIDH->e_lfanew);

		if (!pINTH)
			res = RESULT_NOTPE;
		else if ((PBYTE)pINTH + sizeof(IMAGE_NT_HEADERS) >= ptr + filesize)
			res = RESULT_CORRUPTED;
		else if (pINTH->Signature != IMAGE_NT_SIGNATURE)
			res = RESULT_NOTPE;
		else {
			WORD machine = pINTH->FileHeader.Machine;

			if (debug) {
				switch (machine) {
				case IMAGE_FILE_MACHINE_I386:
					fwprintf(stderr, L"Build: x86\n");
					break;
				case IMAGE_FILE_MACHINE_AMD64:
					fwprintf(stderr, L"Build: x64\n");
					break;
				case IMAGE_FILE_MACHINE_IA64:
					fwprintf(stderr, L"Build: IA64 :-)\n");
					break;
				default:
					fwprintf(stderr, L"Build: unknown :-(\n");
					break;
				}
			}

			DWORD sections = pINTH->FileHeader.NumberOfSections;
			if (!sections)
				res = RESULT_INVALID;
			else {
				PIMAGE_DATA_DIRECTORY pIDD = 0;
				PIMAGE_DEBUG_DIRECTORY pDBG = 0;
				DWORD dbgSize = 0, dbgAddr = 0;    // debug information
				DWORD expSize = 0, expAddr = 0;    // export table
				DWORD resSize = 0, resAddr = 0;    // resource directory
				DWORD relocSize = 0, relocAddr = 0; // relocation table
				PBYTE pRealloc = nullptr;
				ULONGLONG base = 0;

				// try to found correct offset independent of architectures
				DWORD offset = pIDH->e_lfanew + pINTH->FileHeader.SizeOfOptionalHeader + sizeof(IMAGE_NT_HEADERS) - sizeof(IMAGE_OPTIONAL_HEADER);

				if ((machine == IMAGE_FILE_MACHINE_I386) &&
					(pINTH->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER32)) &&
					(pINTH->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC))
				{
					pIDD = (PIMAGE_DATA_DIRECTORY)((PBYTE)pINTH + offsetof(IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory));
					base = *(DWORD*)((PBYTE)pINTH + offsetof(IMAGE_NT_HEADERS32, OptionalHeader.ImageBase));
				}
				else if ((machine == IMAGE_FILE_MACHINE_AMD64) &&
					(pINTH->FileHeader.SizeOfOptionalHeader >= sizeof(IMAGE_OPTIONAL_HEADER64)) &&
					(pINTH->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC))
				{
					pIDD = (PIMAGE_DATA_DIRECTORY)((PBYTE)pINTH + offsetof(IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory));
					base = *(ULONGLONG*)((PBYTE)pINTH + offsetof(IMAGE_NT_HEADERS64, OptionalHeader.ImageBase));
				}
				else res = RESULT_CORRUPTED;

				if (debug)
					fwprintf(stderr, L"Image base is 0x%I64x \n", base);

				if (pIDD) {
					// Debugging information entry
					dbgAddr = pIDD[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
					dbgSize = pIDD[IMAGE_DIRECTORY_ENTRY_DEBUG].Size;

					// Export information entry
					expAddr = pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
					expSize = pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

					// Resource directory
					resAddr = pIDD[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress;
					resSize = pIDD[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size;

					// Reallocation information entry
					relocAddr = pIDD[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
					relocSize = pIDD[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
				}

				// verify image integrity
				for (DWORD idx = 0; idx < sections; idx++) {
					PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(ptr + offset + idx * sizeof(IMAGE_SECTION_HEADER));
					if (((PBYTE)pISH + sizeof(IMAGE_SECTION_HEADER) > ptr + filesize) || (pISH->PointerToRawData + pISH->SizeOfRawData > filesize)) {
						res = RESULT_CORRUPTED;
						break;
					}

					// erase timestamp
					if (dbgSize >= sizeof(IMAGE_DEBUG_DIRECTORY) && Contains(pISH, dbgAddr, dbgSize)) {
						DWORD shift = dbgAddr - pISH->VirtualAddress;
						pDBG = (PIMAGE_DEBUG_DIRECTORY)(ptr + shift + pISH->PointerToRawData);
						for (int i = dbgSize / sizeof(IMAGE_DEBUG_DIRECTORY); i > 0; i--)
							pDBG[i-1].TimeDateStamp = 0;

						if (debug)
							fwprintf(stderr, L"Found debug section entry at 0x%08X (%d), data at 0x%08X (%d)\n", pISH->PointerToRawData + shift, dbgSize, pDBG->PointerToRawData, pDBG->SizeOfData);
					}

					// erase export timestamp
					if (expSize >= sizeof(IMAGE_EXPORT_DIRECTORY) && Contains(pISH, expAddr, expSize)) {
						DWORD shift = expAddr - pISH->VirtualAddress;
						PIMAGE_EXPORT_DIRECTORY pEXP = (PIMAGE_EXPORT_DIRECTORY)(ptr + shift + pISH->PointerToRawData);
						pEXP->TimeDateStamp = 0;

						if (debug)
							fwprintf(stderr, L"Found export section entry at 0x%08X\n", pISH->PointerToRawData + shift);
					}

					// find realocation table
					if ((relocSize >= sizeof(IMAGE_BASE_RELOCATION)) && Contains(pISH, relocAddr, relocSize)) {
						DWORD shift = relocAddr - pISH->VirtualAddress;
						pRealloc = ptr + shift + pISH->PointerToRawData;

						if (debug)
							fwprintf(stderr, L"Found reallocation table entry at 0x%08X (%d)\n", pISH->PointerToRawData + shift, relocSize);
					}
				}

				if (res == RESULT_OK) {
					mir_md5_state_t pms;
					mir_md5_init(&pms);

					for (size_t idx = 0; idx < sections; idx++) {
						PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION_HEADER)(ptr + offset + idx * sizeof(IMAGE_SECTION_HEADER));

						if (((PBYTE)pISH + sizeof(IMAGE_SECTION_HEADER) > ptr + filesize) || (pISH->PointerToRawData + pISH->SizeOfRawData > filesize)) {
							res = RESULT_CORRUPTED;
							break;
						}

						// erase debug information
						if (pDBG && pDBG->SizeOfData > 0)
							if (pDBG->PointerToRawData >= pISH->PointerToRawData && pDBG->PointerToRawData + pDBG->SizeOfData <= pISH->PointerToRawData + pISH->SizeOfRawData)
								ZeroMemory(ptr + pDBG->PointerToRawData, pDBG->SizeOfData);

						// patch resources
						if (resSize > 0 && resAddr >= pISH->VirtualAddress && resAddr + resSize <= pISH->VirtualAddress + pISH->SizeOfRawData) {
							DWORD shift = resAddr - pISH->VirtualAddress + pISH->PointerToRawData;
							IMAGE_RESOURCE_DIRECTORY *pIRD = (IMAGE_RESOURCE_DIRECTORY*)(ptr + shift);
							PatchResourcesDirectory(pIRD, ptr + shift);
						}

						// rebase to zero address
						if (pRealloc) {
							DWORD blocklen = relocSize;
							PIMAGE_BASE_RELOCATION pIBR = (PIMAGE_BASE_RELOCATION)pRealloc;
							while (pIBR) {
								if (Contains(pISH, pIBR->VirtualAddress) && pIBR->SizeOfBlock <= blocklen) {
									DWORD shift = pIBR->VirtualAddress - pISH->VirtualAddress + pISH->PointerToRawData;

									int len = pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION);

									PWORD pw = (PWORD)((PBYTE)pIBR + sizeof(IMAGE_BASE_RELOCATION));
									if (debug)
										fwprintf(stderr, L"Realloc block at %08X (%d)\n", pIBR->VirtualAddress, pIBR->SizeOfBlock);

									while (len > 0) {
										DWORD type = *pw >> 12;
										DWORD addr = (*pw & 0x0FFF);
										PBYTE pAddr = ptr + shift + addr;

										switch (type) {
										case IMAGE_REL_BASED_HIGHLOW:
											if (addr + pIBR->VirtualAddress + sizeof(DWORD) >= pISH->VirtualAddress + pISH->SizeOfRawData) {
												len = 0;
												break;
											}

											if (debug && (*(PDWORD)pAddr < (DWORD)base))
												fwprintf(stderr, L"Realloc address is less than base\n");

											*(PDWORD)pAddr = (DWORD)((*(PDWORD)pAddr) - (DWORD)base);
											break;

										case IMAGE_REL_BASED_DIR64:
											if (addr + pIBR->VirtualAddress + sizeof(ULONGLONG) >= pISH->VirtualAddress + pISH->SizeOfRawData) {
												len = 0;
												break;
											}

											if (debug && (*(ULONGLONG*)pAddr < base))
												fwprintf(stderr, L"Realloc address is less than base\n");

											*(ULONGLONG*)pAddr = (ULONGLONG)((*(ULONGLONG*)pAddr) - base);
											break;

										case IMAGE_REL_BASED_ABSOLUTE:
											// stop processing
											len = 0;
											break;

										case IMAGE_REL_BASED_HIGH:
										case IMAGE_REL_BASED_LOW:
										case IMAGE_REL_BASED_HIGHADJ:
											if (debug)
												fwprintf(stderr, L"Unexpected block type %d\n", type);
											break;

										default:
											if (debug)
												fwprintf(stderr, L"Unknown block type %d\n", type);
											break;
										}

										len -= sizeof(WORD);
										pw++;
									}
								}

								blocklen -= pIBR->SizeOfBlock;
								if (blocklen > sizeof(IMAGE_BASE_RELOCATION))
									pIBR = (PIMAGE_BASE_RELOCATION)((PBYTE)pIBR + pIBR->SizeOfBlock);
								else
									break;
							}
						}

						if (debug) {
							BYTE digest2[16];
							mir_md5_state_t pms2;
							mir_md5_init(&pms2);
							mir_md5_append(&pms2, ptr + pISH->PointerToRawData, pISH->SizeOfRawData);
							mir_md5_finish(&pms2, digest2);

							fwprintf(stderr, L"%S - %08X - %d ", pISH->Name, pISH->PointerToRawData, pISH->SizeOfRawData);
							for (int i = 0; i < sizeof(digest2) / sizeof(digest2[0]); i++)
								fwprintf(stderr, L"%02X", digest2[i]);
							fwprintf(stderr, L"\n");
						}

						mir_md5_append(&pms, ptr + pISH->PointerToRawData, pISH->SizeOfRawData);
					}

					if (res == RESULT_OK)
						mir_md5_finish(&pms, digest);
				}
			}
		}
	}
	else res = RESULT_READERROR;

	if (ptr)
		UnmapViewOfFile(ptr);

	if (hMap)
		CloseHandle(hMap);

	CloseHandle(hFile);

	return res;
}

wchar_t* trtrim(wchar_t *str)
{
	if (str == NULL)
		return NULL;

	wchar_t* p = _tcschr(str, 0);
	while (--p >= str) {
		switch (*p) {
		case L' ': case L'\t': case L'\n': case L'\r':
			*p = 0; break;
		default:
			return str;
		}
	}
	return str;
}


int process(wchar_t *filename)
{
	BYTE digest[16] = { 0 };

	int res = PEChecksum(filename, digest);

	switch (res) {
	case RESULT_NOTFOUND:
		fwprintf(stderr, L"'%s'... not found!\n", filename);
		break;

	case RESULT_READERROR:
		fwprintf(stderr, L"'%s'... read error!\n", filename);
		break;

	case RESULT_NOTPE:
		fwprintf(stderr, L"'%s'... not PE type!\n", filename);
		break;

	case RESULT_CORRUPTED:
		fwprintf(stderr, L"'%s'... corrupted!\n", filename);
		break;

	case RESULT_OK:
		fwprintf(stdout, L"%s ", filename);
		for (int i = 0; i < sizeof(digest) / sizeof(digest[0]); i++)
			fwprintf(stdout, L"%02X", digest[i]);
		fwprintf(stdout, L"\n");
		break;

	default:
		break;
	}
	return res;
}


int _tmain(int argc, wchar_t *argv[])
{
	wchar_t buf[MAX_PATH];
	int res = 0;
	int cnt = 0;
	int i;

	fwprintf(stderr, L"* PE CHECKSUM TOOL * VERSION %S * by Bio (c) 2012\n\n", _VERSION_);

	if (argc > 1) {
		WIN32_FIND_DATA ffd;
		HANDLE hFind = INVALID_HANDLE_VALUE;

		for (i = 1; i < argc; i++) {
			if (!wcscmp(argv[i], L"/debug") || !wcscmp(argv[i], L"/DEBUG")) {
				debug = 1;
				break;
			}
		}

		fwprintf(stderr, L"Processing ... \n");

		for (i = 1; i < argc; i++) {
			if (!wcscmp(argv[i], L"/stdin") || !wcscmp(argv[i], L"/STDIN")) {
				while (fgetws(buf, sizeof(buf), stdin) != NULL) {
					trtrim(buf);
					res = process(buf);
					cnt++;
				}
				continue;
			}

			wchar_t *p = wcsrchr(argv[i], '\\');
			if (p) {
				*p = 0;
				SetCurrentDirectory(argv[i]);
				*p = '\\';
			}

			hFind = FindFirstFile(argv[i], &ffd);

			while (hFind != INVALID_HANDLE_VALUE) {
				if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
					res = process(ffd.cFileName);
					cnt++;
				}
				if (!FindNextFile(hFind, &ffd))
					break;
			}

			FindClose(hFind);
		}

		fwprintf(stderr, L"%d file(s) processed.\n", cnt);
	}
	else {
		fwprintf(stderr, L"Usage:    checksum.exe [/debug] [/stdin] [*.dll] ... [*.exe]\n");
		fwprintf(stderr, L"Example:  dir /b /s | checksum.exe /stdin > hashes.txt\n");
		res = RESULT_NONE;
	}

	return res;
}