pwnfreehard

multifiles

b01lersc

Task: Linux kernel module with custom slab cache and OOB read/write via incomplete bounds check. Solution: SLUB freelist poisoning with safe-linking bypass, tail object technique for cross-page access, struct file spray to leak cred pointer, then cred corruption for privilege escalation.

$ ls tags/ techniques/
safe_linking_bypassslub_freelist_poisoningtail_object_techniquestruct_file_spraycred_corruption

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

multifiles - b01lersc CTF

Description

build artifacts in build_out/ rebuild with pwn_build.sh run challenge with dev.sh

A Linux kernel module called "multifiles" implements a custom slab cache for managing file-like objects. The challenge runs on Linux 6.12.81 with full kernel hardening enabled:

  • KASLR, SMEP, SMAP, PTI
  • CONFIG_SLAB_FREELIST_HARDENED=y
  • CONFIG_HARDENED_USERCOPY=y
  • CONFIG_STATIC_USERMODEHELPER is not set

User runs as ctf (uid=1000), flag is in /root/flag.txt.

Analysis

Kernel Module Structure

The module creates a custom slab cache for MultiFile objects:

typedef struct { u64 type; // offset 0 u64 flags; // offset 8 char name[16]; // offset 16 u64 data[16]; // offset 32 (128 bytes) } MultiFile; // Total: 160 bytes

The cache is created with SLAB_NO_MERGE to prevent merging with other caches:

multifiles_cache = kmem_cache_create_usercopy( MODULE_NAME "_cache", sizeof(MultiFile), // 160 bytes 0, SLAB_NO_MERGE, offsetof(MultiFile, name), // 16 USERCOPY_SIZE, // 144 (name + data) NULL );

With 160-byte objects, each 4096-byte slab page holds 25 objects (25 x 160 = 4000 bytes), leaving 96 bytes unused at the end of each page.

Vulnerability: Incomplete Bounds Check

The read/write operations have a flawed bounds check:

// check read/write bounds if ( count > MAX_RW_SIZE // count <= 64 || (count % sizeof(u64)) != 0 // count % 8 == 0 || *offset >= sizeof(MultiFile) // offset < 160 || *offset < 0 ) { ret = -EINVAL; goto out_unlock; }

Bug: The check verifies offset < sizeof(MultiFile) but NOT offset + count <= sizeof(MultiFile).

With offset = 152 and count = 64, we can read/write bytes 152-215, which extends 56 bytes past the object boundary into adjacent memory (or the next object's freelist pointer when freed).

SLUB Safe-Linking

...

$ grep --similar

Similar writeups