[Kernel] BlackhatMEA Quals 2024 - CPL0 challenge - Interrupt Descriptor Table (IDT) Hooking
( بِسْمِ اللَّـهِ الرَّحْمَـٰنِ الرَّحِيمِ )
Hey guys,
let's try to solve a kernel exploit, and challenge CPL0 from BlackhatMEA Quals 2024 thanks to Saif (@wr3nchsr) and Sameh (@s4muii) for help understanding this challenge. This challenge has a new idea for me so I learned a lot so let's talk about it.
now we will do the following:
- do recon on the challenge files
- get root on the local version for debugging (by modifying the file system)
- use bash scripts to compress and decompress the file system to pass it to qemu
- list what we will have and do in the challenge
- EXPLOIT
Recon
we have
- bzImage: Kernel
- rootfs.cpio: the file system
- run.sh: bash script to run qemu
- qemu-system-x86_64: a modified qemu binary
- qemu.diff: a diff file to the modified qemu binary
if we check the diff file
we can note that the check_cpl0 function is modified and will return "true" so we have access to CPL0, but what is CPL0???
Current Privilege Level (CPL) has different levels (0, 1, 2, 3) -chatGPT answer xD-
- CPL 0 (Ring 0) – Kernel mode (highest privilege, can execute all instructions).
- CPL 1 (Ring 1) – Usually unused or for device drivers (not common).
- CPL 2 (Ring 2) – Usually unused or for specific OS services (not common).
- CPL 3 (Ring 3) – User mode (lowest privilege, restricted access).
we have access to Ring 0 (CPL0) the highest privilege on the OS, what do we do with CPL0?
- Execute more CPU instructions
- Access system memory
- Hardware Devices
the interesting thing here is the CPU instructions that we can execute and i asked ChatGPT to check the answer (but from Saif, i knew what is the target which is IDT)
- Interrupt Descriptor Table (IDT) (we can load and store addresses in this table)
- Model-Specific Register (MSR) (extracts privileged info) (instruction: rdmsr)
"decompress.sh" will give us the file system so we can do our modification for debugging.
We know from "run.sh" there is KASLR in the kernel so we will disable it from "run.sh" and the loaded bash in the boot (etc/init.d/S99ctf) we will set the value in "kptr_restrict" to 1 not 2 and will modify the id in line 4 to be 0 (root), not 1000 (user) -this for debug-
The compress.sh and decompress.sh as the following will be used locally to debug
we will try to do IDT Hooking (manipulate IDT) with instructions (sidt, lidt) and the exploit will use "commit_creds(prepare_kernel_cred(&init_task))" If we call this we will be in root level (maybe like setuid(0)) and then we will execute "get_shell" function, but how?
EXPLOIT
our exploit will
- store the idt table into our script
- creates memory location "fake_idt" to store the manipulated IDT on it
- store the "exploit" function address into "fake_idt" but in the IDT format
- push the "fake_idt" to the memory
- save the state of the registered we will use it in PrivEsc
- write our exploit (and defeat the KASLR)
- trigger the IDT
setup the IDT table struct and other variables
creating the fake chunk filling the "exploit" function address to it in the right format, storing the "fake_idt" to the "new_idt" and loading it to the memory again
- 0x8E (Interrupt Gate Type & Attributes)
- 0x10 (Kernel Code Segment Selector)
the main function will do the magic
now we will save the current state of registers because we will use it again in the exploit
finally the exploit function, first we will store the "old_idt" in memory again and start with the exploit.
We have to defeat the KASLR somehow, I learned this from Saif before knowing it from chatGPT is the method using MSR register we will read the MSR (rdmsr) and it will be stored in (rdx=low 32 bits and rax=high 32 bits) will combine the two parts and subtract the offset of MSR register from it and now we will have the base kernel address.
Example:
now we will complete the exploit in assembly to call "commit_creds(prepare_kernel_cred(&init_task))" and return the state of registers.
we got the shell ;-).
Thank you for reading, the solver.
Comments
Post a Comment