published: Sun, 14 Apr 2019 14:27:36 +0100

The /dev/kmem device allows access to kernel memory through a character device. It is not loaded by default and is only built when CONFIG_DEV_KMEM is set. The kernel on macOS 10.14.4 (18E226) has this enabled on all builds of the kernel (release, development and debug).

Enabling /dev/kmem can be done in the 'vanilla' way by setting kmem=1 in the boot arguments, or there is a global variable in the kernel--dev_kmem_enabled which can be set to TRUE at runtime. Setting this variable won't create the device so you need to created it manually.

node = devfs_make_node(makedev(3, 1), DEVFS_CHAR, UID_ROOT, GID_KMEM, 0640, "kmem");

3 being the major device number and 1 is the minor one. /dev/mem would use minor 1 but there isn't even code for it in the kernel at all so you'd need to implement it entirely yourself.

In order to find these private kernel symbols you'll need to read the Mach-O Symbol Table. You can bypass KASLR pretty easily from within a Kext using a nicely named public function vm_kernel_unslide_or_perm_external().

vm_offset_t slide_address = 0;
/* You can use other symbols than printf in KPI. See xnu/osfmk/vm/vm_kern.c */
vm_kernel_unslide_or_perm_external((vm_offset_t)printf, &slide_address);
vm_offset_t kernel_slide = (vm_offset_t)printf - slide_address;

The symbols dev_kmem_mask_top_bit will also need to be set to TRUE so that addresses can be passed in off_t variables with the MSB unset. The kernel will then translate this to an actual kernel VM address. For example if you passed in 0x7fffff8000200000 it would function the same as if you were able to pass in 0xffffff8000200000.

Anyway, as of xnu-4903.221.2 the kernel memory device still exists in the kernel, not that it's particularly useful being read-only and the user still has to figure out the kernel slide (though easily bruteforced)--but interesting nonetheless. Also check out xnu-4903.221.2/bsd/dev/mem.c for more infomation.