/*
 * uninformed research
 * -------------------
 *
 * elfcmp - Compare a binary image with a process image to verify
 *          that it has not been tampered with.
 *
 * skape
 * mmiller@hick.org
 * 01/19/2003
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <elf.h>
#include <link.h>

typedef struct _elfcmp_ctx {

	// Process side	
	int           pid;

	// File side
	int           fd;
	unsigned long base;
	char          image[1024];
	size_t        imageSize;
	Elf32_Ehdr    elfHeader;

} ELFCMP_CTX;

unsigned long elfcmpInitialize(ELFCMP_CTX *ctx);
unsigned char *elfcmpGetMemory(ELFCMP_CTX *ctx, unsigned long addr, unsigned long size);
unsigned long elfcmpDeinitialize(ELFCMP_CTX *ctx);

unsigned long elfcmpPhaseCode(ELFCMP_CTX *ctx);
unsigned long elfcmpPhaseReadonlyData(ELFCMP_CTX *ctx);
unsigned long elfcmpPhaseLibraries(ELFCMP_CTX *ctx);

struct _phases_st {
	const char *name;
	unsigned long (*func)(ELFCMP_CTX *ctx);
} phases[] = {
	{ "STATIC CODE",    elfcmpPhaseCode         },
	{ "READ-ONLY DATA", elfcmpPhaseReadonlyData },
	{ "LIBRARIES",      elfcmpPhaseLibraries    },
	{ NULL,             NULL                    },
};

int main(int argc, char **argv)
{
	unsigned long failure = 0, attempts = 0;
	ELFCMP_CTX ctx;
	int x;

	if (argc < 2)
	{
		fprintf(stdout, "Usage: %s pid [binary]\n", argv[0]);
		return 0;
	}

	memset(&ctx, 0, sizeof(ctx));

	ctx.pid = atoi(argv[1]) & 0xFFFF;

	if (argc == 3)
		strncpy(ctx.image, argv[2], sizeof(ctx.image) - 1);
	else
		snprintf(ctx.image, sizeof(ctx.image) - 1, "/proc/%d/exe", ctx.pid);

	if (!elfcmpInitialize(&ctx))
	{
		elfcmpDeinitialize(&ctx);

		fprintf(stdout, "elfcmp: failed to initialize.\n");
		return 0;
	}

	fprintf(stdout, "Comparing process %d to binary '%s'...\n\n", ctx.pid, ctx.image);

	for (x = 0; phases[x].name ; x++)
	{
		fprintf(stdout, "Phase %d... (%s)\n", x, phases[x].name);

		if (!phases[x].func(&ctx))
			fprintf(stdout, "Phase %d failed.\n\n", x), failure++;
		else
			fprintf(stdout, "Phase %d succeeded.\n\n", x);

		attempts++;
	}

	fprintf(stdout, "%lu phases tested, %lu phases passed.  (%s)\n", attempts, attempts - failure, (!failure)?"OK":"FAIL");

	elfcmpDeinitialize(&ctx);

	return 1;
}

unsigned long elfcmpInitialize(ELFCMP_CTX *ctx)
{
	struct stat statbuf;
	int status = 0;

	// Ptrace
	
	if (ptrace(PTRACE_ATTACH, ctx->pid, 0, 0) == -1)
		return 0;

	while (1) 
	{
		wait(&status);

		if (WIFSTOPPED(status))
			break;
	}

	// File
	
	if (!(ctx->fd = open(ctx->image, O_RDONLY)))
		return 0;

	if (fstat(ctx->fd, &statbuf) != 0)
		return 0;

	if (!(ctx->base = (unsigned long)mmap(NULL, (ctx->imageSize = statbuf.st_size), PROT_READ, MAP_PRIVATE, ctx->fd, 0)))
		return 0;

	// Copy the elf header for easy access
	memcpy(&ctx->elfHeader, (void *)ctx->base, sizeof(Elf32_Ehdr));

	return 1;
}

unsigned char *elfcmpGetMemory(ELFCMP_CTX *ctx, unsigned long addr, unsigned long size)
{
	unsigned char *ret = (unsigned char *)malloc(size);
	unsigned long x = addr, end = addr + size, data, left = size, y, retPos = 0;

	if (!ret)
		return NULL;

	memset(ret, 0, size);

	for (; x < end; x += 4)
	{
		// If the first address is non-readable, break out and return null
		if (((data = ptrace(PTRACE_PEEKDATA, ctx->pid, x, 0)) == -1) && (errno == 5))
		{
			if (x == addr)
			{
				free(ret);

				ret = NULL;

				break;
			}
		}

		for (y = 0; y < sizeof(unsigned long) && left != 0; y++)
		{
			ret[retPos++] = ((unsigned char *)&data)[y];
			left--;
		}
	}

	return ret;
}

unsigned long elfcmpDeinitialize(ELFCMP_CTX *ctx)
{
	// Ptrace
	
	ptrace(PTRACE_DETACH, ctx->pid, 0, 0);

	// File

	if (ctx->base)
		munmap((void *)ctx->base, ctx->imageSize);
	if (ctx->fd)
		close(ctx->fd);
	
	return 1;
}

unsigned long elfcmpPhaseCode(ELFCMP_CTX *ctx)
{
	unsigned long ret = 1, x = 0;
	Elf32_Shdr *sections = (Elf32_Shdr *)((ctx->base + ctx->elfHeader.e_shoff));
	unsigned char *strings = (unsigned char *)((ctx->base + sections[ctx->elfHeader.e_shstrndx].sh_offset)), *fileCode = NULL, *processCode = NULL;

	for (x = 0; x < ctx->elfHeader.e_shnum; x++)
	{
		// No progbits, no play.
		if (sections[x].sh_type != SHT_PROGBITS)
			continue;

		// We only want executable sections for now.
		if (!(sections[x].sh_flags & SHF_EXECINSTR))
			continue;

		fprintf(stdout, "\t%s...", strings + sections[x].sh_name);
		fflush(stdout);

		fileCode    = (unsigned char *)((ctx->base + sections[x].sh_offset));
		processCode = (unsigned char *)elfcmpGetMemory(ctx, sections[x].sh_addr, sections[x].sh_size);

		if ((!fileCode) || (!processCode))
		{
			fprintf(stdout, "MISSING.\n");
			ret = 0;
			continue;
		}

		if (memcmp(fileCode, processCode, sections[x].sh_size))
		{
			fprintf(stdout, "MISMATCHED\n");
			ret = 0;
		}
		else
			fprintf(stdout, "OK\n");

		free(processCode);
	}

	return ret;
}

unsigned long elfcmpPhaseReadonlyData(ELFCMP_CTX *ctx)
{
	unsigned long ret = 1, x = 0;
	Elf32_Shdr *sections = (Elf32_Shdr *)((ctx->base + ctx->elfHeader.e_shoff));
	unsigned char *strings = (unsigned char *)((ctx->base + sections[ctx->elfHeader.e_shstrndx].sh_offset)), *fileCode = NULL, *processCode = NULL;

	for (x = 0; x < ctx->elfHeader.e_shnum; x++)
	{
		// We want all read-only, non-executable, and allocated pages
		if ((sections[x].sh_flags & SHF_WRITE) ||
			 (sections[x].sh_flags & SHF_EXECINSTR) || 
			 (!((sections[x].sh_flags & (SHF_ALLOC)))))
			continue;

		fprintf(stdout, "\t%s...", strings + sections[x].sh_name);
		fflush(stdout);

		fileCode    = (unsigned char *)((ctx->base + sections[x].sh_offset));
		processCode = (unsigned char *)elfcmpGetMemory(ctx, sections[x].sh_addr, sections[x].sh_size);

		if ((!fileCode) || (!processCode))
		{
			fprintf(stdout, "MISSING.\n");
			ret = 0;
			continue;
		}

		if (memcmp(fileCode, processCode, sections[x].sh_size))
		{
			fprintf(stdout, "MISMATCHED\n");
			ret = 0;
		}
		else
			fprintf(stdout, "OK\n");

		free(processCode);
	}

	return ret;
}

unsigned long elfcmpPhaseLibraries(ELFCMP_CTX *ctx)
{
	Elf32_Phdr *programs = (Elf32_Phdr *)((ctx->base + ctx->elfHeader.e_phoff));
	unsigned long interpBase = 0, dl_rtld_map = 0, interpVirtualBase = 0;
	struct link_map *curr = NULL, *localMap = NULL;
	unsigned long ret = 1, x = 0;
	const char *interp = NULL;
	struct stat statbuf;
	FILE *ldd = NULL;
	int fd;

	// Find the interpreter
	for (x = 0; x < ctx->elfHeader.e_phnum; x++)
	{
		if (programs[x].p_type != PT_INTERP)
			continue;

		interp = (const char *)((ctx->base + programs[x].p_offset));
	}

	// Load it
	if (!interp || ((fd = open(interp, O_RDONLY)) <= 0))
	{
		fprintf(stdout, "\tWARNING: Image has no interpreter.\n");
		return 0;
	}

	// Locate the _dl_rtld_map symbol.
	do
	{
		Elf32_Ehdr *interpEhdr     = NULL;
		Elf32_Shdr *interpSections = NULL;
		unsigned long currentSymbol, numSymbols;

		if (fstat(fd, &statbuf) != 0)
			break;

		if (!(interpBase = (unsigned long)mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)))
			break;

		interpEhdr     = (Elf32_Ehdr *)((interpBase));
		interpSections = (Elf32_Shdr *)((interpBase + interpEhdr->e_shoff));

		for (x = 0; !dl_rtld_map && x < interpEhdr->e_shnum; x++)
		{
			Elf32_Sym  *sym = NULL;
			const char *symStrings = NULL;

			if (interpSections[x].sh_type != SHT_SYMTAB)
				continue;

			numSymbols = interpSections[x].sh_size / interpSections[x].sh_entsize;
			sym        = (Elf32_Sym *)((interpBase + interpSections[x].sh_offset));
			symStrings = (const char *)((interpBase + interpSections[interpSections[x].sh_link].sh_offset));

			for (currentSymbol = 0; !dl_rtld_map && currentSymbol < numSymbols; currentSymbol++)
			{
				if (!strcmp(symStrings + sym[currentSymbol].st_name, "_dl_rtld_map"))
					dl_rtld_map = sym[currentSymbol].st_value;
			}
		}

	} while (0);

	if (interpBase)
		munmap((void *)interpBase, statbuf.st_size);

	close(fd);

	if (!dl_rtld_map)
	{
		fprintf(stdout, "\tWARNING: Interpreter has no _dl_rtld_map symbol.\n");
		return 0;
	}

	// Now we need to know where the interpreter is loaded at.
	
	do
	{
		char cmd[1024];

		cmd[sizeof(cmd) - 1] = cmd[0] = 0;

		snprintf(cmd, sizeof(cmd) - 1, "ldd %s", ctx->image);

		if (!(ldd = popen(cmd, "r")))
			break;

		while (!interpVirtualBase && fgets(cmd, sizeof(cmd) - 1, ldd))
		{
			unsigned long addr = 0;
			char lib[1024], garbage[1024];

			lib[sizeof(lib) - 1] = garbage[sizeof(garbage) - 1] = 0;

			cmd[strlen(cmd) - 1] = 0;
			
			sscanf(cmd, " %1023s => %1023s (%x)", garbage, lib, (unsigned int *)&addr);
		
			if (!strstr(lib, interp))
				continue;

			interpVirtualBase = addr;
		}

	} while (0);

	if (ldd)
		pclose(ldd);

	if (!interpVirtualBase)
	{
		fprintf(stdout, "\tWARNING: Executable doesn't NEED interpreter.\n");
		return 0;
	}

	// Okay, finally, we have the vma of the rtld_map.  Now we can walk it.

	do
	{
		unsigned long currentLink = interpVirtualBase + dl_rtld_map;
		char *name = NULL;

		do
		{
			curr = (struct link_map *)elfcmpGetMemory(ctx, currentLink, sizeof(struct link_map));
			name = NULL;

			if (curr->l_name)
				name = (char *)elfcmpGetMemory(ctx, (unsigned long)curr->l_name, 1024);

			currentLink  = (unsigned long)curr->l_prev;

			// Change the addy of the name to 
			curr->l_name = name;

			// Add it to our local copy
			if (localMap)
			{
				curr->l_next     = localMap;
				localMap->l_prev = curr;
			}
			else
				curr->l_next = NULL;

			localMap = curr;

		} while (currentLink);

	} while (0);

	// Enumerate linked libraries, comparing their file system images with the mapped images.
	for (curr = localMap; curr; curr = curr->l_next)
	{
		Elf32_Ehdr *libraryHeader = NULL;
		Elf32_Shdr *librarySections = NULL;
		const char *libraryStrings = NULL;
		unsigned char *libMemory, *procMemory;
		unsigned long libBase = 0;
		struct stat libstat;
		int libfd = 0;

		if (!curr->l_name || !curr->l_name[0])
			continue;

		do
		{
			if ((libfd = open(curr->l_name, O_RDONLY)) <= 0)
			{
				fprintf(stdout, "\tWARNING: Could not load linked library '%s'.\n", curr->l_name);
				break;
			}
	
			if (fstat(libfd, &libstat) != 0)
			{
				fprintf(stdout, "\tWARNING: Could not load linked library '%s'.\n", curr->l_name);
				break;
			}

			libBase = (unsigned long)mmap(NULL, libstat.st_size, PROT_READ, MAP_PRIVATE, libfd, 0);
			libraryHeader   = (Elf32_Ehdr *)((libBase));	

			if (!libraryHeader)
				break;
			
			// Cool, we've got the library mapped.  Now we must compare symbols/data.
			librarySections = (Elf32_Shdr *)((libBase + libraryHeader->e_shoff));
			libraryStrings  = (const char *)((libBase + librarySections[libraryHeader->e_shstrndx].sh_offset));

			for (x = 0; x < libraryHeader->e_shnum; x++)
			{
				unsigned long passed = 0;
			
				// Executable?
				if (librarySections[x].sh_flags & SHF_EXECINSTR)
					passed = 1;

				// Read only data?
				if (!passed && (!(librarySections[x].sh_flags & SHF_WRITE) && (librarySections[x].sh_flags & SHF_ALLOC)))
					passed = 1;

				if (!passed)
					continue;

				libMemory  = (unsigned char *)((libBase + librarySections[x].sh_offset));
				procMemory = (unsigned char *)elfcmpGetMemory(ctx, curr->l_addr + librarySections[x].sh_addr, librarySections[x].sh_size);

				// It matched, all is well.
				if (procMemory && !memcmp(libMemory, procMemory, librarySections[x].sh_size))
				{
					free(procMemory);
					continue;
				}

				// Well fuck me sideways, a section doesn't match.  Good heavens.  
				// Maybe we can find out what symbol was modified?
				fprintf(stdout, "\tWARNING: Section '%s' in library '%s' mismatches!\n",
									 libraryStrings + librarySections[x].sh_name,
									 curr->l_name);

				if (librarySections[x].sh_flags & SHF_EXECINSTR)
				{
					unsigned long y, end = librarySections[x].sh_addr + librarySections[x].sh_size;
					unsigned long currentSymbol, numSymbols;
					unsigned char *fileSymData = NULL, *procSymData = NULL;
					const char *symStrings;
					Elf32_Sym *sym = NULL;

					for (y = 0; y < libraryHeader->e_shnum; y++)
					{
						// We only want symbols.
						if (librarySections[y].sh_type != SHT_DYNSYM)
							continue;

						sym        = (Elf32_Sym *)((libBase + librarySections[y].sh_offset));
						symStrings = (const char *)((libBase + librarySections[librarySections[y].sh_link].sh_offset));	
						numSymbols = librarySections[y].sh_size / librarySections[y].sh_entsize;

						for (currentSymbol = 0; currentSymbol < numSymbols; currentSymbol++)
						{
							if ((sym[currentSymbol].st_value >= librarySections[x].sh_addr) &&
								 (sym[currentSymbol].st_value <= end))
							{
								fileSymData = (unsigned char *)((libBase + librarySections[x].sh_addr + (sym[currentSymbol].st_value - librarySections[x].sh_addr)));
								procSymData = (unsigned char *)elfcmpGetMemory(ctx, curr->l_addr + sym[currentSymbol].st_value, sym[currentSymbol].st_size);


								if (procSymData && memcmp(fileSymData, procSymData, sym[currentSymbol].st_size))
									fprintf(stdout, "\t\tSymbol '%s' mismatches.\n", symStrings + sym[currentSymbol].st_name);

								if (procSymData)
									free(procSymData);
							}
						}
					}
				}

				ret = 0;

				if (procMemory)
					free(procMemory);
			}

		} while (0);

		if (libBase)
			munmap((void *)libBase, libstat.st_size);
		if (libfd)
			close(libfd);
	}

	// Flush map linked list
	while (localMap)
	{
		curr = localMap->l_next;

		if (localMap->l_name)
			free(localMap->l_name);

		free(localMap);

		localMap = curr;
	}

	return ret;
}

