This project suggestion is from Jonathan Zdziarski, related to his guest lecture [https://youtu.be/lk8xoTzOES0]. You can find his slides and sample code for a kernel extension in http://www.cs.dartmouth.edu/~sergey/cs258/guest_lectures/Crafting-macOS-Root-Kits.pdf, http://www.cs.dartmouth.edu/~sergey/cs258/guest_lectures/littlerooter.zip --------------------------------------------------------------------------- I recently incorporated the KernelResolver sample code into my kernel module in order to use csproc_get_teamid, which is a private kpi function that does exactly what it says: it retrieves the codesign team identifier when passed a proc_t structure. The resolver worked excellent... in Sierra. I then loaded my module into El Capitan, and it blew completely up. What could have changed, I asked? The kernel base address used by the resolver hasn't been updated in years, if ever, nor has the mach-o format. Let's review the code real quick: int64_t slide = 0; vm_offset_t slide_address = 0; vm_kernel_unslide_or_perm_external(KERNEL_BASE, &slide_address); slide = KERNEL_BASE - slide_address; int64_t base_address = slide + KERNEL_BASE; DLOG("%s: aslr slide: %lld\n", __func__, slide); DLOG("%s: base address: %lld\n", __func__, base_address); This first chunk of code was the first I'd tested, and placed a return statement after, so that I could first check and ensure that the very foundation of the resolver code (namely, the slide value and the subsequent kernel base in memory) was correct. I loaded this module up and it ran just fine, dumping the aslr slide and base to debug, where I picked it up with a call to dmesg. Odd - the value for the slide was ridiculously huge. Far more than any aslr slide would have been, and of course the base was miscalculated with this value. No wonder it blew up completely on El Cap. As you can see frmo the code, the slide is calculated based on a value returned from vm_kernel_unslide_or_perm_external. This function is part of the vm susbystem in the xnu kernel... since we're grossly miscalculating the slide, the first thing we ought to do is sanity check the data we're getting back from this function. It's a private kpi function, so has it changed between El Cap and Sierra? Let's find out. The code for vm_kernel_unslide_or_perm_external is a small function and can be found in the osfmk/vm/vm_kern.c source file in the xnu kernel, available at http://opensource.apple.com. If you can't remember where it lives, simply cd into the xnu source tree and: grep -r unslide . Here's the Sierra version of the code: 1478 vm_kernel_unslide_or_perm_external( 1479 vm_offset_t addr, 1480 vm_offset_t *up_addr) 1481 { 1482 if (VM_KERNEL_IS_SLID(addr)) { 1483 *up_addr = addr - vm_kernel_slide; 1484 return; 1485 } 1486 1487 vm_kernel_addrperm_external(addr, up_addr); 1488 return; 1489 } Now let's compare that to the El Capitan code that predated it: 1443 vm_kernel_unslide_or_perm_external( 1444 vm_offset_t addr, 1445 vm_offset_t *up_addr) 1446 { 1447 if (VM_KERNEL_IS_SLID(addr) || VM_KERNEL_IS_KEXT(addr) || 1448 VM_KERNEL_IS_PRELINKTEXT(addr) || VM_KERNEL_IS_PRELINKINFO(addr) || 1449 VM_KERNEL_IS_KEXT_LINKEDIT(addr)) { 1450 *up_addr = addr - vm_kernel_slide; 1451 return; 1452 } 1453 1454 vm_kernel_addrperm_external(addr, up_addr); 1455 return; 1456 } It looks like they've done some work on it; they've removed a number of address checks, such as VM_KERNEL_IS_KEXT, presumably to stop people from passing kext memory into userland. Userland should, instead, pass userland memory to the kernel, not vice versa. But these or statements don't seem to really answer the question. Our code path assumes that VM_KERNEL_IS_SLID returns true, and returns the address minus the slide. So let's look at that macro a bit more closely next. The VM_KERNEL_IS_SLID macro lives in osfmk/mach/vm_param.h. Again, if you can't immediately find it, you can simply cd into the xnu code base, then: grep -r VM_KERNEL_IS_SLID . Here is the Sierra version of this macro: 270 #define VM_KERNEL_IS_SLID(_o) \ 271 (((vm_offset_t)(_o) >= vm_kernel_slid_base) && \ 272 ((vm_offset_t)(_o) < vm_kernel_slid_top)) And here is the El Capitan version: 260 #define VM_KERNEL_IS_SLID(_o) \ 261 (((vm_offset_t)(_o) >= vm_kernel_base) && \ 262 ((vm_offset_t)(_o) <= vm_kernel_top)) Ah! It looks like they've changed the behavior of this macro slightly, so that the new version of the macro takes into account the slide value, but the old version of the macro doesn't. Because we're providing a base address that falls short of the aslr slide, it doesn't fall within the range that VM_KERNEL_IS_SLID is expecting, and so it's returning false, causing the code path we need in order to map the slide to not be called. The value we're passing in is the base address as it lives in the kernel on disk: #define KERNEL_BASE 0xffffff8000200000 But what we really need to pass in is a value that falls within the range of the slid kernel, in order to work on El Capitan. So how can we fix this? Well my initial thought was to simply add a large enough value to the base so that we could trigger our code path, but that's not really a scientific solution. What if the number we chose isn't big enough for some aslr slides? Then we get back garbage data again, and panic. Or what if the value we choose is too big and exceeds the kernel address space? We also get back garbage data and likely end up with a page fault. The solution to this is easy: We already know of a thousands of valid memory addresses all within the slid address space of the kernel. They are the exported symbols used in the public kpi. Pass in any one of them, and we should end up with a value falling within the correct range. Let's look again at our old code to calculate the slide: int64_t slide = 0; vm_offset_t slide_address = 0; vm_kernel_unslide_or_perm_external(KERNEL_BASE, &slide_address); slide = KERNEL_BASE - slide_address; int64_t base_address = slide + KERNEL_BASE; Knowing this won't work, let's instead pass in the address of a known symbol, say printf, into vm_kernel_unslide_or_perm_external, so that we know we will always fall within the correct range to return the slide value: vm_kernel_unslide_or_perm_external((unsigned long long)(void *)printf, &slide_address); We also have to adjust our math to calculate the slide value by replacing the KERNEL_BASE with the same value we fed into the function: slide = (unsigned long long)(void *)printf - slide_address; Now we have a solid slide value to work with, and we can calculate the base memory address off of that: int64_t base_address = slide + KERNEL_BASE; Everything works! Not just in El Capitan, but this is also forward compatible with Sierra, since the new code will also match anything falling within this address range. This is a small bug in the code that, by doing some basic reading of source code, can be solved with a simple solution that does not threaten the stability of the system. All it takes is a few minutes in the code and some deductive reasoning. ---------------------------------------------------------------------------