Full Report
This CTF challenge was a series of 6 challenges pertaining to AArch64 privilege escalation, which is similar to ARM64. The main differences are removal of Thumb instructions and doubling the general purpose registers. There are four exception levels for this: EL0, EL1, EL2 and EL3. EL0 is user mode, EL1 is supervisor, EL2 is hypervisor and EL3 is the firmware. On top of this, everything has a secure and non-secure world as well using ARM TrustZone. To run this, the particpiants had to use QEMU (with patches) with a BIOS image. The first stage is a statically linked ELF binary with an arbitrary call vulnerability via a back of index checking. To get code execution within this stage, an attacker can call the gets() with stdin to overwrite the function pointers within the .bss section. The LIBC is a modified version (of course), since it runs a custom minified OS. To attack the kernel, we're going to need the ability to execute arbitrary code easily. To do this, they wrote some shellcode then used the overflow to call mprotect() to make a section of memory executable. With this, we have escaped EL0 and can start attacking EL1. They detail their process of reversing the EL1 code, including an IDAPython extension that comments about MSRs and very low level instructions. Once they understood the memory mapping they began hunting for SVC handler (syscall) vulnerabilities. There is a classic vulnerability: missing validation on the destination address. This is similar to copy_to_user in Linux. using this, it's an easy write-what-where primitive within the kernel without any KASLR. Unfortunately, the read() syscall can only write a single byte at a time. This means we can't simply corrupt the return address on the stack to get code execution; we'll need to do something else. Eventually, they found a gadget that allows them to corrupt a single byte but jump to an arbitrary location on the PC. Before this, they need to write their shellcode into the kernel using the syscalls. Right now, there is only a limited payload that can be run. So, the author wanted to go from having a flag to full on code execution. Modern kernels have Supervisor Mode Execution Prevention (SMEP) which prevents the kernel from executing code on userland pages. While stepping in GDB, they found that this setting was not turned on but got a page fault. Why is this? When a processor receives a request to go from a virtual address to a physical address it does a page walk with multiple lookups. As a result, there is a Translation Lookaside Buffer (TLB) to speed up this process. These page tables have attributes like access permissions, execution permissions and more. After learning all of this, the author realized that their userland memory had bad permissions. So, they simply used their arbitrary write primitive to corrupt these bit to make the page executable from EL1. Nice! To communicate with EL2, there are hypervisor calls (hvc). The only functions into EL2 were for memory allocation is via mmap(). This takes in two parameters: an address and attributes. While looking at this functionality, there is a check to ensure that the hypervisor memory region cannot be written. However, the check is performed on the physical address and attributes separately before oring the bytes together. So, we can put swap the inputs to bypass the validation but map the intended address! This creates a window between the EL2 and EL1 address spaces. By calling a function in EL1 with the already mapped address from EL2, we can write into EL1 from EL2! They wrote EL2 code byte-by-byte into the RESET vector of the hypervisor. Then, once they triggered the RESET, their shellcode would execute. This is a fire post on exploiting very low level systems. This is just part 1; we need to go into the secure world now. Overall, loved this post and the challenges.
Analysis Summary
# Research: Super Hexagon: A Journey from EL0 to S-EL3
## Metadata
- **Authors:** Grant Hernandez
- **Institution:** University of Florida (Team: Kernel Sanders)
- **Publication:** Personal Technical Blog (hernan.de)
- **Date:** October 30, 2018
## Abstract
This research documents a systematic full-chain exploitation of an AArch64 (ARM64) system, progressing through three distinct privilege levels: User Mode (EL0), Kernel/Supervisor Mode (EL1), and Hypervisor Mode (EL2). The study utilizes a custom QEMU-based environment to demonstrate how traditional vulnerabilities (buffer overflows, missing validation) can be paired with architectural nuances (Page Table attributes, Hypervisor Call logic errors) to achieve cross-exception level execution.
## Research Objective
The objective was to achieve privilege escalation and arbitrary code execution across the ARMv8 exception hierarchy, specifically moving from non-secure userland (EL0) up to the hypervisor (EL2), and eventually into the Secure World (TrustZone).
## Methodology
### Approach
- **Vulnerability Research:** Manual auditing of SVC (system call) and HVC (hypervisor call) handlers.
- **Reverse Engineering:** Disassembly of the custom BIOS and minified OS using IDA Pro and IDAPython.
- **Exploitation Development:** Creating multi-stage shellcode to bypass hardware-level protections.
### Dataset/Environment
- **Architecture:** AArch64 (ARMv8-A profile).
- **Setup:** A custom QEMU machine type with 3GB RAM and a specific BIOS image simulating a Trusted Execution Environment (TEE) and Hypervisor (VMM).
### Tools & Technologies
- **QEMU:** Emulation with custom patches.
- **IDA Pro:** With the `ida-arm-system-highlight` plugin for MSR (System Register) instruction auditing.
- **GDB:** For dynamic analysis and kernel state inspection.
- **Python:** For exploit orchestration.
## Key Findings
### Primary Results
1. **EL0 to EL1:** Exploited an arbitrary call vulnerability via a missing index check in a "Trusted Keystore" service.
2. **EL1 to EL2:** Identified a logic flaw in the Hypervisor’s memory mapping function that allowed mapping sensitive hypervisor memory into the kernel's address space.
3. **Privilege Escalation:** Demonstrated that kernel code execution could be achieved by "bit-twiddling" page table attributes to bypass execution permissions.
### Supporting Evidence
- **Memory Corruption:** Successful overwrite of function pointers in the `.bss` section to redirect control flow.
- **Logic Bypass:** Discovered that the Hypervisor validated physical addresses and attributes separately before an OR operation, allowing it to be tricked into mapping unintended regions.
### Novel Contributions
- **System-Level Auditing:** Implementation of IDAPython scripts to automatically comment on low-level AArch64 instructions (MSR/MRS) to identify system register configurations.
- **Cross-Layer Shellcode:** Development of a "full-house" exploit that remains resident in memory while escalating through ELs.
## Technical Details
- **The Page Table Attack:** To execute code in EL1, the author bypassed the lack of executable user pages by using an arbitrary write primitive to modify the Translation Lookaside Buffer (TLB) descriptors/Page Table entries. By flipping the execution-related bits in the page table, the kernel was forced to treat user-supplied buffers as executable kernel memory.
- **The HVC Vulnerability:** The Hypervisor `mmap` function checked if `(physical_addr > 0x3bfff)` and if `(attributes != expected)`. However, because the check was performed on individual variables before they were merged, an attacker could provide a valid address in the attribute field and a valid attribute in the address field to "swap" them, bypassing the bounds check and mapping the Hypervisor’s own code (RESET vector) as writable.
## Practical Implications
### For Security Practitioners
- **Complex Chains:** Security is only as strong as the weakest boundary; a single EL0 overflow can eventually lead to firmware compromise if the hypervisor interface (HVC) is not defensively programmed.
### For Defenders
- **Input Sanitization:** "Copy-to-user"/Dest-address validation is critical. Any syscall that writes to a user-provided pointer must strictly validate that the pointer does not reside in kernel memory.
- **Validated Merging:** Ensure that security checks are performed on the *final* computed value (e.g., after bitwise ORing attributes and addresses), not just the raw inputs.
### For Researchers
- **AArch64 Nuances:** Unlike x86, AArch64 system registers (SCTLR_ELx) define unique security behaviors (like SMEP/PAN equivalents) that must be audited via specific MSR instructions.
## Limitations
- **Environment:** The research was conducted on a custom, minified OS which lacked some modern hardening features like KASLR (Kernel Address Space Layout Randomization).
- **Hardware:** Specific to AArch64; results may vary on ARMv7 or other architectures with different MMU implementations.
## Comparison to Prior Work
While traditional kernel exploits focus on Linux or Windows, this research targets a "bare-metal" style custom VMM/TEE environment, highlighting vulnerabilities in the glue logic that connects EL0 through EL2.
## Real-world Applications
- **AArch64 Platform Security:** Insights apply to mobile (Android/iOS) and cloud (Graviton) environments using ARMv8-A.
- **TrustZone Security:** Provides a roadmap for auditing TEE (Trusted Execution Environment) implementations.
## Future Work
- **Secure World Escalation:** Part II aims to move from EL2 to S-EL0 (Secure User Mode) and S-EL3 (Secure Monitor/Firmware).
- **32-bit/64-bit Transitions:** Exploring the complexity of "AArch32" (Thumb) execution within an AArch64 supervisor.
## References
- [Grant Hernandez - Super Hexagon Blog Post](https://hernan[.]de/blog/super-hexagon-a-journey-from-el0-to-s-el3/)
- [ARMv8-A Architecture Reference Manual](https://developer[.]arm[.]com/documentation/ddi0487/latest/)
- [IDA ARM System Highlight Plugin](github[.]com/gdelugre/ida-arm-system-highlight)