SEPC (Secure Enclave)
SEPC (Secure Enclave)
Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Linux | Author: D3v0o0Nu11 | Date: 2026-02-19 | Status: Solved Techniques: xor_key_recovery, kernel_module_analysis, rodata_extraction, char_device_protocol_reversing, initramfs_extraction
Summary
The challenge provides a bootable Linux system: bzImage (kernel), initramfs.cpio.gz (filesystem), run.sh (QEMU launch script). The goal is to reverse a kernel module that implements password verification through a character device and extract the flag from the .rodata section.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
HackTheBox| ID:20260219_hackthebox_sepc - Tags: xor, linux_kernel_module, char_device, qemu, initramfs, kernel_reversing, elf_static, cpio
- Indicators: bzImage + initramfs.cpio.gz + run.sh (QEMU boot), insmod .ko in init script, char device with custom major number, XOR of two rodata tables, byte-by-byte write/read protocol through /dev/ device
- Source:
20260219_hackthebox_sepc.md
Foothold
Vulnerability / Misconfiguration
- Xor_key_recovery
- Kernel_module_analysis
- Rodata_extraction
- Char_device_protocol_reversing
- Initramfs_extraction
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- xor_key_recovery
- kernel_module_analysis
- rodata_extraction
- char_device_protocol_reversing
- initramfs_extraction
- Tags: xor, linux_kernel_module, char_device, qemu, initramfs, kernel_reversing, elf_static, cpio
Original Writeup
<details><summary>Click to expand original content</summary>Description
We've extracted an embedded operating system running on an intercepted deep-space satellite launched by Arodor. If we can breach the secure enclave and extract their security mechanisms, we can crack their encrypted communications.
The challenge provides a bootable Linux system: bzImage (kernel), initramfs.cpio.gz (filesystem), run.sh (QEMU launch script). The goal is to reverse a kernel module that implements password verification through a character device and extract the flag from the .rodata section.
Analysis
Step 1: Extracting the Filesystem
Unpacking initramfs.cpio.gz reveals the structure:
/init — boot script
/checker — ELF 64-bit statically linked stripped (userspace)
/checker.ko — Linux kernel module (not stripped)
/initramfs.cpio.gz — internal initramfs (empty/incomplete)
The /init script shows the interaction architecture:
insmod checker.ko mount -t proc none /proc mount -t sysfs none /sys mknod /dev/checker c 137 0 chmod 0666 /dev/checker exec /checker
Key point: the kernel module checker.ko is loaded, a character device /dev/checker is created with major number 137, then the userspace binary /checker is launched which communicates with the kernel through this device.
Step 2: Analyzing the Kernel Module (checker.ko)
The module is not stripped — all symbols are available for analysis. It creates a character device with 4 handlers:
| Handler | Behavior |
|---|---|
| open | Single-open guard; initializes counter = 0, clears state |
| release | Resets the open flag |
| write | Copies exactly 1 byte from userspace to BSS buffer |
| read | Main verification logic — byte-by-byte XOR comparison |
The read handler implements byte-by-byte verification:
// Pseudocode of read handler
if (count != 1) return -EINVAL;
byte expected = rodata[0x60 + counter] ^ rodata[0x20 + counter];
if (user_byte != expected) {
result = 0; // FAIL — byte mismatch
} else {
counter++;
if (counter > 33) {
result = 2; // SUCCESS — all 34 bytes matched
counter = 0;
} else {
result = 1; // CONTINUE — need more bytes
}
}
copy_to_user(buf, &result, 1);
Critical comparison in assembly: cmp rax, 0x21 (33) with ja (jump if above) — 34 bytes are verified (indices 0–33).
Step 3: Analyzing the Userspace Binary (checker)
The /checker binary is a statically linked, stripped ELF. Logic:
- Prints
"Please enter your security key for offline verification" - Reads user input
- Opens
/dev/checker - For each input byte:
write(fd, &byte, 1)→read(fd, &result, 1) result == 0→"[X] PASSWORD REJECTED [X]"result == 2→"] PASSWORD VERIFIED ["
Protocol: userspace sends one byte at a time via write, the kernel compares and returns status via read. This is a classic userspace↔kernel communication pattern through a char device.
Step 4: Extracting XOR Tables from .rodata
Two 34-byte tables in the .rodata section of the kernel module:
Table 1 (offset 0x20):
b4 e4 e9 ab 09 36 4a c2 a5 14 e5 35 66 c3 99 14
5a 34 f1 18 91 7d 23 70 fa b5 3d fa 3d e5 00 d1
69 15
Table 2 (offset 0x60):
fc b0 ab d0 6e 44 2b a0 c7 7d 8b 52 39 a7 ad 60
6e 6b 97 6a a1 10 7c 1b c9 c7 53 c9 51 d0 70 e5
0a 26
Solution
Flag Extraction Script
#!/usr/bin/env python3
"""
SEPC (Secure Enclave) — HackTheBox
Extracting the flag by XORing two tables from .rodata of the kernel module checker.ko
"""
# Table 1: rodata offset 0x20 (34 bytes)
t1 = bytes([
0xb4, 0xe4, 0xe9, 0xab, 0x09, 0x36, 0x4a, 0xc2,
0xa5, 0x14, 0xe5, 0x35, 0x66, 0xc3, 0x99, 0x14,
0x5a, 0x34, 0xf1, 0x18, 0x91, 0x7d, 0x23, 0x70,
0xfa, 0xb5, 0x3d, 0xfa, 0x3d, 0xe5, 0x00, 0xd1,
0x69, 0x15
])
# Table 2: rodata offset 0x60 (34 bytes)
t2 = bytes([
0xfc, 0xb0, 0xab, 0xd0, 0x6e, 0x44, 0x2b, 0xa0,
0xc7, 0x7d, 0x8b, 0x52, 0x39, 0xa7, 0xad, 0x60,
0x6e, 0x6b, 0x97, 0x6a, 0xa1, 0x10, 0x7c, 0x1b,
0xc9, 0xc7, 0x53, 0xc9, 0x51, 0xd0, 0x70, 0xe5,
0x0a, 0x26
])
# XOR of two tables = expected password
flag_bytes = bytes([a ^ b for a, b in zip(t1, t2)])
flag = flag_bytes.decode('ascii')
print(f"Flag: {flag}}}")
# HTB{REDACTED}
Explanation
The kernel module verifies 34 bytes (the flag content without the closing curly brace }). Each byte is checked as input_byte == table1[i] ^ table2[i]. A simple XOR of the two tables from .rodata yields:
HTB{REDACTED
The closing brace } is added by the userspace binary context (the 35th character is not verified by the module).
Lessons
- Start with the init script — it shows the entire architecture: which modules are loaded, which devices are created, what runs in userspace
- Kernel module > userspace binary — if the
.kois not stripped but userspace is stripped, reverse the module first: symbols and handler structure will give you the complete picture - Char device protocol —
writesends data to the kernel,readreceives the result. Understanding this protocol is key to understanding the verification logic - XOR in .rodata — if you see two arrays of equal length and an XOR operation between them, it's almost certainly a key/flag. Extract the tables and XOR them offline
- Count the bytes —
cmp rax, 0x21+jameans 34 bytes (0–33), not 33. An off-by-one error can cost you the flag
Alternative Approaches
- Dynamic analysis — run the system in QEMU, attach GDB to the kernel (
-s -S), set a breakpoint on the read handler and observe the XOR operation - Module patching — modify
checker.koto output the expected bytes todmesginstead of comparing - Bruteforce via QEMU — send all 256 variants byte by byte, determine the correct byte by the response (0 vs 1). Slow, but doesn't require reversing the module
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR