IOTTMCO

Intuitively Obvious to the Most Casual Observer

Some kernel hacking: dumping physical memory

In the good old days, /dev/mem provided direct read/write access to physical memory on linux. This was a fun toy, but also enormously helpful for writers of rootkits, so it’s since been restricted by default (CONFIG_STRICT_DEVMEM) to all but a few addresses needed for device drivers. There are very few legitimate purposes for needing to access the rest of RAM directly, (forensics is one), but goofing around is important too. Rather than re-compile a kernel from source (which can get somewhat unwieldy, especially if there’s no compelling reason to do so), I decided to just put the necessary code into a kernel module of its own.

About half of the code was trivial boilerplate - if you’re curious, look a TLDP’s tutorial. The initialization and cleanup routines were just tasked with registering a new character device (rmem) to major number 250:

static int __init init(void) {
	printk(KERN_INFO DEVICE_NAME ": loading\n");
	if(register_chrdev(DEV_MAJOR, DEVICE_NAME, &fops)) {
		printk(KERN_ALERT "Registering char device failed\n");
		return -1;
	}
	return 0;
}

static void __exit cleanup(void) {
	printk(KERN_INFO DEVICE_NAME ": exiting\n");
	unregister_chrdev(DEV_MAJOR, DEVICE_NAME);
}

The file_operations struct is simple enough.

static struct file_operations fops = {
	.read = device_read,
};

I wasn’t interested in supporting any file operations other than read, since all I was looking to do was poke around in unallocated memory and see what’s left over.

I’m not sure that the implementation for device_read below is entirely cross-platform. I used the actual kernel code for /dev/mem as a guide. The arguments are: filp, a pointer to the file being read from (we can ignore it); buf, a pointer to the destination buffer; len, the number of bytes requested, and off, a pointer to the requested offset (which we’ll change on writing).

static ssize_t device_read(struct file *filp, char *buf, size_t len, loff_t *off) {
	ssize_t read = 0, sz;
	char *ptr;
	while (len) {
		sz = min(PAGE_SIZE - ((unsigned long)*off & (PAGE_SIZE - 1)), len);

The __va macro converts the given number to a virtual address, which we’ll then use as a pointer to read from.

		ptr = __va(*off);
		if (!ptr) return -EFAULT;
		if (copy_to_user(buf, ptr, len)) return -EFAULT;
		read += sz;
		buf += sz;
		*off += sz;
		len -= sz;
	}

	return read;
}

The full source is available here.